root/drivers/media/pci/solo6x10/solo6x10-i2c.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>
 */

/* XXX: The SOLO6x10 i2c does not have separate interrupts for each i2c
 * channel. The bus can only handle one i2c event at a time. The below handles
 * this all wrong. We should be using the status registers to see if the bus
 * is in use, and have a global lock to check the status register. Also,
 * the bulk of the work should be handled out-of-interrupt. The ugly loops
 * that occur during interrupt scare me. The ISR should merely signal
 * thread context, ACK the interrupt, and move on. -- BenC */

#include <linux/kernel.h>
#include <linux/sched/signal.h>

#include "solo6x10.h"

u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off)
{
        struct i2c_msg msgs[2];
        u8 data;

        msgs[0].flags = 0;
        msgs[0].addr = addr;
        msgs[0].len = 1;
        msgs[0].buf = &off;

        msgs[1].flags = I2C_M_RD;
        msgs[1].addr = addr;
        msgs[1].len = 1;
        msgs[1].buf = &data;

        i2c_transfer(&solo_dev->i2c_adap[id], msgs, 2);

        return data;
}

void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr,
                        u8 off, u8 data)
{
        struct i2c_msg msgs;
        u8 buf[2];

        buf[0] = off;
        buf[1] = data;
        msgs.flags = 0;
        msgs.addr = addr;
        msgs.len = 2;
        msgs.buf = buf;

        i2c_transfer(&solo_dev->i2c_adap[id], &msgs, 1);
}

static void solo_i2c_flush(struct solo_dev *solo_dev, int wr)
{
        u32 ctrl;

        ctrl = SOLO_IIC_CH_SET(solo_dev->i2c_id);

        if (solo_dev->i2c_state == IIC_STATE_START)
                ctrl |= SOLO_IIC_START;

        if (wr) {
                ctrl |= SOLO_IIC_WRITE;
        } else {
                ctrl |= SOLO_IIC_READ;
                if (!(solo_dev->i2c_msg->flags & I2C_M_NO_RD_ACK))
                        ctrl |= SOLO_IIC_ACK_EN;
        }

        if (solo_dev->i2c_msg_ptr == solo_dev->i2c_msg->len)
                ctrl |= SOLO_IIC_STOP;

        solo_reg_write(solo_dev, SOLO_IIC_CTRL, ctrl);
}

static void solo_i2c_start(struct solo_dev *solo_dev)
{
        u32 addr = solo_dev->i2c_msg->addr << 1;

        if (solo_dev->i2c_msg->flags & I2C_M_RD)
                addr |= 1;

        solo_dev->i2c_state = IIC_STATE_START;
        solo_reg_write(solo_dev, SOLO_IIC_TXD, addr);
        solo_i2c_flush(solo_dev, 1);
}

static void solo_i2c_stop(struct solo_dev *solo_dev)
{
        solo_irq_off(solo_dev, SOLO_IRQ_IIC);
        solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0);
        solo_dev->i2c_state = IIC_STATE_STOP;
        wake_up(&solo_dev->i2c_wait);
}

static int solo_i2c_handle_read(struct solo_dev *solo_dev)
{
prepare_read:
        if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) {
                solo_i2c_flush(solo_dev, 0);
                return 0;
        }

        solo_dev->i2c_msg_ptr = 0;
        solo_dev->i2c_msg++;
        solo_dev->i2c_msg_num--;

        if (solo_dev->i2c_msg_num == 0) {
                solo_i2c_stop(solo_dev);
                return 0;
        }

        if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) {
                solo_i2c_start(solo_dev);
        } else {
                if (solo_dev->i2c_msg->flags & I2C_M_RD)
                        goto prepare_read;
                else
                        solo_i2c_stop(solo_dev);
        }

        return 0;
}

static int solo_i2c_handle_write(struct solo_dev *solo_dev)
{
retry_write:
        if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) {
                solo_reg_write(solo_dev, SOLO_IIC_TXD,
                               solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr]);
                solo_dev->i2c_msg_ptr++;
                solo_i2c_flush(solo_dev, 1);
                return 0;
        }

        solo_dev->i2c_msg_ptr = 0;
        solo_dev->i2c_msg++;
        solo_dev->i2c_msg_num--;

        if (solo_dev->i2c_msg_num == 0) {
                solo_i2c_stop(solo_dev);
                return 0;
        }

        if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) {
                solo_i2c_start(solo_dev);
        } else {
                if (solo_dev->i2c_msg->flags & I2C_M_RD)
                        solo_i2c_stop(solo_dev);
                else
                        goto retry_write;
        }

        return 0;
}

