root/drivers/media/pci/solo6x10/solo6x10-eeprom.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
 *
 * Original author:
 * Ben Collins <bcollins@ubuntu.com>
 *
 * Additional work by:
 * John Brooks <john.brooks@bluecherry.net>
 */

#include <linux/kernel.h>
#include <linux/delay.h>

#include "solo6x10.h"

/* Control */
#define EE_SHIFT_CLK    0x04
#define EE_CS           0x08
#define EE_DATA_WRITE   0x02
#define EE_DATA_READ    0x01
#define EE_ENB          (0x80 | EE_CS)

#define eeprom_delay()  udelay(100)
#if 0
#define eeprom_delay()  solo_reg_read(solo_dev, SOLO_EEPROM_CTRL)
#define eeprom_delay()  ({                              \
        int i, ret;                                     \
        udelay(100);                                    \
        for (i = ret = 0; i < 1000 && !ret; i++)        \
                ret = solo_eeprom_reg_read(solo_dev);   \
})
#endif
#define ADDR_LEN        6

/* Commands */
#define EE_EWEN_CMD     4
#define EE_EWDS_CMD     4
#define EE_WRITE_CMD    5
#define EE_READ_CMD     6
#define EE_ERASE_CMD    7

static unsigned int solo_eeprom_reg_read(struct solo_dev *solo_dev)
{
        return solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) & EE_DATA_READ;
}

static void solo_eeprom_reg_write(struct solo_dev *solo_dev, u32 data)
{
        solo_reg_write(solo_dev, SOLO_EEPROM_CTRL, data);
        eeprom_delay();
}

static void solo_eeprom_cmd(struct solo_dev *solo_dev, int cmd)
{
        int i;

        solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ACCESS_EN);
        solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);

        for (i = 4 + ADDR_LEN; i >= 0; i--) {
                int dataval = (cmd & (1 << i)) ? EE_DATA_WRITE : 0;

                solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | dataval);
                solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
                                      EE_SHIFT_CLK | dataval);
        }

        solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
}

unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en)
{
        int ewen_cmd = (w_en ? 0x3f : 0) | (EE_EWEN_CMD << ADDR_LEN);
        unsigned int retval = 0;
        int i;

        solo_eeprom_cmd(solo_dev, ewen_cmd);

        for (i = 0; i < 16; i++) {
                solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
                                      EE_SHIFT_CLK);
                retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
                solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
                retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
        }

        solo_eeprom_reg_write(solo_dev, ~EE_CS);
        retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);

        return retval;
}

__be16 solo_eeprom_read(struct solo_dev *solo_dev, int loc)
{
        int read_cmd = loc | (EE_READ_CMD << ADDR_LEN);
        u16 retval = 0;
        int i;

        solo_eeprom_cmd(solo_dev, read_cmd);

        for (i = 0; i < 16; i++) {
                solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
                                      EE_SHIFT_CLK);
                retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
                solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
        }

        solo_eeprom_reg_write(solo_dev, ~EE_CS);

        return (__force __be16)retval;
}

int solo_eeprom_write(struct solo_dev *solo_dev, int loc,
                      __be16 data)
{
        int write_cmd = loc | (EE_WRITE_CMD << ADDR_LEN);
        unsigned int retval;
        int i;

        solo_eeprom_cmd(solo_dev, write_cmd);

        for (i = 15; i >= 0; i--) {
                unsigned int dataval = ((__force unsigned)data >> i) & 1;

                solo_eeprom_reg_write(solo_dev, EE_ENB);
                solo_eeprom_reg_write(solo_dev,
                                      EE_ENB | (dataval << 1) | EE_SHIFT_CLK);
        }

        solo_eeprom_reg_write(solo_dev, EE_ENB);
        solo_eeprom_reg_write(solo_dev, ~EE_CS);
        solo_eeprom_reg_write(solo_dev, EE_ENB);

        for (i = retval = 0; i < 10000 && !retval; i++)
                retval = solo_eeprom_reg_read(solo_dev);

        solo_eeprom_reg_write(solo_dev, ~EE_CS);

        return !retval;
}