// 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; 
 | 
    } 
 | 
} 
 |