/*
* Driver for I2C adapter in Rockchip RK3xxx SoC
*
* Max Schwarz <max.schwarz@online.de>
* based on the patches by Rockchip Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/clk.h>
#include <linux/wait.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/math64.h>
/* Register Map */
#define REG_CON 0x00 /* control register */
#define REG_CLKDIV 0x04 /* clock divisor register */
#define REG_MRXADDR 0x08 /* slave address for REGISTER_TX */
#define REG_MRXRADDR 0x0c /* slave register address for REGISTER_TX */
#define REG_MTXCNT 0x10 /* number of bytes to be transmitted */
#define REG_MRXCNT 0x14 /* number of bytes to be received */
#define REG_IEN 0x18 /* interrupt enable */
#define REG_IPD 0x1c /* interrupt pending */
#define REG_FCNT 0x20 /* finished count */
/* Data buffer offsets */
#define TXBUFFER_BASE 0x100
#define RXBUFFER_BASE 0x200
/* REG_CON bits */
#define REG_CON_EN BIT(0)
enum {
REG_CON_MOD_TX = 0, /* transmit data */
REG_CON_MOD_REGISTER_TX, /* select register and restart */
REG_CON_MOD_RX, /* receive data */
REG_CON_MOD_REGISTER_RX, /* broken: transmits read addr AND writes
* register addr */
};
#define REG_CON_MOD(mod) ((mod) << 1)
#define REG_CON_MOD_MASK (BIT(1) | BIT(2))
#define REG_CON_START BIT(3)
#define REG_CON_STOP BIT(4)
#define REG_CON_LASTACK BIT(5) /* 1: send NACK after last received byte */
#define REG_CON_ACTACK BIT(6) /* 1: stop if NACK is received */
/* REG_MRXADDR bits */
#define REG_MRXADDR_VALID(x) BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR valid */
/* REG_IEN/REG_IPD bits */
#define REG_INT_BTF BIT(0) /* a byte was transmitted */
#define REG_INT_BRF BIT(1) /* a byte was received */
#define REG_INT_MBTF BIT(2) /* master data transmit finished */
#define REG_INT_MBRF BIT(3) /* master data receive finished */
#define REG_INT_START BIT(4) /* START condition generated */
#define REG_INT_STOP BIT(5) /* STOP condition generated */
#define REG_INT_NAKRCV BIT(6) /* NACK received */
#define REG_INT_ALL 0x7f
/* Constants */
#define WAIT_TIMEOUT 1000 /* ms */
#define DEFAULT_SCL_RATE (100 * 1000) /* Hz */
enum rk3x_i2c_state {
STATE_IDLE,
STATE_START,
STATE_READ,
STATE_WRITE,
STATE_STOP
};
/**
* @grf_offset: offset inside the grf regmap for setting the i2c type
*/
struct rk3x_i2c_soc_data {
int grf_offset;
};
struct rk3x_i2c {
struct i2c_adapter adap;
struct device *dev;
struct rk3x_i2c_soc_data *soc_data;
/* Hardware resources */
void __iomem *regs;
struct clk *clk;
struct notifier_block clk_rate_nb;
/* Settings */
unsigned int scl_frequency;
unsigned int scl_rise_ns;
unsigned int scl_fall_ns;
unsigned int sda_fall_ns;
/* Synchronization & notification */
spinlock_t lock;
wait_queue_head_t wait;
bool busy;
/* Current message */
struct i2c_msg *msg;
u8 addr;
unsigned int mode;
bool is_last_msg;
/* I2C state machine */
enum rk3x_i2c_state state;
unsigned int processed; /* sent/received bytes */
int error;
};
static inline void i2c_writel(struct rk3x_i2c *i2c, u32 value,
unsigned int offset)
{
writel(value, i2c->regs + offset);
}
static inline u32 i2c_readl(struct rk3x_i2c *i2c, unsigned int offset)
{
return readl(i2c->regs + offset);
}
/* Reset all interrupt pending bits */
static inline void rk3x_i2c_clean_ipd(struct rk3x_i2c *i2c)
{
i2c_writel(i2c, REG_INT_ALL, REG_IPD);
}
/**
* Generate a START condition, which triggers a REG_INT_START interrupt.
*/
static void rk3x_i2c_start(struct rk3x_i2c *i2c)
{
u32 val;
rk3x_i2c_clean_ipd(i2c);
i2c_writel(i2c, REG_INT_START, REG_IEN);
/* enable adapter with correct mode, send START condition */
val = REG_CON_EN | REG_CON_MOD(i2c->mode) | REG_CON_START;
/* if we want to react to NACK, set ACTACK bit */
if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
val |= REG_CON_ACTACK;
i2c_writel(i2c, val, REG_CON);
}
/**
* Generate a STOP condition, which triggers a REG_INT_STOP interrupt.
*
* @error: Error code to return in rk3x_i2c_xfer
*/
static void rk3x_i2c_stop(struct rk3x_i2c *i2c, int error)
{
unsigned int ctrl;
i2c->processed = 0;
i2c->msg = NULL;
i2c->error = error;
if (i2c->is_last_msg) {
/* Enable stop interrupt */
i2c_writel(i2c, REG_INT_STOP, REG_IEN);
i2c->state = STATE_STOP;
ctrl = i2c_readl(i2c, REG_CON);
ctrl |= REG_CON_STOP;
i2c_writel(i2c, ctrl, REG_CON);
} else {
/* Signal rk3x_i2c_xfer to start the next message. */
i2c->busy = false;
i2c->state = STATE_IDLE;
/*
* The HW is actually not capable of REPEATED START. But we can
* get the intended effect by resetting its internal state
* and issuing an ordinary START.
<