int solo_i2c_isr(struct solo_dev *solo_dev)
{
        u32 status = solo_reg_read(solo_dev, SOLO_IIC_CTRL);
        int ret = -EINVAL;


        if (CHK_FLAGS(status, SOLO_IIC_STATE_TRNS | SOLO_IIC_STATE_SIG_ERR)
            || solo_dev->i2c_id < 0) {
                solo_i2c_stop(solo_dev);
                return -ENXIO;
        }

        switch (solo_dev->i2c_state) {
        case IIC_STATE_START:
                if (solo_dev->i2c_msg->flags & I2C_M_RD) {
                        solo_dev->i2c_state = IIC_STATE_READ;
                        ret = solo_i2c_handle_read(solo_dev);
                        break;
                }

                solo_dev->i2c_state = IIC_STATE_WRITE;
                fallthrough;
        case IIC_STATE_WRITE:
                ret = solo_i2c_handle_write(solo_dev);
                break;

        case IIC_STATE_READ:
                solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr] =
                        solo_reg_read(solo_dev, SOLO_IIC_RXD);
                solo_dev->i2c_msg_ptr++;

                ret = solo_i2c_handle_read(solo_dev);
                break;

        default:
                solo_i2c_stop(solo_dev);
        }

        return ret;
}

static int solo_i2c_master_xfer(struct i2c_adapter *adap,
                                struct i2c_msg msgs[], int num)
{
        struct solo_dev *solo_dev = adap->algo_data;
        unsigned long timeout;
        int ret;
        int i;
        DEFINE_WAIT(wait);

        for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
                if (&solo_dev->i2c_adap[i] == adap)
                        break;
        }

        if (i == SOLO_I2C_ADAPTERS)
                return num; /* XXX Right return value for failure? */

        mutex_lock(&solo_dev->i2c_mutex);
        solo_dev->i2c_id = i;
        solo_dev->i2c_msg = msgs;
        solo_dev->i2c_msg_num = num;
        solo_dev->i2c_msg_ptr = 0;

        solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0);
        solo_irq_on(solo_dev, SOLO_IRQ_IIC);
        solo_i2c_start(solo_dev);

        timeout = HZ / 2;

        for (;;) {
                prepare_to_wait(&solo_dev->i2c_wait, &wait,
                                TASK_INTERRUPTIBLE);

                if (solo_dev->i2c_state == IIC_STATE_STOP)
                        break;

                timeout = schedule_timeout(timeout);
                if (!timeout)
                        break;

                if (signal_pending(current))
                        break;
        }

        finish_wait(&solo_dev->i2c_wait, &wait);
        ret = num - solo_dev->i2c_msg_num;
        solo_dev->i2c_state = IIC_STATE_IDLE;
        solo_dev->i2c_id = -1;

        mutex_unlock(&solo_dev->i2c_mutex);

        return ret;
}

static u32 solo_i2c_functionality(struct i2c_adapter *adap)
{
        return I2C_FUNC_I2C;
}

static const struct i2c_algorithm solo_i2c_algo = {
        .master_xfer    = solo_i2c_master_xfer,
        .functionality  = solo_i2c_functionality,
};

int solo_i2c_init(struct solo_dev *solo_dev)
{
        int i;
        int ret;

        solo_reg_write(solo_dev, SOLO_IIC_CFG,
                       SOLO_IIC_PRESCALE(8) | SOLO_IIC_ENABLE);

        solo_dev->i2c_id = -1;
        solo_dev->i2c_state = IIC_STATE_IDLE;
        init_waitqueue_head(&solo_dev->i2c_wait);
        mutex_init(&solo_dev->i2c_mutex);

        for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
                struct i2c_adapter *adap = &solo_dev->i2c_adap[i];

                snprintf(adap->name, I2C_NAME_SIZE, "%s I2C %d",
                         SOLO6X10_NAME, i);
                adap->algo = &solo_i2c_algo;
                adap->algo_data = solo_dev;
                adap->retries = 1;
                adap->dev.parent = &solo_dev->pdev->dev;

                ret = i2c_add_adapter(adap);
                if (ret) {
                        adap->algo_data = NULL;
                        break;
                }
        }

        if (ret) {
                for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
                        if (!solo_dev->i2c_adap[i].algo_data)
                                break;
                        i2c_del_adapter(&solo_dev->i2c_adap[i]);
                        solo_dev->i2c_adap[i].algo_data = NULL;
                }
                return ret;
        }

        return 0;
}

void solo_i2c_exit(struct solo_dev *solo_dev)
{
        int i;

        for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
                if (!solo_dev->i2c_adap[i].algo_data)
                        continue;
                i2c_del_adapter(&solo_dev->i2c_adap[i]);
                solo_dev->i2c_adap[i].algo_data = NULL;
        }
}