forked from Minki/linux
c942fddf87
Based on 3 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
324 lines
6.8 KiB
C
324 lines
6.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2010-2013 Bluecherry, LLC <http://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;
|
|
/* fall through */
|
|
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;
|
|
}
|
|
}
|