1063 lines
27 KiB
C
1063 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/interconnect.h>
|
|
#include <linux/io.h>
|
|
#include <linux/ipc_logging.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
#include "i2c-slave.h"
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
#include <trace/i2c_slave_trace.h>
|
|
|
|
/**
|
|
* i2c_slave_trace_log: FTRACE Logging.
|
|
* @dev: Driver model device node for the i2c slave.
|
|
* @fmt: ftrace log format.
|
|
*
|
|
* This function will add logs to ftrace
|
|
* file.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
void i2c_slave_trace_log(struct device *dev, const char *fmt, ...)
|
|
{
|
|
struct va_format vaf = {
|
|
.fmt = fmt,
|
|
};
|
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
vaf.va = &args;
|
|
trace_i2c_slave_log_info(dev_name(dev), &vaf);
|
|
va_end(args);
|
|
}
|
|
|
|
/**
|
|
* dump_register - To dump the register value.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will dump the all readable register
|
|
* value to IPC log.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
static void dump_register(struct i2c_slave *i2c_slave)
|
|
{
|
|
unsigned int temp;
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_DEVICE_ADDR);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_DEVICE_ADDR: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_IRQ_STATUS);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_IRQ_STATUS: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_CONFIG);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_CONFIG: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_IRQ_EN);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_IRQ_EN: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_FIFOS_STATUS);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_FIFOS_STATUS: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_DEBUG_REG1);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_DEBUG_REG1: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_DEBUG_REG2);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_DEBUG_REG2: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_CLK_LOW_TIMEOUT);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_CLK_LOW_TIMEOUT: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_CLK_RELEASE_DELAY_CNT_VAL);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_CLK_RELEASE_DELAY_CNT_VAL: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + I2C_S_SDA_HOLD_CNT_VAL);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"I2C_S_SDA_HOLD_CNT_VAL: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + SMBALERT_STATUS_REG);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"SMBALERT_STATUS_REG: 0x%x\n", temp);
|
|
|
|
temp = readl_relaxed(i2c_slave->base + SMBALERT_PEC_REG);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"SMBALERT_PEC_REG: 0x%x\n", temp);
|
|
}
|
|
|
|
/**
|
|
* read_tx_fifo_byte_count: To read TX FIFO count.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will read the number of bytes written
|
|
* to the TX FIFO.
|
|
*
|
|
* Return: Number of bytes written to the TX FIFO.
|
|
*/
|
|
static unsigned int read_tx_fifo_byte_count(struct i2c_slave *i2c_slave)
|
|
{
|
|
unsigned int count = 0;
|
|
|
|
count = readl_relaxed(i2c_slave->base + I2C_S_FIFOS_STATUS);
|
|
return (count & 0xFFFF);
|
|
}
|
|
|
|
/**
|
|
* read_rx_fifo_byte_count: To read RX FIFO count.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will read the number of bytes written
|
|
* to the RX FIFO.
|
|
*
|
|
* Return: Number of bytes written to the RX FIFO.
|
|
*/
|
|
static unsigned int read_rx_fifo_byte_count(struct i2c_slave *i2c_slave)
|
|
{
|
|
unsigned int count = 0;
|
|
|
|
count = readl_relaxed(i2c_slave->base + I2C_S_FIFOS_STATUS);
|
|
return ((count & 0xFFFF0000) >> 16);
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_write_fifo: This function will write data to TX FIFO.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will write data to TX FIFO.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
static void i2c_slave_write_fifo(struct i2c_slave *i2c_slave)
|
|
{
|
|
int i;
|
|
|
|
if (i2c_slave->tx_count == 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"TX FIFO write count is zero\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < i2c_slave->tx_count; i++) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"Data to Tx FIFO: 0x%x\n", i2c_slave->tx_msg_buf[i]);
|
|
writel_relaxed(i2c_slave->tx_msg_buf[i],
|
|
i2c_slave->base + I2C_S_TX_FIFO);
|
|
}
|
|
|
|
i2c_slave->tx_count = 0;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_read_fifo: This function will read data to RX FIFO.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will read data to RX FIFO.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
static void i2c_slave_read_fifo(struct i2c_slave *i2c_slave)
|
|
{
|
|
unsigned int rx_data_count;
|
|
int i;
|
|
|
|
rx_data_count = read_rx_fifo_byte_count(i2c_slave);
|
|
if (rx_data_count == 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"RX FIFO empty\n");
|
|
} else if (i2c_slave->rx_count >= I2C_SLAVE_MAX_MSG_SIZE) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"RX data buffer full\n");
|
|
} else {
|
|
for (i = 0; i < rx_data_count &&
|
|
i2c_slave->rx_count < I2C_SLAVE_MAX_MSG_SIZE; i++) {
|
|
i2c_slave->rx_msg_buf[i2c_slave->rx_count] =
|
|
readl_relaxed(i2c_slave->base + I2C_S_RX_FIFO);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"Data from RX_FIFO: 0x%x",
|
|
i2c_slave->rx_msg_buf[i2c_slave->rx_count]);
|
|
i2c_slave->rx_count++;
|
|
}
|
|
wake_up_interruptible(&i2c_slave->readq);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_write_bit: To SET the register bit.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
* @reg: offset of Register address.
|
|
* @bit: bit to modify.
|
|
*
|
|
* This function will set the bit to given register.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
|
|
static void i2c_slave_write_bit(struct i2c_slave *i2c_slave, int reg, int bit)
|
|
{
|
|
unsigned int temp;
|
|
|
|
temp = readl_relaxed(i2c_slave->base + reg) | bit;
|
|
writel_relaxed(temp, i2c_slave->base + reg);
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_send_ack: To send ACK to master.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will send ACK to master.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
static void i2c_slave_send_ack(struct i2c_slave *i2c_slave)
|
|
{
|
|
writel_relaxed(ACK_RESUME, i2c_slave->base + I2C_S_CONTROL);
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_send_nack: To send NACK to master.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will send NACK to master.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
|
|
static void i2c_slave_send_nack(struct i2c_slave *i2c_slave)
|
|
{
|
|
writel_relaxed(NACK, i2c_slave->base + I2C_S_CONTROL);
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_clear_irq: To clear IRQ bit
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
* enum: enum to IRQ bit
|
|
*
|
|
* This function will clear the handled IRQ bit.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
static void i2c_slave_clear_irq(struct i2c_slave *i2c_slave,
|
|
enum i2c_slave_irq_status bit)
|
|
{
|
|
if (bit == ALL_IRQ)
|
|
writel_relaxed(ALL_IRQ, i2c_slave->base + I2C_S_IRQ_CLR);
|
|
else
|
|
writel_relaxed(1 << bit, i2c_slave->base + I2C_S_IRQ_CLR);
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_enable_irq: To enable IRQ.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will enable the IRQ.
|
|
*
|
|
* Return: None.
|
|
*/
|
|
static void i2c_slave_enable_irq(struct i2c_slave *i2c_slave)
|
|
{
|
|
writel_relaxed(ALL_IRQ, i2c_slave->base + I2C_S_IRQ_EN);
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_irq: I2C Slave IQR handler
|
|
* @irq: IRQ number
|
|
* @dev: Pointer to Dev Structure.
|
|
*
|
|
* This function will handle IRQ bits.
|
|
*
|
|
* return: IRQ_HANDLED for success.
|
|
*/
|
|
static irqreturn_t i2c_slave_irq(int irq, void *dev)
|
|
{
|
|
unsigned int irq_stat;
|
|
struct i2c_slave *i2c_slave = dev;
|
|
|
|
irq_stat = readl_relaxed(i2c_slave->base + I2C_S_IRQ_STATUS);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"irq status: 0x%x\n", irq_stat);
|
|
|
|
if (irq_stat & (1 << ERR_CONDITION)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[ERR_CONDITION]);
|
|
dump_register(i2c_slave);
|
|
i2c_slave_write_bit(i2c_slave, I2C_S_SW_RESET_REG, SW_RESET);
|
|
i2c_slave_clear_irq(i2c_slave, ALL_IRQ);
|
|
i2c_slave_enable_irq(i2c_slave);
|
|
i2c_slave_write_bit(i2c_slave, I2C_S_CONTROL,
|
|
CLEAR_TX_FIFO | CLEAR_RX_FIFO);
|
|
i2c_slave_write_bit(i2c_slave, I2C_S_CLK_LOW_TIMEOUT, TIMER_MODE);
|
|
i2c_slave_write_bit(i2c_slave, I2C_S_CONFIG, CORE_EN);
|
|
i2c_slave_send_nack(i2c_slave);
|
|
}
|
|
|
|
if (irq_stat & (1 << CLOCK_LOW_TIMEOUT)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[CLOCK_LOW_TIMEOUT]);
|
|
i2c_slave_clear_irq(i2c_slave, ALL_IRQ);
|
|
}
|
|
|
|
if (irq_stat & (1 << STOP_DETECTED)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[STOP_DETECTED]);
|
|
i2c_slave_clear_irq(i2c_slave, STOP_DETECTED);
|
|
i2c_slave_read_fifo(i2c_slave);
|
|
}
|
|
|
|
if (irq_stat & (1 << RX_FIFO_FULL)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[RX_FIFO_FULL]);
|
|
i2c_slave_send_nack(i2c_slave);
|
|
i2c_slave_clear_irq(i2c_slave, RX_FIFO_FULL);
|
|
}
|
|
|
|
if (irq_stat & (1 << STRCH_RD)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[STRCH_RD]);
|
|
i2c_slave->tx_count = read_tx_fifo_byte_count(i2c_slave);
|
|
if (i2c_slave->tx_count > 0) {
|
|
i2c_slave_send_ack(i2c_slave);
|
|
} else {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"TX FIFO empty\n");
|
|
i2c_slave_send_nack(i2c_slave);
|
|
}
|
|
i2c_slave_clear_irq(i2c_slave, STRCH_RD);
|
|
}
|
|
|
|
if (irq_stat & (1 << RX_DATA_AVAIL)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[RX_DATA_AVAIL]);
|
|
i2c_slave_clear_irq(i2c_slave, RX_DATA_AVAIL);
|
|
}
|
|
|
|
if (irq_stat & (1 << STRCH_WR)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[STRCH_WR]);
|
|
i2c_slave_clear_irq(i2c_slave, STRCH_WR);
|
|
i2c_slave_send_ack(i2c_slave);
|
|
}
|
|
|
|
if (irq_stat & (1 << TX_FIFO_EMPTY)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[TX_FIFO_EMPTY]);
|
|
i2c_slave->tx_count = read_tx_fifo_byte_count(i2c_slave);
|
|
i2c_slave_write_fifo(i2c_slave);
|
|
i2c_slave_clear_irq(i2c_slave, TX_FIFO_EMPTY);
|
|
}
|
|
|
|
if (irq_stat & (1 << GCA_DETECTED)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[GCA_DETECTED]);
|
|
i2c_slave_send_nack(i2c_slave);
|
|
i2c_slave_clear_irq(i2c_slave, GCA_DETECTED);
|
|
}
|
|
|
|
if (irq_stat & (1 << RESTART_DETECTED)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[RESTART_DETECTED]);
|
|
i2c_slave_send_ack(i2c_slave);
|
|
i2c_slave_clear_irq(i2c_slave, RESTART_DETECTED);
|
|
}
|
|
|
|
if (irq_stat & (1 << SMBALERT_ARA_DONE)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[SMBALERT_ARA_DONE]);
|
|
i2c_slave_clear_irq(i2c_slave, SMBALERT_ARA_DONE);
|
|
}
|
|
|
|
if (irq_stat & (1 << SMBALERT_LOST_ARB)) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev, "%s\n",
|
|
irq_log[SMBALERT_LOST_ARB]);
|
|
i2c_slave_clear_irq(i2c_slave, SMBALERT_LOST_ARB);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_write: write data to fifo.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
* @buf: TX Data buffer.
|
|
* @count: TX Data count.
|
|
*
|
|
* This function will write data to TX FIFO.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
static int i2c_slave_write(struct i2c_slave *i2c_slave, uint8_t *buf, size_t count)
|
|
{
|
|
if (!buf) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Invalid write buffer\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!count) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"Write count is zero\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
i2c_slave->tx_msg_buf = buf;
|
|
i2c_slave->tx_count = count;
|
|
i2c_slave_write_fifo(i2c_slave);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_read: read data from fifo.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
* @buf: RX Data buffer.
|
|
* @count: RX Data count.
|
|
*
|
|
* This function will read data from RX FIFO.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
|
|
static int i2c_slave_read(struct i2c_slave *i2c_slave, uint8_t *buf, size_t count)
|
|
{
|
|
int i;
|
|
|
|
if (!buf) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Invalid Read buffer\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (count == 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"Read count is zero\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (count <= I2C_SMBUS_BYTE_DATA &&
|
|
i2c_slave->rx_count < count) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Data not available\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (count > I2C_SMBUS_BYTE_DATA)
|
|
count = i2c_slave->rx_count;
|
|
|
|
i2c_slave->rx_count -= count;
|
|
for (i = 0; i < count; i++)
|
|
buf[i] = i2c_slave->rx_msg_buf[i];
|
|
|
|
if (count <= I2C_SMBUS_BYTE_DATA) {
|
|
for (i = 0; i < i2c_slave->rx_count; i++)
|
|
i2c_slave->rx_msg_buf[i] =
|
|
i2c_slave->rx_msg_buf[i + count];
|
|
}
|
|
|
|
i2c_slave_read_fifo(i2c_slave);
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_xfer: SMbus transfer function.
|
|
* @adap: I2C driver adapter
|
|
* @addr: Slave address
|
|
* @flags: i2c client flag
|
|
* @read_write: Read/Write flag
|
|
* @command: SMbus command code
|
|
* @protocol: Command type
|
|
* @data: Pointer to client data
|
|
*
|
|
* Based on read_write flag, it will call read
|
|
* write function.
|
|
*
|
|
* Return: 0 for success, Negative number for error condition.
|
|
*/
|
|
static int i2c_slave_xfer(struct i2c_adapter *adap, u16 addr,
|
|
unsigned short flags, char read_write,
|
|
u8 command, int protocol, union i2c_smbus_data *data)
|
|
{
|
|
struct i2c_slave *i2c_slave = i2c_get_adapdata(adap);
|
|
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
|
int ret = 0, count, i;
|
|
|
|
/* Every open/close of i2c device node, i2c framework will set
|
|
* slave address variable to 0 in i2c client structure and
|
|
* client has to run IOCTL on every open/close opration.
|
|
* So, to avoid slave address to be set to zero, added
|
|
* check for non-zero slave address.
|
|
*/
|
|
if (addr && addr != i2c_slave->slave_addr) {
|
|
i2c_slave->slave_addr = addr;
|
|
writel_relaxed(addr, i2c_slave->base + I2C_S_DEVICE_ADDR);
|
|
}
|
|
|
|
if (read_write == I2C_SMBUS_READ) {
|
|
switch (protocol) {
|
|
case I2C_SMBUS_BYTE:
|
|
case I2C_SMBUS_BYTE_DATA:
|
|
count = i2c_slave_read(i2c_slave, buf, I2C_SLAVE_BYTE_DATA);
|
|
if (count == I2C_SMBUS_BYTE) {
|
|
data->byte = buf[0];
|
|
return 0;
|
|
}
|
|
ret = count;
|
|
break;
|
|
|
|
case I2C_SMBUS_WORD_DATA:
|
|
count = i2c_slave_read(i2c_slave, buf, I2C_SLAVE_WORD_DATA);
|
|
if (count == I2C_SMBUS_BYTE_DATA) {
|
|
data->word = (buf[0] | (buf[1] << 8));
|
|
return 0;
|
|
}
|
|
ret = count;
|
|
break;
|
|
|
|
case I2C_SMBUS_BLOCK_DATA:
|
|
count = i2c_slave_read(i2c_slave, buf, I2C_SMBUS_BLOCK_MAX);
|
|
if (count > 0) {
|
|
data->block[0] = count;
|
|
for (i = 0; i < count; i++)
|
|
data->block[i + 1] = buf[i];
|
|
return 0;
|
|
}
|
|
ret = count;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} else if (read_write == I2C_SMBUS_WRITE) {
|
|
switch (protocol) {
|
|
case I2C_SMBUS_BYTE:
|
|
case I2C_SMBUS_BYTE_DATA:
|
|
buf[0] = data->byte;
|
|
ret = i2c_slave_write(i2c_slave, buf, I2C_SLAVE_BYTE_DATA);
|
|
break;
|
|
|
|
case I2C_SMBUS_WORD_DATA:
|
|
buf[0] = (uint8_t)(data->word & 0xFF);
|
|
buf[1] = (uint8_t)(data->word >> 8);
|
|
ret = i2c_slave_write(i2c_slave, buf, I2C_SLAVE_WORD_DATA);
|
|
break;
|
|
|
|
case I2C_SMBUS_BLOCK_DATA:
|
|
if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
|
|
data->block[0] = I2C_SMBUS_BLOCK_MAX;
|
|
for (i = 0; i < data->block[0]; i++)
|
|
buf[i] = data->block[i + 1];
|
|
ret = i2c_slave_write(i2c_slave, buf, data->block[0]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_enable_clk: Enable AHB & XO clock.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will enable AHB and XO clock.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
static int i2c_slave_enable_clk(struct i2c_slave *i2c_slave)
|
|
{
|
|
int ret = 0;
|
|
|
|
i2c_slave->xo_clk = devm_clk_get(i2c_slave->dev, "sm_bus_xo_clk");
|
|
if (IS_ERR(i2c_slave->xo_clk)) {
|
|
ret = PTR_ERR(i2c_slave->xo_clk);
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Err getting XO clk %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
i2c_slave->ahb_clk = devm_clk_get(i2c_slave->dev, "sm_bus_ahb_clk");
|
|
if (IS_ERR(i2c_slave->ahb_clk)) {
|
|
ret = PTR_ERR(i2c_slave->ahb_clk);
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Err getting AHB clk %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(i2c_slave->ahb_clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(i2c_slave->xo_clk);
|
|
if (ret)
|
|
goto err_on_xo;
|
|
|
|
return 0;
|
|
|
|
err_on_xo:
|
|
clk_disable_unprepare(i2c_slave->ahb_clk);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_icc_init: Enable ICB voting.
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will enable ICB voting for i2c slave.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
static int i2c_slave_icc_init(struct i2c_slave *i2c_slave)
|
|
{
|
|
int ret = 0;
|
|
|
|
i2c_slave->icc_path = devm_of_icc_get(i2c_slave->dev, "i2c-slave-config");
|
|
if (IS_ERR(i2c_slave->icc_path)) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"devm_of_icc_get failed: %d:\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
i2c_slave->bw = APPS_PROC_TO_I2C_SLAVE_VOTE;
|
|
icc_set_bw(i2c_slave->icc_path, i2c_slave->bw, i2c_slave->bw);
|
|
|
|
ret = icc_enable(i2c_slave->icc_path);
|
|
if (ret) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"ICC enable failed err: %d\n", ret);
|
|
devres_free(&i2c_slave->icc_path);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* i2c_slave_open: open function for dev node.
|
|
* @inode: Pointer to inode.
|
|
* @file: Pointer to file descriptor.
|
|
*
|
|
* This function will be called when we open the dev node
|
|
* from user-space application.
|
|
*
|
|
* Return: 0 for success.
|
|
*/
|
|
static int i2c_slave_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct cdev *cdev = inode->i_cdev;
|
|
struct i2c_slave *i2c_slave = container_of(cdev, struct i2c_slave, cdev);
|
|
|
|
file->private_data = i2c_slave;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* i2c_slave_release: close function for dev node.
|
|
* @inode: pointer to inode.
|
|
* @file: Pointer to file descriptor.
|
|
*
|
|
* This function will be called when we close the dev node
|
|
* from user-space application.
|
|
*
|
|
* Return: 0 for success.
|
|
*/
|
|
static int i2c_slave_release(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_poll: poll() syscall for I2C slave.
|
|
* @f: Pointer to the file structure.
|
|
* @wait: Pointer to Poll table.
|
|
*
|
|
* This function is used to poll on the I2C slave device.
|
|
* when userspace client do a poll() system call.
|
|
*
|
|
* Return: POLLIN if RX data available else 0.
|
|
*/
|
|
static __poll_t i2c_slave_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
struct i2c_slave *i2c_slave = file->private_data;
|
|
__poll_t mask = 0;
|
|
|
|
poll_wait(file, &i2c_slave->readq, wait);
|
|
if (i2c_slave->rx_count > 0) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"%s: RX data available\n", __func__);
|
|
mask |= POLLIN;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static const struct file_operations fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = i2c_slave_open,
|
|
.poll = i2c_slave_poll,
|
|
.release = i2c_slave_release,
|
|
};
|
|
|
|
/**
|
|
* i2c_slave_create_dev_node: Create dev node for poll
|
|
* @i2c_slave: Pointer to Main Structure.
|
|
*
|
|
* This function will create user-space dev node to support
|
|
* poll function on the I2C slave device.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
|
|
static int i2c_slave_create_dev_node(struct i2c_slave *i2c_slave)
|
|
{
|
|
struct device *dev_ret;
|
|
struct class *i2c_slave_class;
|
|
dev_t i2c_slave_dev;
|
|
int ret = 0;
|
|
|
|
ret = alloc_chrdev_region(&i2c_slave_dev, 0, 1, I2C_SLAVE_DEV);
|
|
if (ret < 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s: failed in alloc_chrdev_region ret:%d\n", ret);
|
|
goto err_alloc;
|
|
}
|
|
cdev_init(&i2c_slave->cdev, &fops);
|
|
|
|
ret = cdev_add(&i2c_slave->cdev, i2c_slave_dev, 1);
|
|
if (ret < 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s: failed in cdev_add ret:%d\n", ret);
|
|
goto err_cdev_add;
|
|
}
|
|
|
|
i2c_slave_class = class_create(THIS_MODULE, I2C_SLAVE_DEV);
|
|
if (IS_ERR_OR_NULL(i2c_slave_class)) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s: failed in class_create:%d\n", ret);
|
|
ret = PTR_ERR(i2c_slave_class);
|
|
goto err_class_create;
|
|
}
|
|
|
|
dev_ret = device_create(i2c_slave_class, NULL, i2c_slave_dev, NULL, I2C_SLAVE_DEV);
|
|
if (IS_ERR_OR_NULL(dev_ret)) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s: failed in device_create:%d\n", ret);
|
|
ret = PTR_ERR(dev_ret);
|
|
goto err_device_create;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_device_create:
|
|
class_destroy(i2c_slave_class);
|
|
err_class_create:
|
|
cdev_del(&i2c_slave->cdev);
|
|
err_cdev_add:
|
|
unregister_chrdev_region(i2c_slave_dev, 1);
|
|
err_alloc:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_func: To check supported i2c functionality.
|
|
* @adap: I2C driver adapter.
|
|
*
|
|
* This function will use to determine what the adapter supports.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
static u32 i2c_slave_func(struct i2c_adapter *adap)
|
|
{
|
|
return I2C_FUNC_SMBUS_BYTE;
|
|
}
|
|
|
|
static const struct i2c_algorithm i2c_slave_algo = {
|
|
.smbus_xfer = i2c_slave_xfer,
|
|
.functionality = i2c_slave_func,
|
|
};
|
|
|
|
/**
|
|
* i2c_slave_probe: Driver Probe function.
|
|
* @pdev: Pointer to platform device structure.
|
|
*
|
|
* This function will performs pre-initialization tasks such as reading dtsi property,
|
|
* setting clock, IRQ request, initializing registers, allocating memory,
|
|
* and initializing registers for i2c slave.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
static int i2c_slave_probe(struct platform_device *pdev)
|
|
{
|
|
struct i2c_slave *i2c_slave;
|
|
struct resource *res;
|
|
static const char adapName[10] = "I2C-slave";
|
|
char ipc_name[I2C_NAME_SIZE];
|
|
int ret = 0;
|
|
|
|
i2c_slave = devm_kzalloc(&pdev->dev,
|
|
sizeof(*i2c_slave), GFP_KERNEL);
|
|
if (!i2c_slave) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
i2c_slave->dev = &pdev->dev;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
i2c_slave->base = devm_ioremap_resource(i2c_slave->dev, res);
|
|
if (IS_ERR(i2c_slave->base)) {
|
|
ret = PTR_ERR(i2c_slave->base);
|
|
goto err;
|
|
}
|
|
|
|
scnprintf(ipc_name, I2C_NAME_SIZE, "%s", dev_name(i2c_slave->dev));
|
|
i2c_slave->ipcl = ipc_log_context_create(2, ipc_name, 0);
|
|
if (!i2c_slave->ipcl) {
|
|
dev_err(i2c_slave->dev, "Error: Failed to create IPC log file\n");
|
|
ret = -EINVAL;
|
|
goto err_ipc;
|
|
}
|
|
|
|
ret = i2c_slave_create_dev_node(i2c_slave);
|
|
if (ret < 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"failed to create dev node ret: %d:\n", ret);
|
|
goto err_ipc;
|
|
}
|
|
|
|
ret = i2c_slave_enable_clk(i2c_slave);
|
|
if (ret)
|
|
goto err_ipc;
|
|
|
|
i2c_slave->irq = platform_get_irq(pdev, 0);
|
|
if (i2c_slave->irq < 0) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"get_irq failed: %d:\n", i2c_slave->irq);
|
|
ret = i2c_slave->irq;
|
|
goto err_irq;
|
|
}
|
|
|
|
ret = devm_request_irq(i2c_slave->dev, i2c_slave->irq, i2c_slave_irq, 0,
|
|
"i2c_slave", i2c_slave);
|
|
if (ret) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Request_irq failed: %d: err: %d\n", i2c_slave->irq, ret);
|
|
goto err_irq;
|
|
}
|
|
|
|
ret = i2c_slave_icc_init(i2c_slave);
|
|
if (ret) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"ICC init failed ret: %d\n", ret);
|
|
goto err_icc;
|
|
}
|
|
|
|
i2c_slave->adap.algo = &i2c_slave_algo;
|
|
i2c_slave->rx_count = 0;
|
|
|
|
/* Enable IRQ */
|
|
i2c_slave_enable_irq(i2c_slave);
|
|
|
|
/* Set default slave address */
|
|
writel_relaxed(SLAVE_ADDR, i2c_slave->base + I2C_S_DEVICE_ADDR);
|
|
i2c_slave->slave_addr = SLAVE_ADDR;
|
|
|
|
/* Enable core */
|
|
writel_relaxed(CORE_EN, i2c_slave->base + I2C_S_CONFIG);
|
|
|
|
ret = strscpy(i2c_slave->adap.name, adapName, sizeof(i2c_slave->adap.name));
|
|
if (ret != strlen(adapName)) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Failed to set adapter name\n");
|
|
goto err_adap;
|
|
}
|
|
|
|
init_waitqueue_head(&i2c_slave->readq);
|
|
i2c_set_adapdata(&i2c_slave->adap, i2c_slave);
|
|
platform_set_drvdata(pdev, i2c_slave);
|
|
i2c_slave->adap.dev.parent = i2c_slave->dev;
|
|
i2c_slave->adap.dev.of_node = pdev->dev.of_node;
|
|
ret = i2c_add_adapter(&i2c_slave->adap);
|
|
if (ret) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"Add adapter failed, ret=%d\n", ret);
|
|
goto err_adap;
|
|
}
|
|
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"I2C Slave probed\n");
|
|
return 0;
|
|
|
|
err_adap:
|
|
icc_disable(i2c_slave->icc_path);
|
|
devres_free(&i2c_slave->icc_path);
|
|
err_icc:
|
|
disable_irq(i2c_slave->irq);
|
|
err_irq:
|
|
clk_disable_unprepare(i2c_slave->ahb_clk);
|
|
clk_disable_unprepare(i2c_slave->xo_clk);
|
|
err_ipc:
|
|
if (i2c_slave->ipcl)
|
|
ipc_log_context_destroy(i2c_slave->ipcl);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_remove: This function will release the resources.
|
|
* @pdev: Pointer to platform device structure.
|
|
*
|
|
* This function will release clocks, IRQ and other allocated resources for
|
|
* i2c slave.
|
|
*
|
|
* Return: 0 for success.
|
|
*/
|
|
static int i2c_slave_remove(struct platform_device *pdev)
|
|
{
|
|
struct i2c_slave *i2c_slave = platform_get_drvdata(pdev);
|
|
|
|
clk_disable_unprepare(i2c_slave->ahb_clk);
|
|
clk_disable_unprepare(i2c_slave->xo_clk);
|
|
disable_irq(i2c_slave->irq);
|
|
icc_disable(i2c_slave->icc_path);
|
|
devres_free(&i2c_slave->icc_path);
|
|
i2c_del_adapter(&i2c_slave->adap);
|
|
|
|
if (i2c_slave->ipcl)
|
|
ipc_log_context_destroy(i2c_slave->ipcl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_suspend: driver suspend function.
|
|
* @dev: Pointer to device structure.
|
|
*
|
|
* This function will put driver into suspend state by releasing
|
|
* icc, irq and core clock.
|
|
*
|
|
* Return: 0 for success.
|
|
*/
|
|
#ifdef I2C_SLAVE_SUSPEND_RESUME
|
|
static int i2c_slave_suspend(struct device *dev)
|
|
{
|
|
struct i2c_slave *i2c_slave = dev_get_drvdata(dev);
|
|
|
|
disable_irq(i2c_slave->irq);
|
|
icc_disable(i2c_slave->icc_path);
|
|
clk_disable_unprepare(i2c_slave->ahb_clk);
|
|
clk_disable_unprepare(i2c_slave->xo_clk);
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, false, i2c_slave->dev,
|
|
"%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i2c_slave_resume: driver resume function.
|
|
* @dev: Pointer to device structure.
|
|
*
|
|
* This function will resume the driver by enabling icc, irq
|
|
* and core clock.
|
|
*
|
|
* Return: 0 for success, negative number for error condition.
|
|
*/
|
|
static int i2c_slave_resume(struct device *dev)
|
|
{
|
|
struct i2c_slave *i2c_slave = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
enable_irq(i2c_slave->irq);
|
|
|
|
ret = icc_enable(i2c_slave->icc_path);
|
|
if (ret) {
|
|
I2C_SLAVE_ERR(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"ICC enable failed err: %d\n", ret);
|
|
goto err_icc;
|
|
}
|
|
|
|
ret = clk_prepare_enable(i2c_slave->ahb_clk);
|
|
if (ret) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s: failing at ahb clk prepare enable ret=%d\n",
|
|
__func__, ret);
|
|
goto err_ahb_clk;
|
|
}
|
|
|
|
ret = clk_prepare_enable(i2c_slave->xo_clk);
|
|
if (ret) {
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s: failing at xo clk prepare enable ret=%d\n",
|
|
__func__, ret);
|
|
clk_disable_unprepare(i2c_slave->ahb_clk);
|
|
goto err_xo_clk;
|
|
}
|
|
|
|
I2C_SLAVE_DBG(i2c_slave->ipcl, true, i2c_slave->dev,
|
|
"%s:\n", __func__);
|
|
return 0;
|
|
|
|
err_xo_clk:
|
|
clk_disable_unprepare(i2c_slave->ahb_clk);
|
|
err_ahb_clk:
|
|
icc_disable(i2c_slave->icc_path);
|
|
err_icc:
|
|
return ret;
|
|
}
|
|
#else
|
|
static int i2c_slave_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_slave_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops i2c_slave_pm_ops = {
|
|
.suspend = i2c_slave_suspend,
|
|
.resume = i2c_slave_resume,
|
|
};
|
|
|
|
static const struct of_device_id i2c_slave_dt_match[] = {
|
|
{.compatible = "qcom,i2c-slave" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, i2c_slave_dt_match);
|
|
|
|
static struct platform_driver i2c_slave_driver = {
|
|
.driver = {
|
|
.name = "i2c_slave",
|
|
.pm = &i2c_slave_pm_ops,
|
|
.of_match_table = i2c_slave_dt_match,
|
|
},
|
|
.probe = i2c_slave_probe,
|
|
.remove = i2c_slave_remove,
|
|
};
|
|
|
|
module_platform_driver(i2c_slave_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("i2c slave");
|