2533 lines
67 KiB
C
Executable File
2533 lines
67 KiB
C
Executable File
/*
|
||
* drivers/i2c/busses/i2c-sunxi.c
|
||
*
|
||
* Copyright (C) 2013 Allwinner.
|
||
* Pan Nan <pannan@reuuimllatech.com>
|
||
*
|
||
* SUNXI TWI Controller Driver
|
||
*
|
||
* 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.
|
||
*
|
||
* 2013.5.3 Mintow <duanmintao@allwinnertech.com>
|
||
* Adapt to all the new chip of Allwinner. Support sun8i/sun9i
|
||
*/
|
||
|
||
#include <linux/kernel.h>
|
||
#include <linux/module.h>
|
||
#include <linux/i2c.h>
|
||
#include <linux/sched.h>
|
||
#include <linux/delay.h>
|
||
#include <linux/interrupt.h>
|
||
#include <linux/platform_device.h>
|
||
#include <linux/err.h>
|
||
#include <linux/clk.h>
|
||
#include <linux/slab.h>
|
||
#include <linux/io.h>
|
||
#include <linux/gpio.h>
|
||
#include <linux/pinctrl/consumer.h>
|
||
#include <linux/clk/sunxi.h>
|
||
#include <linux/pm_runtime.h>
|
||
#include <linux/dma-mapping.h>
|
||
#include <linux/dmaengine.h>
|
||
#include <linux/dmapool.h>
|
||
#include <asm/uaccess.h>
|
||
#include <linux/time.h>
|
||
|
||
#include <linux/of.h>
|
||
#include <linux/of_irq.h>
|
||
#include <linux/of_address.h>
|
||
|
||
#include "i2c-sunxi.h"
|
||
|
||
/* For debug */
|
||
#define I2C_EXIT() pr_debug("%s()%d - %s\n", __func__, __LINE__, "Exit")
|
||
#define I2C_ENTER() pr_debug("%s()%d - %s\n", __func__, __LINE__, "Enter ...")
|
||
#define I2C_ERR(fmt, arg...) pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg)
|
||
|
||
#ifndef CONFIG_SUNXI_I2C_PRINT_TRANSFER_INFO
|
||
static int bus_transfer_dbg = -1;
|
||
module_param_named(transfer_debug, bus_transfer_dbg,
|
||
int, S_IRUGO | S_IWUSR | S_IWGRP);
|
||
#endif
|
||
|
||
#define SUNXI_I2C_OK 0
|
||
#define SUNXI_I2C_FAIL -1
|
||
#define SUNXI_I2C_RETRY -2
|
||
#define SUNXI_I2C_SFAIL -3 /* start fail */
|
||
#define SUNXI_I2C_TFAIL -4 /* stop fail */
|
||
|
||
#define DMA_THRESHOLD 32
|
||
#define MAX_FIFO 32
|
||
#define DMA_TIMEOUT 2000
|
||
|
||
static u32 debug_mask;
|
||
#define dprintk(level_mask, fmt, arg...) \
|
||
do { \
|
||
if (unlikely(debug_mask & level_mask)) \
|
||
pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg); \
|
||
} while (0)
|
||
|
||
static int twi_used_mask;
|
||
|
||
/* I2C transfer status */
|
||
enum {
|
||
I2C_XFER_IDLE = 0x1,
|
||
I2C_XFER_START = 0x2,
|
||
I2C_XFER_RUNNING = 0x4,
|
||
};
|
||
|
||
struct sunxi_i2c_dma {
|
||
struct dma_chan *chan;
|
||
dma_addr_t dma_buf;
|
||
unsigned int dma_len;
|
||
enum dma_transfer_direction dma_transfer_dir;
|
||
enum dma_data_direction dma_data_dir;
|
||
};
|
||
|
||
struct sunxi_i2c {
|
||
struct platform_device *pdev;
|
||
int bus_num;
|
||
unsigned int status; /* start, running, idle */
|
||
struct i2c_adapter adap;
|
||
struct device *dev;
|
||
|
||
spinlock_t lock; /* syn */
|
||
wait_queue_head_t wait;
|
||
struct completion cmd_complete;
|
||
|
||
struct i2c_msg *msg;
|
||
unsigned int msg_num;
|
||
unsigned int msg_idx;
|
||
unsigned int msg_ptr;
|
||
|
||
struct clk *mclk;
|
||
|
||
unsigned int bus_freq;
|
||
int irq;
|
||
unsigned int debug_state; /* log the twi machine state */
|
||
|
||
void __iomem *base_addr;
|
||
|
||
struct pinctrl *pctrl;
|
||
bool twi_drv_used;
|
||
|
||
unsigned char result;
|
||
struct sunxi_i2c_dma *dma_tx;
|
||
struct sunxi_i2c_dma *dma_rx;
|
||
struct sunxi_i2c_dma *dma_using;
|
||
|
||
};
|
||
|
||
static void dump_reg(void __iomem *base_addr, u32 offset, u32 len)
|
||
{
|
||
u32 i, j;
|
||
|
||
for (i = 0; i < len; i += 0x10) {
|
||
pr_warn("0x%04x: ", offset + i);
|
||
for (j = 0; j < 0x10; j += 0x04)
|
||
pr_warn("0x%08x ", readl(base_addr + offset + i + j));
|
||
pr_warn("\n");
|
||
}
|
||
}
|
||
|
||
/* clear the interrupt flag */
|
||
static inline void twi_clear_irq_flag(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
/* start and stop bit should be 0 */
|
||
reg_val |= TWI_CTL_INTFLG;
|
||
reg_val &= ~(TWI_CTL_STA | TWI_CTL_STP);
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
/* read two more times to make sure that */
|
||
/* interrupt flag does really be cleared */
|
||
{
|
||
unsigned int temp;
|
||
|
||
temp = readl(base_addr + TWI_CTL_REG);
|
||
temp |= readl(base_addr + TWI_CTL_REG);
|
||
}
|
||
}
|
||
|
||
/* get data first, then clear flag */
|
||
static inline void
|
||
twi_get_byte(void __iomem *base_addr, unsigned char *buffer)
|
||
{
|
||
*buffer = (unsigned char)(TWI_DATA_MASK &
|
||
readl(base_addr + TWI_DATA_REG));
|
||
twi_clear_irq_flag(base_addr);
|
||
}
|
||
|
||
/* only get data, we will clear the flag when stop */
|
||
static inline void
|
||
twi_get_last_byte(void __iomem *base_addr, unsigned char *buffer)
|
||
{
|
||
*buffer = (unsigned char)(TWI_DATA_MASK &
|
||
readl(base_addr + TWI_DATA_REG));
|
||
}
|
||
|
||
/* write data and clear irq flag to trigger send flow */
|
||
static inline void
|
||
twi_put_byte(void __iomem *base_addr, const unsigned char *buffer)
|
||
{
|
||
writel((unsigned int)*buffer, base_addr + TWI_DATA_REG);
|
||
twi_clear_irq_flag(base_addr);
|
||
}
|
||
|
||
static inline void twi_enable_irq(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
/*
|
||
* 1 when enable irq for next operation, set intflag to 0 to prevent
|
||
* to clear it by a mistake (intflag bit is write-1-to-clear bit)
|
||
* 2 Similarly, mask START bit and STOP bit to prevent to set it
|
||
* twice by a mistake (START bit and STOP bit are self-clear-to-0 bits)
|
||
*/
|
||
reg_val |= TWI_CTL_INTEN;
|
||
reg_val &= ~(TWI_CTL_STA | TWI_CTL_STP | TWI_CTL_INTFLG);
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
}
|
||
|
||
static inline void twi_disable_irq(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val &= ~TWI_CTL_INTEN;
|
||
reg_val &= ~(TWI_CTL_STA | TWI_CTL_STP | TWI_CTL_INTFLG);
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
}
|
||
|
||
static inline void
|
||
twi_disable(void __iomem *base_addr, unsigned int reg, unsigned int mask)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + reg);
|
||
|
||
reg_val &= ~mask;
|
||
writel(reg_val, base_addr + reg);
|
||
dprintk(DEBUG_INFO, "[i2c0] reg: 0x%x value: 0x%x\n", reg,
|
||
readl(base_addr + reg));
|
||
}
|
||
|
||
static inline void
|
||
twi_enable(void __iomem *base_addr, unsigned int reg, unsigned int mask)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + reg);
|
||
|
||
reg_val |= mask;
|
||
writel(reg_val, base_addr + reg);
|
||
dprintk(DEBUG_INFO, "[i2c0] reg: 0x%x value: 0x%x\n", reg,
|
||
readl(base_addr + reg));
|
||
}
|
||
|
||
|
||
/* trigger start signal, the start bit will be cleared automatically */
|
||
static inline void twi_set_start(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val |= TWI_CTL_STA;
|
||
reg_val &= ~TWI_CTL_INTFLG;
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
}
|
||
|
||
/* get start bit status, poll if start signal is sent */
|
||
static inline unsigned int twi_get_start(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val >>= 5;
|
||
return reg_val & 1;
|
||
}
|
||
|
||
/* trigger stop signal, the stop bit will be cleared automatically */
|
||
static inline void twi_set_stop(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val |= TWI_CTL_STP;
|
||
reg_val &= ~TWI_CTL_INTFLG;
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
}
|
||
|
||
/* get stop bit status, poll if stop signal is sent */
|
||
static inline unsigned int twi_get_stop(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val >>= 4;
|
||
return reg_val & 1;
|
||
}
|
||
|
||
static inline void twi_disable_ack(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val &= ~TWI_CTL_ACK;
|
||
reg_val &= ~TWI_CTL_INTFLG;
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
}
|
||
|
||
/* when sending ack or nack, it will send ack automatically */
|
||
static inline void twi_enable_ack(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
reg_val |= TWI_CTL_ACK;
|
||
reg_val &= ~TWI_CTL_INTFLG;
|
||
writel(reg_val, base_addr + TWI_CTL_REG);
|
||
}
|
||
|
||
/* get the interrupt flag */
|
||
static inline unsigned int twi_query_irq_flag(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_CTL_REG);
|
||
|
||
return (reg_val & TWI_CTL_INTFLG);/* 0x 0000_1000 */
|
||
}
|
||
|
||
/* get interrupt status */
|
||
static inline unsigned int twi_query_irq_status(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_STAT_REG);
|
||
|
||
return (reg_val & TWI_STAT_MASK);
|
||
}
|
||
|
||
/* set twi clock
|
||
*
|
||
* clk_n: clock divider factor n
|
||
* clk_m: clock divider factor m
|
||
*/
|
||
static void twi_clk_write_reg(struct sunxi_i2c *i2c, unsigned int reg_clk,
|
||
unsigned int clk_m, unsigned int clk_n,
|
||
unsigned int mask_clk_m, unsigned int mask_clk_n)
|
||
{
|
||
unsigned int reg_val = readl(i2c->base_addr + reg_clk);
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] reg_clk = 0x%x, clk_m = %u, clk_n = %u,"
|
||
"mask_clk_m = %u, mask_clk_n = %u\n", i2c->bus_num,
|
||
reg_clk, clk_m, clk_n, mask_clk_m, mask_clk_n);
|
||
if (reg_clk == TWI_DRIVER_BUSC) {
|
||
reg_val &= ~(mask_clk_m | mask_clk_n);
|
||
reg_val |= ((clk_m | (clk_n << 3)) << 8);
|
||
writel(reg_val, i2c->base_addr + reg_clk);
|
||
dprintk(DEBUG_INFO, "[i2c%d] reg: 0x%x value: 0x%x\n",
|
||
i2c->bus_num, reg_clk,
|
||
readl(i2c->base_addr + reg_clk));
|
||
} else {
|
||
reg_val &= ~(mask_clk_m | mask_clk_n);
|
||
reg_val |= ((clk_m << 3) | clk_n);
|
||
writel(reg_val, i2c->base_addr + reg_clk);
|
||
dprintk(DEBUG_INFO, "[i2c%d] reg: 0x%x value: 0x%x\n",
|
||
i2c->bus_num, reg_clk,
|
||
readl(i2c->base_addr + reg_clk));
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Fin is APB CLOCK INPUT;
|
||
* Fsample = F0 = Fin/2^CLK_N;
|
||
* F1 = F0/(CLK_M+1);
|
||
* Foscl = F1/10 = Fin/(2^CLK_N * (CLK_M+1)*10);
|
||
* Foscl is clock SCL;100KHz or 400KHz
|
||
*
|
||
* clk_in: apb clk clock
|
||
* sclk_req: freqence to set in HZ
|
||
*/
|
||
static int twi_set_clock(struct sunxi_i2c *i2c, unsigned int reg_clk,
|
||
unsigned int clk_in, unsigned int sclk_req,
|
||
unsigned int mask_clk_m, unsigned int mask_clk_n)
|
||
{
|
||
unsigned int clk_m = 0;
|
||
unsigned int clk_n = 0;
|
||
unsigned int _2_pow_clk_n = 1;
|
||
unsigned int src_clk = clk_in/10;
|
||
unsigned int divider = src_clk/sclk_req; /* 400khz or 100khz */
|
||
unsigned int sclk_real = 0; /* the real clock frequency */
|
||
|
||
if (divider == 0) {
|
||
clk_m = 1;
|
||
goto set_clk;
|
||
}
|
||
|
||
/*
|
||
* search clk_n and clk_m,from large to small value so
|
||
* that can quickly find suitable m & n.
|
||
*/
|
||
while (clk_n < 8) { /* 3bits max value is 8 */
|
||
/* (m+1)*2^n = divider -->m = divider/2^n -1 */
|
||
clk_m = (divider/_2_pow_clk_n) - 1;
|
||
/* clk_m = (divider >> (_2_pow_clk_n>>1))-1 */
|
||
while (clk_m < 16) { /* 4bits max value is 16 */
|
||
/* src_clk/((m+1)*2^n) */
|
||
sclk_real = src_clk/(clk_m + 1)/_2_pow_clk_n;
|
||
if (sclk_real <= sclk_req)
|
||
goto set_clk;
|
||
else
|
||
clk_m++;
|
||
}
|
||
clk_n++;
|
||
_2_pow_clk_n *= 2; /* mutilple by 2 */
|
||
}
|
||
|
||
set_clk:
|
||
twi_clk_write_reg(i2c, reg_clk, clk_m, clk_n, mask_clk_m, mask_clk_n);
|
||
return 0;
|
||
}
|
||
|
||
/* soft reset twi */
|
||
static inline void
|
||
twi_soft_reset(void __iomem *base_addr, unsigned int reg, unsigned int mask)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + reg);
|
||
|
||
reg_val |= mask;
|
||
writel(reg_val, base_addr + reg);
|
||
}
|
||
|
||
/* Enhanced Feature Register */
|
||
static inline void twi_set_efr(void __iomem *base_addr, unsigned int efr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_EFR_REG);
|
||
|
||
reg_val &= ~TWI_EFR_MASK;
|
||
efr &= TWI_EFR_MASK;
|
||
reg_val |= efr;
|
||
writel(reg_val, base_addr + TWI_EFR_REG);
|
||
}
|
||
|
||
static int sunxi_i2c_xfer_complete(struct sunxi_i2c *i2c, int code);
|
||
static int
|
||
sunxi_i2c_do_xfer(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num);
|
||
|
||
static int twi_chan_is_enable(int _ch)
|
||
{
|
||
return twi_used_mask & SUNXI_TWI_CHAN_MASK(_ch);
|
||
}
|
||
|
||
static int twi_select_gpio_state(struct pinctrl *pctrl, char *name, u32 no)
|
||
{
|
||
int ret = 0;
|
||
struct pinctrl_state *pctrl_state = NULL;
|
||
|
||
pctrl_state = pinctrl_lookup_state(pctrl, name);
|
||
if (IS_ERR(pctrl_state)) {
|
||
I2C_ERR("TWI%d pinctrl_lookup_state(%s) failed! return %p\n",
|
||
no, name, pctrl_state);
|
||
return -1;
|
||
}
|
||
|
||
ret = pinctrl_select_state(pctrl, pctrl_state);
|
||
if (ret < 0)
|
||
I2C_ERR("TWI%d pinctrl_select_state(%s) failed! return %d\n",
|
||
no, name, ret);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int twi_request_gpio(struct sunxi_i2c *i2c)
|
||
{
|
||
dprintk(DEBUG_INFO, "Pinctrl init %d ... [%s]\n",
|
||
i2c->bus_num, i2c->adap.dev.parent->init_name);
|
||
|
||
if (!twi_chan_is_enable(i2c->bus_num))
|
||
return -1;
|
||
|
||
i2c->pctrl = devm_pinctrl_get(i2c->adap.dev.parent);
|
||
if (IS_ERR(i2c->pctrl)) {
|
||
I2C_ERR("TWI%d devm_pinctrl_get() failed! return %ld\n",
|
||
i2c->bus_num, PTR_ERR(i2c->pctrl));
|
||
return -1;
|
||
}
|
||
|
||
return twi_select_gpio_state(i2c->pctrl, PINCTRL_STATE_DEFAULT,
|
||
i2c->bus_num);
|
||
}
|
||
|
||
static void twi_release_gpio(struct sunxi_i2c *i2c)
|
||
{
|
||
devm_pinctrl_put(i2c->pctrl);
|
||
}
|
||
|
||
/* function */
|
||
static int twi_start(void __iomem *base_addr, int bus_num)
|
||
{
|
||
unsigned int timeout = 0xff;
|
||
|
||
twi_set_start(base_addr);
|
||
while ((twi_get_start(base_addr) == 1) && (--timeout))
|
||
;
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] START can't sendout!\n", bus_num);
|
||
return SUNXI_I2C_FAIL;
|
||
}
|
||
|
||
return SUNXI_I2C_OK;
|
||
}
|
||
|
||
static int twi_restart(void __iomem *base_addr, int bus_num)
|
||
{
|
||
unsigned int timeout = 0xff;
|
||
|
||
twi_set_start(base_addr);
|
||
twi_clear_irq_flag(base_addr);
|
||
while ((twi_get_start(base_addr) == 1) && (--timeout))
|
||
;
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] Restart can't sendout!\n", bus_num);
|
||
return SUNXI_I2C_FAIL;
|
||
}
|
||
|
||
return SUNXI_I2C_OK;
|
||
}
|
||
|
||
static int twi_stop(void __iomem *base_addr, int bus_num)
|
||
{
|
||
unsigned int timeout = 0xff;
|
||
|
||
twi_set_stop(base_addr);
|
||
twi_clear_irq_flag(base_addr);
|
||
|
||
twi_get_stop(base_addr);/* it must delay 1 nop to check stop bit */
|
||
while ((twi_get_stop(base_addr) == 1) && (--timeout))
|
||
;
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] STOP can't sendout!\n", bus_num);
|
||
return SUNXI_I2C_TFAIL;
|
||
}
|
||
|
||
timeout = 0xff;
|
||
while ((readl(base_addr + TWI_STAT_REG) != TWI_STAT_IDLE)
|
||
&& (--timeout))
|
||
;
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] i2c state(0x%0x) isn't idle(0xf8)\n",
|
||
bus_num, readl(base_addr + TWI_STAT_REG));
|
||
return SUNXI_I2C_TFAIL;
|
||
}
|
||
|
||
timeout = 0xff;
|
||
while ((readl(base_addr + TWI_LCR_REG) != TWI_LCR_IDLE_STATUS
|
||
&& readl(base_addr + TWI_LCR_REG) != TWI_LCR_NORM_STATUS)
|
||
&& (--timeout))
|
||
;
|
||
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] i2c lcr(0x%0x) isn't idle(0x3a)\n",
|
||
bus_num, readl(base_addr + TWI_LCR_REG));
|
||
return SUNXI_I2C_TFAIL;
|
||
}
|
||
|
||
return SUNXI_I2C_OK;
|
||
}
|
||
|
||
/* get SDA state */
|
||
static unsigned int twi_get_sda(void __iomem *base_addr)
|
||
{
|
||
unsigned int status = 0;
|
||
|
||
status = TWI_LCR_SDA_STATE_MASK & readl(base_addr + TWI_LCR_REG);
|
||
status >>= 4;
|
||
return (status&0x1);
|
||
}
|
||
|
||
/* set SCL level(high/low), only when SCL enable */
|
||
static void twi_set_scl(void __iomem *base_addr, unsigned int hi_lo)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_LCR_REG);
|
||
|
||
reg_val &= ~TWI_LCR_SCL_CTL;
|
||
hi_lo &= 0x01;
|
||
reg_val |= (hi_lo<<3);
|
||
writel(reg_val, base_addr + TWI_LCR_REG);
|
||
}
|
||
|
||
/* enable SDA or SCL */
|
||
static void twi_enable_lcr(void __iomem *base_addr, unsigned int sda_scl)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_LCR_REG);
|
||
|
||
sda_scl &= 0x01;
|
||
if (sda_scl)
|
||
reg_val |= TWI_LCR_SCL_EN;/* enable scl line control */
|
||
else
|
||
reg_val |= TWI_LCR_SDA_EN;/* enable sda line control */
|
||
|
||
writel(reg_val, base_addr + TWI_LCR_REG);
|
||
}
|
||
|
||
/* disable SDA or SCL */
|
||
static void twi_disable_lcr(void __iomem *base_addr, unsigned int sda_scl)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_LCR_REG);
|
||
|
||
sda_scl &= 0x01;
|
||
if (sda_scl)
|
||
reg_val &= ~TWI_LCR_SCL_EN;/* disable scl line control */
|
||
else
|
||
reg_val &= ~TWI_LCR_SDA_EN;/* disable sda line control */
|
||
|
||
writel(reg_val, base_addr + TWI_LCR_REG);
|
||
}
|
||
|
||
/* send 9 clock to release sda */
|
||
static int twi_send_clk_9pulse(void __iomem *base_addr, int bus_num)
|
||
{
|
||
int twi_scl = 1;
|
||
int low = 0;
|
||
int high = 1;
|
||
int cycle = 0;
|
||
unsigned char status;
|
||
|
||
/* enable scl control */
|
||
twi_enable_lcr(base_addr, twi_scl);
|
||
|
||
while (cycle < 9) {
|
||
if (twi_get_sda(base_addr)
|
||
&& twi_get_sda(base_addr)
|
||
&& twi_get_sda(base_addr)) {
|
||
break;
|
||
}
|
||
/* twi_scl -> low */
|
||
twi_set_scl(base_addr, low);
|
||
udelay(1000);
|
||
|
||
/* twi_scl -> high */
|
||
twi_set_scl(base_addr, high);
|
||
udelay(1000);
|
||
cycle++;
|
||
}
|
||
|
||
if (twi_get_sda(base_addr)) {
|
||
twi_disable_lcr(base_addr, twi_scl);
|
||
status = SUNXI_I2C_OK;
|
||
} else {
|
||
I2C_ERR("[i2c%d] SDA is still Stuck Low, failed.\n", bus_num);
|
||
twi_disable_lcr(base_addr, twi_scl);
|
||
status = SUNXI_I2C_FAIL;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
static int twi_regulator_request(struct sunxi_i2c_platform_data *pdata)
|
||
{
|
||
struct regulator *regu = NULL;
|
||
|
||
/* Consider "n*" as nocare. Support "none", "nocare", "null", "" etc. */
|
||
if ((pdata->regulator_id[0] == 'n') || (pdata->regulator_id[0] == 0))
|
||
return 0;
|
||
|
||
regu = regulator_get(NULL, pdata->regulator_id);
|
||
if (IS_ERR(regu)) {
|
||
I2C_ERR("[i2c%d] get regulator %s failed!\n",
|
||
pdata->bus_num, pdata->regulator_id);
|
||
return -1;
|
||
}
|
||
pdata->regulator = regu;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int twi_regulator_release(struct sunxi_i2c_platform_data *pdata)
|
||
{
|
||
if (pdata->regulator == NULL)
|
||
return 0;
|
||
|
||
regulator_put(pdata->regulator);
|
||
pdata->regulator = NULL;
|
||
|
||
return 1;
|
||
}
|
||
|
||
static unsigned int twi_drv_query_irq_status(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val = readl(base_addr + TWI_DRIVER_INTC);
|
||
|
||
return (reg_val & TWI_DRV_STAT_MASK);
|
||
}
|
||
|
||
static void twi_drv_clear_irq_flag(u32 pending_bit, void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_INTC);
|
||
|
||
pending_bit &= TWI_DRV_STAT_MASK;
|
||
reg_val |= pending_bit;
|
||
writel(reg_val, base_addr + TWI_DRIVER_INTC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_INTC,
|
||
readl(base_addr + TWI_DRIVER_INTC));
|
||
}
|
||
|
||
static void i2c_drv_clear_pending(void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_INTC);
|
||
|
||
reg_val |= TWI_DRV_STAT_MASK;
|
||
writel(reg_val, base_addr + TWI_DRIVER_INTC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_INTC,
|
||
readl(base_addr + TWI_DRIVER_INTC));
|
||
}
|
||
|
||
/* start i2c transfer */
|
||
static void i2c_start_xfer(void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_CTRL);
|
||
|
||
reg_val |= START_TRAN;
|
||
writel(reg_val, base_addr + TWI_DRIVER_CTRL);
|
||
}
|
||
|
||
/*
|
||
* send DMA RX Req when the data byte number in RECV_FIFO reaches RX_TRIG
|
||
* or Read Packet Tansmission completed with RECV_FIFO not empty
|
||
*/
|
||
static void i2c_set_rx_trig_level(u32 val, void __iomem *base_addr)
|
||
{
|
||
u32 mask = TRIG_MASK;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_DMAC);
|
||
|
||
val = (val & mask) << 16;
|
||
reg_val &= ~(mask << 16);
|
||
reg_val |= val;
|
||
writel(reg_val, base_addr + TWI_DRIVER_DMAC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_DMAC,
|
||
readl(base_addr + TWI_DRIVER_DMAC));
|
||
}
|
||
|
||
/* bytes be send as slave device reg address */
|
||
static void i2c_set_packet_addr_byte(u32 val, void __iomem *base_addr)
|
||
{
|
||
u32 mask = ADDR_BYTE;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_FMT);
|
||
|
||
reg_val &= ~mask;
|
||
val = (val << 16) & mask;
|
||
reg_val |= val;
|
||
writel(reg_val, base_addr + TWI_DRIVER_FMT);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_FMT,
|
||
readl(base_addr + TWI_DRIVER_FMT));
|
||
}
|
||
|
||
/* bytes be send/received as data */
|
||
static void i2c_set_packet_data_byte(u32 val, void __iomem *base_addr)
|
||
{
|
||
u32 mask = DATA_BYTE;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_FMT);
|
||
|
||
reg_val &= ~mask;
|
||
val &= mask;
|
||
reg_val |= val;
|
||
writel(reg_val, base_addr + TWI_DRIVER_FMT);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_FMT,
|
||
readl(base_addr + TWI_DRIVER_FMT));
|
||
|
||
}
|
||
|
||
/* interval between each packet in 32*Fscl cycles */
|
||
static void i2c_set_packet_interval(u32 val, void __iomem *base_addr)
|
||
{
|
||
u32 mask = INTERVAL_MASK;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_CFG);
|
||
|
||
reg_val &= ~mask;
|
||
val <<= 16;
|
||
val &= INTERVAL_MASK;
|
||
reg_val |= val;
|
||
writel(reg_val, base_addr + TWI_DRIVER_CFG);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_CFG,
|
||
readl(base_addr + TWI_DRIVER_CFG));
|
||
}
|
||
|
||
/* FIFO data be transmitted as PACKET_CNT packets in current format */
|
||
static void i2c_set_packet_cnt(u32 val, void __iomem *base_addr)
|
||
{
|
||
u32 mask = PACKET_MASK;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_CFG);
|
||
|
||
reg_val &= ~mask;
|
||
val &= mask;
|
||
reg_val |= val;
|
||
writel(reg_val, base_addr + TWI_DRIVER_CFG);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_CFG,
|
||
readl(base_addr + TWI_DRIVER_CFG));
|
||
}
|
||
|
||
/* do not send slave_id +W */
|
||
static void i2c_enable_read_tran_mode(void __iomem *base_addr)
|
||
{
|
||
u32 mask = READ_TRAN;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_CTRL);
|
||
|
||
reg_val |= mask;
|
||
writel(reg_val, base_addr + TWI_DRIVER_CTRL);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_CTRL,
|
||
readl(base_addr + TWI_DRIVER_CTRL));
|
||
|
||
}
|
||
|
||
/* send slave_id + W */
|
||
static void i2c_disable_read_tran_mode(void __iomem *base_addr)
|
||
{
|
||
u32 mask = READ_TRAN;
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_CTRL);
|
||
|
||
reg_val &= ~mask;
|
||
writel(reg_val, base_addr + TWI_DRIVER_CTRL);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_CTRL,
|
||
readl(base_addr + TWI_DRIVER_CTRL));
|
||
|
||
}
|
||
|
||
static void i2c_drv_enable_tran_irq(u32 bitmap, void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_INTC);
|
||
|
||
bitmap &= TWI_DRV_INT_MASK;
|
||
reg_val |= bitmap;
|
||
writel(reg_val, base_addr + TWI_DRIVER_INTC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_INTC,
|
||
readl(base_addr + TWI_DRIVER_INTC));
|
||
|
||
}
|
||
|
||
static void i2c_drv_disable_tran_irq(u32 bitmap, void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_INTC);
|
||
|
||
bitmap &= TWI_DRV_INT_MASK;
|
||
reg_val &= ~bitmap;
|
||
writel(reg_val, base_addr + TWI_DRIVER_INTC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_INTC,
|
||
readl(base_addr + TWI_DRIVER_INTC));
|
||
}
|
||
|
||
static void i2c_drv_enable_dma_irq(u32 bitmap, void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_DMAC);
|
||
|
||
bitmap &= I2C_DRQEN_MASK;
|
||
reg_val |= bitmap;
|
||
writel(reg_val, base_addr + TWI_DRIVER_DMAC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_DMAC,
|
||
readl(base_addr + TWI_DRIVER_DMAC));
|
||
}
|
||
|
||
static void i2c_drv_disable_dma_irq(u32 bitmap, void __iomem *base_addr)
|
||
{
|
||
u32 reg_val = readl(base_addr + TWI_DRIVER_DMAC);
|
||
|
||
bitmap &= I2C_DRQEN_MASK;
|
||
reg_val &= ~bitmap;
|
||
writel(reg_val, base_addr + TWI_DRIVER_DMAC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_DMAC,
|
||
readl(base_addr + TWI_DRIVER_DMAC));
|
||
}
|
||
|
||
static void
|
||
sunxi_i2c_drv_slave_addr(struct sunxi_i2c *i2c, struct i2c_msg *msgs)
|
||
{
|
||
unsigned int val = 0, cmd = 0;
|
||
|
||
/* read, default value is write */
|
||
if (msgs->flags & I2C_M_RD)
|
||
cmd = SLV_CMD;
|
||
|
||
if (msgs->flags & I2C_M_TEN) {
|
||
/* SLV_ID | CMD | SLV_ID_X */
|
||
val = ((0x78 | ((msgs->addr >> 8) & 0x03)) << 9) | cmd
|
||
| (msgs->addr & 0xff);
|
||
} else {
|
||
val = ((msgs->addr & 0x7f) << 9) | cmd;
|
||
}
|
||
|
||
writel(val, i2c->base_addr + TWI_DRIVER_SLV);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_SLV,
|
||
readl(i2c->base_addr + TWI_DRIVER_SLV));
|
||
|
||
}
|
||
|
||
/* the number of data in SEND_FIFO */
|
||
static int i2c_query_txfifo(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val;
|
||
|
||
reg_val = readl(base_addr + TWI_DRIVER_FIFOC) & SEND_FIFO_CONT;
|
||
|
||
return reg_val;
|
||
}
|
||
|
||
/* the number of data in RECV_FIFO */
|
||
static int i2c_query_rxfifo(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val;
|
||
|
||
reg_val = readl(base_addr + TWI_DRIVER_FIFOC) & RECV_FIFO_CONT;
|
||
reg_val >>= 16;
|
||
|
||
return reg_val;
|
||
}
|
||
|
||
static void i2c_clear_txfifo(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val;
|
||
|
||
reg_val = readl(base_addr + TWI_DRIVER_FIFOC);
|
||
reg_val |= SEND_FIFO_CLEAR;
|
||
writel(reg_val, base_addr + TWI_DRIVER_FIFOC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_FIFOC,
|
||
readl(base_addr + TWI_DRIVER_FIFOC));
|
||
}
|
||
|
||
static void i2c_clear_rxfifo(void __iomem *base_addr)
|
||
{
|
||
unsigned int reg_val;
|
||
|
||
reg_val = readl(base_addr + TWI_DRIVER_FIFOC);
|
||
reg_val |= RECV_FIFO_CLEAR;
|
||
writel(reg_val, base_addr + TWI_DRIVER_FIFOC);
|
||
dprintk(DEBUG_INFO, "offset: 0x%x value: 0x%x\n", TWI_DRIVER_FIFOC,
|
||
readl(base_addr + TWI_DRIVER_FIFOC));
|
||
}
|
||
|
||
static int i2c_sunxi_send_msgs(struct sunxi_i2c *i2c, struct i2c_msg *msgs)
|
||
{
|
||
unsigned int i;
|
||
unsigned char time = 0xff;
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] msgs->len = %d\n",
|
||
i2c->bus_num, msgs->len);
|
||
|
||
for (i = 0; i < msgs->len; i++) {
|
||
while ((i2c_query_txfifo(i2c->base_addr) >= MAX_FIFO) && time--)
|
||
;
|
||
dprintk(DEBUG_INFO, "[i2c%d] time = %d\n", i2c->bus_num, time);
|
||
if (time) {
|
||
writeb(msgs->buf[i], i2c->base_addr + TWI_DRIVER_SENDF);
|
||
dprintk(DEBUG_INFO, "[i2c%d] writeb: Byte[0] = 0x%x,"
|
||
"fifo len = %d\n",
|
||
i2c->bus_num, msgs->buf[i],
|
||
i2c_query_txfifo(i2c->base_addr));
|
||
} else {
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] SEND FIFO overflow. timeout\n",
|
||
i2c->bus_num);
|
||
return -EINVAL;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static unsigned int
|
||
i2c_sunxi_recv_msgs(struct sunxi_i2c *i2c, struct i2c_msg *msgs)
|
||
{
|
||
unsigned int i;
|
||
unsigned char time = 0xff;
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] recving data\n", i2c->bus_num);
|
||
for (i = 0; i < msgs->len; i++) {
|
||
while (!i2c_query_rxfifo(i2c->base_addr) && time--)
|
||
;
|
||
if (time) {
|
||
msgs->buf[i] = readb(i2c->base_addr + TWI_DRIVER_RECVF);
|
||
dprintk(DEBUG_INFO, "[i2c%d] readb: Byte[%d] = 0x%x\n",
|
||
i2c->bus_num, i, msgs->buf[i]);
|
||
} else
|
||
return 0;
|
||
}
|
||
return msgs->len;
|
||
}
|
||
|
||
static int sunxi_i2c_drv_core_process(struct sunxi_i2c *i2c)
|
||
{
|
||
void __iomem *base_addr = i2c->base_addr;
|
||
unsigned long flags = 0;
|
||
unsigned int status, code;
|
||
unsigned int fifolen;
|
||
|
||
spin_lock_irqsave(&i2c->lock, flags);
|
||
|
||
status = twi_drv_query_irq_status(base_addr);
|
||
dprintk(DEBUG_INFO, "[i2c%d] irq status = 0x%x\n",
|
||
i2c->bus_num, status);
|
||
twi_drv_clear_irq_flag(status, base_addr);
|
||
|
||
if ((status & RX_REQ_PD) && (i2c->msg->len < DMA_THRESHOLD)) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] i2c->msg->len = %d\n",
|
||
i2c->bus_num, i2c->msg->len);
|
||
fifolen = (readl(base_addr + TWI_DRIVER_FIFOC)
|
||
& RECV_FIFO_CONT) >> 16;
|
||
dprintk(DEBUG_INFO, "[i2c%d] fifo len = %d\n",
|
||
i2c->bus_num, fifolen);
|
||
if (i2c_sunxi_recv_msgs(i2c, i2c->msg))
|
||
i2c_drv_disable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT
|
||
| RX_REQ_INT, i2c->base_addr);
|
||
fifolen = (readl(base_addr + TWI_DRIVER_FIFOC)
|
||
& RECV_FIFO_CONT) >> 16;
|
||
dprintk(DEBUG_INFO, "[i2c%d] fifo len = %d\n",
|
||
i2c->bus_num, fifolen);
|
||
}
|
||
|
||
if (status & TRAN_COM_PD) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] packet transmission completed\n",
|
||
i2c->bus_num);
|
||
dprintk(DEBUG_INFO, "[i2c%d] fifo len = %d\n", i2c->bus_num,
|
||
i2c_query_txfifo(i2c->base_addr));
|
||
i2c->result = RESULT_COMPLETE;
|
||
|
||
wake_up(&i2c->wait);
|
||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||
}
|
||
|
||
if (status & TRAN_ERR_PD) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] packet transmission failed\n",
|
||
i2c->bus_num);
|
||
i2c->result = RESULT_ERR;
|
||
code = readl(base_addr + TWI_DRIVER_CTRL);
|
||
code = (code & TWI_DRV_STA) >> 16;
|
||
switch (code) {
|
||
case 0x00:
|
||
I2C_ERR("[i2c%d] bus error\n", i2c->bus_num);
|
||
break;
|
||
case 0x01:
|
||
I2C_ERR("[i2c%d] Timeout when sending 9th SCL clk\n",
|
||
i2c->bus_num);
|
||
break;
|
||
case 0x20:
|
||
I2C_ERR("[i2c%d] Address + Write bit transmitted,"
|
||
"ACK not received\n", i2c->bus_num);
|
||
break;
|
||
case 0x30:
|
||
I2C_ERR("[i2c%d] Data byte transmitted in master mode,"
|
||
"ACK not received\n", i2c->bus_num);
|
||
break;
|
||
case 0x38:
|
||
I2C_ERR("[i2c%d] Arbitration lost in address"
|
||
"or data byte\n", i2c->bus_num);
|
||
break;
|
||
case 0x48:
|
||
I2C_ERR("[i2c%d] Address + Read bit transmitted,"
|
||
"ACK not received\\n", i2c->bus_num);
|
||
break;
|
||
case 0x58:
|
||
I2C_ERR("[i2c%d] Data byte received in master mode,"
|
||
"ACK not received\n", i2c->bus_num);
|
||
break;
|
||
default:
|
||
I2C_ERR("[i2c%d] unknown error\n",
|
||
i2c->bus_num);
|
||
break;
|
||
}
|
||
i2c->msg_idx = code;
|
||
wake_up(&i2c->wait);
|
||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||
return code;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Functions for DMA support */
|
||
static void sunxi_i2c_dma_request(struct sunxi_i2c *i2c,
|
||
dma_addr_t phy_addr)
|
||
{
|
||
struct sunxi_i2c_dma *dma_tx, *dma_rx;
|
||
struct dma_slave_config dma_sconfig;
|
||
struct device *dev = &i2c->adap.dev;
|
||
dma_cap_mask_t mask_tx, mask_rx;
|
||
int ret;
|
||
|
||
dma_tx = devm_kzalloc(dev, sizeof(*dma_tx), GFP_KERNEL);
|
||
dma_rx = devm_kzalloc(dev, sizeof(*dma_rx), GFP_KERNEL);
|
||
if (!dma_tx || !dma_rx)
|
||
return;
|
||
|
||
dma_cap_zero(mask_tx);
|
||
dma_cap_set(DMA_SLAVE, mask_tx);
|
||
dma_tx->chan = dma_request_channel(mask_tx, NULL, NULL);
|
||
|
||
if (!dma_tx->chan) {
|
||
I2C_ERR("[i2c%d] can't request DMA tx channel\n", i2c->bus_num);
|
||
goto fail_al;
|
||
}
|
||
|
||
dma_sconfig.dst_addr = phy_addr + TWI_DRIVER_SENDF;
|
||
dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||
dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||
dma_sconfig.src_maxburst = 16;
|
||
dma_sconfig.dst_maxburst = 16;
|
||
#if defined(CONFIG_ARCH_SUN8IW18)
|
||
dma_sconfig.slave_id = sunxi_slave_id(SUNXI_TWI_DRQ_TX(i2c->bus_num),
|
||
DRQSRC_SDRAM);
|
||
#endif
|
||
dma_sconfig.direction = DMA_MEM_TO_DEV;
|
||
ret = dmaengine_slave_config(dma_tx->chan, &dma_sconfig);
|
||
if (ret < 0) {
|
||
I2C_ERR("[i2c%d] can't configure tx channel\n", i2c->bus_num);
|
||
goto fail_tx;
|
||
}
|
||
i2c->dma_tx = dma_tx;
|
||
|
||
dma_cap_zero(mask_rx);
|
||
dma_cap_set(DMA_SLAVE, mask_rx);
|
||
dma_rx->chan = dma_request_channel(mask_rx, NULL, NULL);
|
||
|
||
if (!dma_rx->chan) {
|
||
I2C_ERR("[i2c%d] can't request DMA rx channel\n", i2c->bus_num);
|
||
goto fail_tx;
|
||
}
|
||
|
||
dma_sconfig.src_addr = phy_addr + TWI_DRIVER_RECVF;
|
||
dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||
dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||
dma_sconfig.src_maxburst = 16;
|
||
dma_sconfig.dst_maxburst = 16;
|
||
#if defined(CONFIG_ARCH_SUN8IW18)
|
||
dma_sconfig.slave_id = sunxi_slave_id(DRQDST_SDRAM,
|
||
SUNXI_TWI_DRQ_RX(i2c->bus_num));
|
||
#endif
|
||
dma_sconfig.direction = DMA_DEV_TO_MEM;
|
||
ret = dmaengine_slave_config(dma_rx->chan, &dma_sconfig);
|
||
if (ret < 0) {
|
||
I2C_ERR("[i2c%d] can't configure rx channel\n", i2c->bus_num);
|
||
goto fail_rx;
|
||
}
|
||
i2c->dma_rx = dma_rx;
|
||
|
||
|
||
init_completion(&i2c->cmd_complete);
|
||
dprintk(DEBUG_INIT, "[i2c%d] using %s (tx) and %s (rx)"
|
||
"for DMA transfers\n", i2c->bus_num,
|
||
dma_chan_name(i2c->dma_tx->chan),
|
||
dma_chan_name(i2c->dma_rx->chan));
|
||
|
||
return;
|
||
|
||
fail_rx:
|
||
dma_release_channel(i2c->dma_rx->chan);
|
||
fail_tx:
|
||
dma_release_channel(i2c->dma_tx->chan);
|
||
fail_al:
|
||
devm_kfree(dev, dma_tx);
|
||
devm_kfree(dev, dma_rx);
|
||
dprintk(DEBUG_INIT, "[i2c%d] can't use DMA, using PIO instead\n",
|
||
i2c->bus_num);
|
||
}
|
||
|
||
static void sunxi_i2c_dma_callback(void *arg)
|
||
{
|
||
struct sunxi_i2c *i2c = (struct sunxi_i2c *)arg;
|
||
|
||
if (i2c->dma_using == i2c->dma_tx)
|
||
dprintk(DEBUG_INFO, "[i2c%d] dma write data end\n",
|
||
i2c->bus_num);
|
||
else if (i2c->dma_using == i2c->dma_rx)
|
||
dprintk(DEBUG_INFO, "[i2c%d] dma read data end\n",
|
||
i2c->bus_num);
|
||
dma_unmap_single(i2c->dma_using->chan->device->dev,
|
||
i2c->dma_using->dma_buf,
|
||
i2c->dma_using->dma_len, i2c->dma_using->dma_data_dir);
|
||
complete(&i2c->cmd_complete);
|
||
dprintk(DEBUG_INFO, "[i2c%d] callback complete\n", i2c->bus_num);
|
||
}
|
||
|
||
static void sunxi_i2c_dma_free(struct sunxi_i2c_dma *dma)
|
||
{
|
||
dma->dma_buf = 0;
|
||
dma->dma_len = 0;
|
||
dma_release_channel(dma->chan);
|
||
dma->chan = NULL;
|
||
}
|
||
|
||
static int i2c_sunxi_drv_complete(struct sunxi_i2c *i2c)
|
||
{
|
||
unsigned long timeout = 0;
|
||
|
||
timeout = wait_event_timeout(i2c->wait, i2c->result, 2*HZ);
|
||
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] twi driver xfer timeout (dev addr:0x%x)\n",
|
||
i2c->bus_num, i2c->msg->addr);
|
||
|
||
dump_reg(i2c->base_addr, 0x200, 0x40);
|
||
|
||
i2c_drv_disable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT
|
||
| RX_REQ_INT | TX_REQ_INT, i2c->base_addr);
|
||
i2c_drv_disable_dma_irq(DMA_TX | DMA_RX, i2c->base_addr);
|
||
return -ETIME;
|
||
} else if (i2c->result == RESULT_ERR) {
|
||
I2C_ERR("[i2c%d] incomplete xfer"
|
||
"(status: 0x%x, dev addr: 0x%x)\n",
|
||
i2c->bus_num, i2c->msg_idx, i2c->msg->addr);
|
||
|
||
dump_reg(i2c->base_addr, 0x200, 0x40);
|
||
|
||
i2c_drv_disable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT
|
||
| RX_REQ_INT | TX_REQ_INT, i2c->base_addr);
|
||
i2c_drv_disable_dma_irq(DMA_TX | DMA_RX, i2c->base_addr);
|
||
return -ECOMM;
|
||
}
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] xfer complete\n", i2c->bus_num);
|
||
i2c->result = 0;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int i2c_sunxi_dma_xfer(struct sunxi_i2c *i2c, unsigned char *buf)
|
||
{
|
||
unsigned long time_left;
|
||
struct sunxi_i2c_dma *dma = i2c->dma_using;
|
||
struct dma_async_tx_descriptor *dma_desc;
|
||
struct device *chan_dev = dma->chan->device->dev;
|
||
|
||
dma->dma_buf = dma_map_single(chan_dev, buf,
|
||
dma->dma_len, dma->dma_data_dir);
|
||
if (dma_mapping_error(chan_dev, dma->dma_buf)) {
|
||
I2C_ERR("DMA mapping failed\n");
|
||
goto err_map;
|
||
}
|
||
dma_desc = dmaengine_prep_slave_single(dma->chan, dma->dma_buf,
|
||
dma->dma_len, dma->dma_transfer_dir,
|
||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||
if (!dma_desc) {
|
||
I2C_ERR("Not able to get desc for DMA xfer\n");
|
||
goto err_desc;
|
||
}
|
||
dma_desc->callback = sunxi_i2c_dma_callback;
|
||
dma_desc->callback_param = i2c;
|
||
if (dma_submit_error(dmaengine_submit(dma_desc))) {
|
||
I2C_ERR("[i2c%d] DMA submit failed\n", i2c->bus_num);
|
||
goto err_submit;
|
||
}
|
||
|
||
reinit_completion(&i2c->cmd_complete);
|
||
dma_async_issue_pending(dma->chan);
|
||
dprintk(DEBUG_INFO, "[i2c%d] dma issue pending\n", i2c->bus_num);
|
||
|
||
time_left = wait_for_completion_timeout(
|
||
&i2c->cmd_complete,
|
||
msecs_to_jiffies(DMA_TIMEOUT));
|
||
dprintk(DEBUG_INFO, "[i2c%d] time_left = %lu\n",
|
||
i2c->bus_num, time_left);
|
||
|
||
return 0;
|
||
|
||
err_submit:
|
||
err_desc:
|
||
dma_unmap_single(chan_dev, dma->dma_buf,
|
||
dma->dma_len, dma->dma_data_dir);
|
||
err_map:
|
||
return -EINVAL;
|
||
}
|
||
|
||
static int sunxi_i2c_drv_write(struct sunxi_i2c *i2c, struct i2c_msg *msgs)
|
||
{
|
||
sunxi_i2c_drv_slave_addr(i2c, msgs);
|
||
if (msgs->len == 1) {
|
||
i2c_set_packet_addr_byte(0, i2c->base_addr);
|
||
i2c_set_packet_data_byte(msgs->len, i2c->base_addr);
|
||
} else {
|
||
i2c_set_packet_addr_byte(1, i2c->base_addr);
|
||
i2c_set_packet_data_byte(msgs->len - 1, i2c->base_addr);
|
||
}
|
||
i2c_set_packet_cnt(1, i2c->base_addr);
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_enable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT, i2c->base_addr);
|
||
i2c_start_xfer(i2c->base_addr);
|
||
|
||
i2c_sunxi_send_msgs(i2c, msgs);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_drv_dma_write(struct sunxi_i2c *i2c, struct i2c_msg *msgs)
|
||
{
|
||
int ret = 0;
|
||
|
||
sunxi_i2c_drv_slave_addr(i2c, msgs);
|
||
i2c_set_packet_addr_byte(1, i2c->base_addr);
|
||
i2c_set_packet_data_byte(msgs->len - 1, i2c->base_addr);
|
||
i2c_set_packet_cnt(1, i2c->base_addr);
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_enable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT, i2c->base_addr);
|
||
i2c_drv_enable_dma_irq(DMA_TX, i2c->base_addr);
|
||
i2c_start_xfer(i2c->base_addr);
|
||
|
||
i2c->dma_using = i2c->dma_tx;
|
||
i2c->dma_using->dma_transfer_dir = DMA_MEM_TO_DEV;
|
||
i2c->dma_using->dma_data_dir = DMA_TO_DEVICE;
|
||
i2c->dma_using->dma_len = msgs->len;
|
||
|
||
ret = i2c_sunxi_dma_xfer(i2c, msgs->buf);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int
|
||
sunxi_i2c_drv_mulpk_write(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num)
|
||
{
|
||
int i;
|
||
|
||
sunxi_i2c_drv_slave_addr(i2c, msgs);
|
||
i2c_set_packet_data_byte(msgs->len - 1, i2c->base_addr);
|
||
i2c_set_packet_interval(0x30, i2c->base_addr);
|
||
i2c_set_packet_cnt(num, i2c->base_addr);
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_enable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT, i2c->base_addr);
|
||
i2c_start_xfer(i2c->base_addr);
|
||
|
||
for (i = 0; i < num; i++)
|
||
i2c_sunxi_send_msgs(i2c, msgs + i);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
sunxi_i2c_drv_dma_mulpk_write(struct sunxi_i2c *i2c, struct i2c_msg *msgs,
|
||
int num)
|
||
{
|
||
int i, ret = 0;
|
||
unsigned char *buf;
|
||
unsigned int tlen = num * msgs->len;
|
||
|
||
sunxi_i2c_drv_slave_addr(i2c, msgs);
|
||
i2c_set_packet_data_byte(msgs->len - 1, i2c->base_addr);
|
||
i2c_set_packet_interval(0x30, i2c->base_addr);
|
||
i2c_set_packet_cnt(num, i2c->base_addr);
|
||
|
||
buf = kzalloc(tlen, GFP_KERNEL);
|
||
if (!buf) {
|
||
I2C_ERR("[i2c%d] kzalloc failed\n", i2c->bus_num);
|
||
return -ENOMEM;
|
||
}
|
||
|
||
for (i = 0; i < num; i++) {
|
||
memcpy(buf, msgs->buf, msgs->len);
|
||
msgs++;
|
||
buf += msgs->len;
|
||
}
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_enable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT, i2c->base_addr);
|
||
i2c_drv_enable_dma_irq(DMA_TX, i2c->base_addr);
|
||
i2c_start_xfer(i2c->base_addr);
|
||
|
||
i2c->dma_using = i2c->dma_tx;
|
||
i2c->dma_using->dma_transfer_dir = DMA_MEM_TO_DEV;
|
||
i2c->dma_using->dma_data_dir = DMA_TO_DEVICE;
|
||
i2c->dma_using->dma_len = tlen;
|
||
|
||
ret = i2c_sunxi_dma_xfer(i2c, buf);
|
||
kfree(buf);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int
|
||
sunxi_i2c_drv_read(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num)
|
||
{
|
||
struct i2c_msg *wmsgs, *rmsgs;
|
||
|
||
if (num == 1) {
|
||
wmsgs = NULL;
|
||
rmsgs = msgs;
|
||
} else if (num == 2) {
|
||
wmsgs = msgs;
|
||
rmsgs = msgs + 1;
|
||
}
|
||
|
||
i2c->msg = rmsgs;
|
||
|
||
sunxi_i2c_drv_slave_addr(i2c, rmsgs);
|
||
i2c_set_packet_cnt(1, i2c->base_addr);
|
||
i2c_set_packet_data_byte(rmsgs->len, i2c->base_addr);
|
||
if (rmsgs->len > MAX_FIFO)
|
||
i2c_set_rx_trig_level(MAX_FIFO, i2c->base_addr);
|
||
else
|
||
i2c_set_rx_trig_level(rmsgs->len, i2c->base_addr);
|
||
if (i2c_query_rxfifo(i2c->base_addr))
|
||
i2c_clear_rxfifo(i2c->base_addr);
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_enable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT | RX_REQ_INT,
|
||
i2c->base_addr);
|
||
i2c_start_xfer(i2c->base_addr);
|
||
|
||
if (wmsgs)
|
||
i2c_sunxi_send_msgs(i2c, wmsgs);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
sunxi_i2c_drv_dma_read(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num)
|
||
{
|
||
int ret = 0;
|
||
struct i2c_msg *wmsgs, *rmsgs;
|
||
|
||
if (num == 1) {
|
||
wmsgs = NULL;
|
||
rmsgs = msgs;
|
||
} else if (num == 2) {
|
||
wmsgs = msgs;
|
||
rmsgs = msgs + 1;
|
||
}
|
||
|
||
i2c->msg = rmsgs;
|
||
|
||
sunxi_i2c_drv_slave_addr(i2c, rmsgs);
|
||
i2c_set_packet_data_byte(rmsgs->len, i2c->base_addr);
|
||
i2c_set_packet_cnt(1, i2c->base_addr);
|
||
i2c_set_rx_trig_level(MAX_FIFO/2, i2c->base_addr);
|
||
if (i2c_query_rxfifo(i2c->base_addr))
|
||
i2c_clear_rxfifo(i2c->base_addr);
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_enable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT, i2c->base_addr);
|
||
i2c_drv_enable_dma_irq(DMA_RX, i2c->base_addr);
|
||
i2c_start_xfer(i2c->base_addr);
|
||
if (wmsgs)
|
||
i2c_sunxi_send_msgs(i2c, wmsgs);
|
||
|
||
i2c->dma_using = i2c->dma_rx;
|
||
i2c->dma_using->dma_transfer_dir = DMA_DEV_TO_MEM;
|
||
i2c->dma_using->dma_data_dir = DMA_FROM_DEVICE;
|
||
i2c->dma_using->dma_len = rmsgs->len;
|
||
|
||
ret = i2c_sunxi_dma_xfer(i2c, rmsgs->buf);
|
||
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* sunxi_i2c_drv_do_xfer - twi driver transmission control
|
||
* @i2c: struct of sunxi_i2c
|
||
* @msgs: One or more messages to execute before STOP is issued to
|
||
* terminate the operation; each message begins with a START.
|
||
* @num: Number of messages to be executed.
|
||
*
|
||
* Returns negative errno, else the number of messages executed.
|
||
*/
|
||
static int
|
||
sunxi_i2c_drv_do_xfer(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num)
|
||
{
|
||
int ret;
|
||
|
||
i2c_drv_clear_pending(i2c->base_addr);
|
||
i2c_drv_disable_tran_irq(TRAN_COM_INT | TRAN_ERR_INT
|
||
| RX_REQ_INT | TX_REQ_INT, i2c->base_addr);
|
||
i2c_drv_disable_dma_irq(DMA_TX | DMA_RX, i2c->base_addr);
|
||
if (i2c_query_txfifo(i2c->base_addr))
|
||
i2c_clear_txfifo(i2c->base_addr);
|
||
|
||
if (num == 1) {
|
||
if (msgs->flags & I2C_M_RD) {
|
||
/* 1 msgs read */
|
||
i2c_enable_read_tran_mode(i2c->base_addr);
|
||
i2c_set_packet_addr_byte(0, i2c->base_addr);
|
||
|
||
if (i2c->dma_rx && (msgs->len >= DMA_THRESHOLD)) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] master dma read\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_dma_read(i2c, msgs, num);
|
||
} else {
|
||
dprintk(DEBUG_INFO, "[i2c%d] master cpu read\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_read(i2c, msgs, num);
|
||
}
|
||
} else {
|
||
/* 1 msgs write */
|
||
i2c_disable_read_tran_mode(i2c->base_addr);
|
||
|
||
if (i2c->dma_tx && (msgs->len >= DMA_THRESHOLD)) {
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] master dma write\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_dma_write(i2c, msgs);
|
||
} else {
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] master cpu write\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_write(i2c, msgs);
|
||
}
|
||
}
|
||
} else if ((num == 2) && ((msgs + 1)->flags & I2C_M_RD)) {
|
||
/* 2 msgs read */
|
||
i2c_disable_read_tran_mode(i2c->base_addr);
|
||
i2c_set_packet_addr_byte(1, i2c->base_addr);
|
||
|
||
if (i2c->dma_rx && ((msgs + 1)->len >= DMA_THRESHOLD)) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] master dma read\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_dma_read(i2c, msgs, num);
|
||
} else {
|
||
dprintk(DEBUG_INFO, "[i2c%d] master cpu read\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_read(i2c, msgs, num);
|
||
}
|
||
} else {
|
||
/* multiple write with the same format packet */
|
||
i2c_disable_read_tran_mode(i2c->base_addr);
|
||
i2c_set_packet_addr_byte(1, i2c->base_addr);
|
||
|
||
if (i2c->dma_tx && ((num * msgs->len) >= DMA_THRESHOLD)) {
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] master dma multiple packet write\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_dma_mulpk_write(i2c, msgs, num);
|
||
} else {
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] master cpu multiple packet write\n",
|
||
i2c->bus_num);
|
||
ret = sunxi_i2c_drv_mulpk_write(i2c, msgs, num);
|
||
}
|
||
}
|
||
if (ret)
|
||
return ret;
|
||
|
||
ret = i2c_sunxi_drv_complete(i2c);
|
||
|
||
if (ret)
|
||
return ret;
|
||
else
|
||
return num;
|
||
}
|
||
|
||
|
||
/*
|
||
****************************************************************************
|
||
*
|
||
* FunctionName: sunxi_i2c_addr_byte
|
||
*
|
||
* Description:
|
||
* 7bits addr: 7-1bits addr+0 bit r/w
|
||
* 10bits addr: 1111_11xx_xxxx_xxxx-->1111_0xx_rw,xxxx_xxxx
|
||
* send the 7 bits addr,or the first part of 10 bits addr
|
||
* Parameters:
|
||
*
|
||
*
|
||
* Return value:
|
||
* <20><>
|
||
* Notes:
|
||
*
|
||
****************************************************************************
|
||
*/
|
||
static void sunxi_i2c_addr_byte(struct sunxi_i2c *i2c)
|
||
{
|
||
unsigned char addr = 0;
|
||
unsigned char tmp = 0;
|
||
|
||
if (i2c->msg[i2c->msg_idx].flags & I2C_M_TEN) {
|
||
/* 0111_10xx,ten bits address--9:8bits */
|
||
tmp = 0x78 | (((i2c->msg[i2c->msg_idx].addr)>>8) & 0x03);
|
||
addr = tmp << 1; /*1111_0xx0*/
|
||
/* how about the second part of ten bits addr? */
|
||
/* Answer: deal at twi_core_process() */
|
||
} else
|
||
/* 7-1bits addr, xxxx_xxx0 */
|
||
addr = (i2c->msg[i2c->msg_idx].addr & 0x7f) << 1;
|
||
|
||
/* read, default value is write */
|
||
if (i2c->msg[i2c->msg_idx].flags & I2C_M_RD)
|
||
addr |= 1;
|
||
|
||
#ifdef CONFIG_SUNXI_I2C_PRINT_TRANSFER_INFO
|
||
if (i2c->bus_num == CONFIG_SUNXI_I2C_PRINT_TRANSFER_INFO_WITH_BUS_NUM) {
|
||
if (i2c->msg[i2c->msg_idx].flags & I2C_M_TEN)
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] first part of 10bits = 0x%x\n",
|
||
i2c->bus_num, addr);
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] 7bits+r/w = 0x%x\n",
|
||
i2c->bus_num, addr);
|
||
}
|
||
#else
|
||
if (unlikely(bus_transfer_dbg != -1)) {
|
||
if (i2c->bus_num == bus_transfer_dbg) {
|
||
if (i2c->msg[i2c->msg_idx].flags & I2C_M_TEN)
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] first part of 10bits = 0x%x\n",
|
||
i2c->bus_num, addr);
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] 7bits+r/w = 0x%x\n",
|
||
i2c->bus_num, addr);
|
||
}
|
||
}
|
||
#endif
|
||
/* send 7bits+r/w or the first part of 10bits */
|
||
twi_put_byte(i2c->base_addr, &addr);
|
||
}
|
||
|
||
|
||
static int sunxi_i2c_core_process(struct sunxi_i2c *i2c)
|
||
{
|
||
void __iomem *base_addr = i2c->base_addr;
|
||
int ret = SUNXI_I2C_OK;
|
||
int err_code = 0;
|
||
unsigned char state = 0;
|
||
unsigned char tmp = 0;
|
||
unsigned long flags = 0;
|
||
|
||
state = twi_query_irq_status(base_addr);
|
||
|
||
spin_lock_irqsave(&i2c->lock, flags);
|
||
|
||
#ifdef CONFIG_SUNXI_I2C_PRINT_TRANSFER_INFO
|
||
if (i2c->bus_num == CONFIG_SUNXI_I2C_PRINT_TRANSFER_INFO_WITH_BUS_NUM)
|
||
dprintk(DEBUG_INFO, "[i2c%d][slave address = (0x%x),"
|
||
"state = (0x%x)]\n",
|
||
i2c->bus_num, i2c->msg->addr, state);
|
||
#else
|
||
if (unlikely(bus_transfer_dbg != -1)) {
|
||
if (i2c->bus_num == bus_transfer_dbg)
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d][slave address = (0x%x),"
|
||
"state = (0x%x)]\n",
|
||
i2c->bus_num, i2c->msg->addr, state);
|
||
}
|
||
#endif
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d][slave address = (0x%x), state = (0x%x)]\n",
|
||
i2c->bus_num, i2c->msg->addr, state);
|
||
|
||
if (i2c->msg == NULL) {
|
||
I2C_ERR("[i2c%d] i2c message is NULL, err_code = 0xfe\n",
|
||
i2c->bus_num);
|
||
err_code = 0xfe;
|
||
goto msg_null;
|
||
}
|
||
|
||
switch (state) {
|
||
case 0xf8:
|
||
/* On reset or stop the bus is idle, use only at poll method */
|
||
err_code = 0xf8;
|
||
goto err_out;
|
||
case 0x08: /* A START condition has been transmitted */
|
||
case 0x10: /* A repeated start condition has been transmitted */
|
||
sunxi_i2c_addr_byte(i2c);/* send slave address */
|
||
break;
|
||
case 0xd8: /* second addr has transmitted, ACK not received! */
|
||
case 0x20: /* SLA+W has been transmitted; NOT ACK has been received */
|
||
err_code = 0x20;
|
||
goto err_out;
|
||
case 0x18: /* SLA+W has been transmitted; ACK has been received */
|
||
/* if any, send second part of 10 bits addr */
|
||
if (i2c->msg[i2c->msg_idx].flags & I2C_M_TEN) {
|
||
/* the remaining 8 bits of address */
|
||
tmp = i2c->msg[i2c->msg_idx].addr & 0xff;
|
||
twi_put_byte(base_addr, &tmp); /* case 0xd0: */
|
||
break;
|
||
}
|
||
/* for 7 bit addr, then directly send data byte--case 0xd0: */
|
||
case 0xd0: /* second addr has transmitted,ACK received! */
|
||
case 0x28: /* Data byte in DATA REG has been transmitted; */
|
||
/* ACK has been received */
|
||
/* after send register address then START send write data */
|
||
if (i2c->msg_ptr < i2c->msg[i2c->msg_idx].len) {
|
||
twi_put_byte(base_addr,
|
||
&(i2c->msg[i2c->msg_idx].buf[i2c->msg_ptr]));
|
||
i2c->msg_ptr++;
|
||
break;
|
||
}
|
||
|
||
i2c->msg_idx++; /* the other msg */
|
||
i2c->msg_ptr = 0;
|
||
if (i2c->msg_idx == i2c->msg_num) {
|
||
err_code = SUNXI_I2C_OK;/* Success,wakeup */
|
||
goto ok_out;
|
||
} else if (i2c->msg_idx < i2c->msg_num) {
|
||
/* for restart pattern, read spec, two msgs */
|
||
ret = twi_restart(base_addr, i2c->bus_num);
|
||
if (ret == SUNXI_I2C_FAIL) {
|
||
I2C_ERR("[i2c%d] twi_regulator: %s\n",
|
||
i2c->bus_num,
|
||
((struct sunxi_i2c_platform_data *)(i2c->adap.dev.parent->platform_data))->regulator_id);
|
||
err_code = SUNXI_I2C_SFAIL;
|
||
goto err_out;/* START can't sendout */
|
||
}
|
||
} else {
|
||
err_code = SUNXI_I2C_FAIL;
|
||
goto err_out;
|
||
}
|
||
break;
|
||
case 0x30: /* Data byte in I2CDAT has been transmitted; */
|
||
/* NOT ACK has been received */
|
||
err_code = 0x30; /*err,wakeup the thread*/
|
||
goto err_out;
|
||
case 0x38: /* Arbitration lost during SLA+W, SLA+R or data bytes */
|
||
err_code = 0x38; /*err,wakeup the thread*/
|
||
goto err_out;
|
||
case 0x40: /* SLA+R has been transmitted; ACK has been received */
|
||
/* with Restart,needn't to send second part of 10 bits addr */
|
||
/* refer-"I2C-SPEC v2.1" */
|
||
/* enable A_ACK need it(receive data len) more than 1. */
|
||
if (i2c->msg[i2c->msg_idx].len > 1) {
|
||
/* send register addr complete,then enable the A_ACK */
|
||
/* and get ready for receiving data */
|
||
twi_enable_ack(base_addr);
|
||
twi_clear_irq_flag(base_addr);/* jump to case 0x50 */
|
||
} else if (i2c->msg[i2c->msg_idx].len == 1) {
|
||
twi_clear_irq_flag(base_addr);/* jump to case 0x58 */
|
||
}
|
||
break;
|
||
case 0x48: /* SLA+R has been transmitted; NOT ACK has been received */
|
||
err_code = 0x48; /*err,wakeup the thread*/
|
||
goto err_out;
|
||
case 0x50: /* Data bytes has been received; ACK has been transmitted */
|
||
/* receive first data byte */
|
||
if (i2c->msg_ptr < i2c->msg[i2c->msg_idx].len) {
|
||
/* more than 2 bytes, the last byte need not to send ACK */
|
||
if ((i2c->msg_ptr + 2) == i2c->msg[i2c->msg_idx].len)
|
||
/* last byte no ACK */
|
||
twi_disable_ack(base_addr);
|
||
|
||
/* get data then clear flag,then next data coming */
|
||
twi_get_byte(base_addr,
|
||
&i2c->msg[i2c->msg_idx].buf[i2c->msg_ptr]);
|
||
i2c->msg_ptr++;
|
||
break;
|
||
}
|
||
/* err process, the last byte should be @case 0x58 */
|
||
err_code = SUNXI_I2C_FAIL;/* err, wakeup */
|
||
goto err_out;
|
||
case 0x58:
|
||
/* Data byte has been received; NOT ACK has been transmitted */
|
||
/* received the last byte */
|
||
if (i2c->msg_ptr == i2c->msg[i2c->msg_idx].len - 1) {
|
||
twi_get_last_byte(base_addr,
|
||
&i2c->msg[i2c->msg_idx].buf[i2c->msg_ptr]);
|
||
i2c->msg_idx++;
|
||
i2c->msg_ptr = 0;
|
||
if (i2c->msg_idx == i2c->msg_num) {
|
||
/* succeed,wakeup the thread */
|
||
err_code = SUNXI_I2C_OK;
|
||
goto ok_out;
|
||
} else if (i2c->msg_idx < i2c->msg_num) {
|
||
/* repeat start */
|
||
ret = twi_restart(base_addr, i2c->bus_num);
|
||
if (ret == SUNXI_I2C_FAIL) {/* START fail */
|
||
I2C_ERR("[i2c%d] twi_regulator: %s\n",
|
||
i2c->bus_num,
|
||
((struct sunxi_i2c_platform_data *)(i2c->adap.dev.parent->platform_data))->regulator_id);
|
||
err_code = SUNXI_I2C_SFAIL;
|
||
goto err_out;
|
||
}
|
||
break;
|
||
}
|
||
} else {
|
||
err_code = 0x58;
|
||
goto err_out;
|
||
}
|
||
case 0x00: /* Bus error during master or slave mode due to illegal level condition */
|
||
err_code = 0xff;
|
||
goto err_out;
|
||
default:
|
||
err_code = state;
|
||
goto err_out;
|
||
}
|
||
i2c->debug_state = state;/* just for debug */
|
||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||
return ret;
|
||
|
||
ok_out:
|
||
err_out:
|
||
if (twi_stop(base_addr, i2c->bus_num) == SUNXI_I2C_TFAIL)
|
||
I2C_ERR("[i2c%d] STOP failed!\n", i2c->bus_num);
|
||
|
||
|
||
msg_null:
|
||
ret = sunxi_i2c_xfer_complete(i2c, err_code);/* wake up */
|
||
i2c->debug_state = state;/* just for debug */
|
||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||
return ret;
|
||
}
|
||
|
||
static irqreturn_t sunxi_i2c_handler(int this_irq, void *dev_id)
|
||
{
|
||
struct sunxi_i2c *i2c = (struct sunxi_i2c *)dev_id;
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] run to interrupt\n", i2c->bus_num);
|
||
|
||
if (i2c->twi_drv_used)
|
||
sunxi_i2c_drv_core_process(i2c);
|
||
else {
|
||
if (!twi_query_irq_flag(i2c->base_addr)) {
|
||
I2C_ERR("unknown interrupt!\n");
|
||
return IRQ_NONE;
|
||
}
|
||
|
||
/* disable irq */
|
||
twi_disable_irq(i2c->base_addr);
|
||
|
||
/* twi core process */
|
||
sunxi_i2c_core_process(i2c);
|
||
|
||
/*
|
||
* enable irq only when twi is transferring,
|
||
* otherwise disable irq
|
||
*/
|
||
if (i2c->status != I2C_XFER_IDLE)
|
||
twi_enable_irq(i2c->base_addr);
|
||
}
|
||
return IRQ_HANDLED;
|
||
}
|
||
|
||
static int sunxi_i2c_xfer_complete(struct sunxi_i2c *i2c, int code)
|
||
{
|
||
int ret = SUNXI_I2C_OK;
|
||
|
||
i2c->msg = NULL;
|
||
i2c->msg_num = 0;
|
||
i2c->msg_ptr = 0;
|
||
i2c->status = I2C_XFER_IDLE;
|
||
|
||
/* i2c->msg_idx store the information */
|
||
if (code == SUNXI_I2C_FAIL) {
|
||
I2C_ERR("[i2c%d] Maybe Logic Error, debug it!\n", i2c->bus_num);
|
||
i2c->msg_idx = code;
|
||
ret = SUNXI_I2C_FAIL;
|
||
} else if (code != SUNXI_I2C_OK) {
|
||
i2c->msg_idx = code;
|
||
ret = SUNXI_I2C_FAIL;
|
||
}
|
||
|
||
wake_up(&i2c->wait);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int
|
||
sunxi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
|
||
{
|
||
struct sunxi_i2c *i2c = (struct sunxi_i2c *)adap->algo_data;
|
||
int ret = SUNXI_I2C_FAIL;
|
||
int i = 0;
|
||
unsigned long diff_time;
|
||
struct timeval start, end;
|
||
|
||
do_gettimeofday(&start);
|
||
|
||
dprintk(DEBUG_INFO, "[i2c%d] num = %d\n", i2c->bus_num, num);
|
||
for (i = 0; i < num; i++) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] msgs[%d].addr = 0x%x\n",
|
||
i2c->bus_num, i, msgs[i].addr);
|
||
dprintk(DEBUG_INFO, "[i2c%d] msgs[%d].flags = 0x%x\n",
|
||
i2c->bus_num, i, msgs[i].flags);
|
||
dprintk(DEBUG_INFO, "[i2c%d] msgs[%d].len = %u\n",
|
||
i2c->bus_num, i, msgs[i].len);
|
||
}
|
||
|
||
ret = pm_runtime_get_sync(i2c->dev);
|
||
if (ret < 0)
|
||
goto out;
|
||
if (i2c->twi_drv_used) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] twi driver xfer\n", i2c->bus_num);
|
||
ret = sunxi_i2c_drv_do_xfer(i2c, msgs, num);
|
||
|
||
} else {
|
||
dprintk(DEBUG_INFO, "[i2c%d] twi engine xfer\n", i2c->bus_num);
|
||
for (i = 1; i <= adap->retries; i++) {
|
||
ret = sunxi_i2c_do_xfer(i2c, msgs, num);
|
||
|
||
if (ret != SUNXI_I2C_RETRY)
|
||
goto out;
|
||
|
||
dprintk(DEBUG_INFO,
|
||
"[i2c%d] Retrying transmission %d\n",
|
||
i2c->adap.nr, i);
|
||
udelay(100);
|
||
}
|
||
|
||
ret = -EREMOTEIO;
|
||
}
|
||
|
||
if (msgs->flags & I2C_M_RD)
|
||
dprintk(DEBUG_INFO, "[i2c%d] msgs->buf[0]=0x%x\n",
|
||
i2c->bus_num, (msgs->buf)[0]);
|
||
|
||
out:
|
||
pm_runtime_mark_last_busy(i2c->dev);
|
||
pm_runtime_put_autosuspend(i2c->dev);
|
||
|
||
do_gettimeofday(&end);
|
||
diff_time = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec
|
||
- start.tv_usec;
|
||
dprintk(DEBUG_INFO, "[i2c%d] diff_time = %ld us\n", i2c->bus_num,
|
||
diff_time);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int
|
||
sunxi_i2c_do_xfer(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num)
|
||
{
|
||
unsigned long timeout = 0;
|
||
int ret = SUNXI_I2C_FAIL;
|
||
unsigned long flags = 0;
|
||
|
||
twi_soft_reset(i2c->base_addr, TWI_SRST_REG, TWI_SRST_SRST);
|
||
udelay(100);
|
||
|
||
/* test the bus is free,already protect by the semaphore at DEV layer */
|
||
while (twi_query_irq_status(i2c->base_addr) != TWI_STAT_IDLE &&
|
||
twi_query_irq_status(i2c->base_addr) != TWI_STAT_BUS_ERR &&
|
||
twi_query_irq_status(i2c->base_addr) != TWI_STAT_ARBLOST_SLAR_ACK) {
|
||
dprintk(DEBUG_INFO, "[i2c%d] bus is busy, status = %x\n",
|
||
i2c->bus_num, twi_query_irq_status(i2c->base_addr));
|
||
if (twi_send_clk_9pulse(i2c->base_addr, i2c->bus_num) != SUNXI_I2C_OK) {
|
||
ret = SUNXI_I2C_RETRY;
|
||
goto out;
|
||
} else
|
||
break;
|
||
}
|
||
|
||
/* may conflict with xfer_complete */
|
||
spin_lock_irqsave(&i2c->lock, flags);
|
||
i2c->msg = msgs;
|
||
i2c->msg_num = num;
|
||
i2c->msg_ptr = 0;
|
||
i2c->msg_idx = 0;
|
||
i2c->status = I2C_XFER_START;
|
||
twi_enable_irq(i2c->base_addr); /* enable irq */
|
||
twi_disable_ack(i2c->base_addr); /* disabe ACK */
|
||
/* set the special function register,default:0. */
|
||
twi_set_efr(i2c->base_addr, 0);
|
||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||
|
||
/* START signal, needn't clear int flag */
|
||
ret = twi_start(i2c->base_addr, i2c->bus_num);
|
||
if (ret == SUNXI_I2C_FAIL) {
|
||
I2C_ERR("[i2c%d] twi_regulator: %s\n",
|
||
i2c->bus_num,
|
||
((struct sunxi_i2c_platform_data *)(i2c->adap.dev.parent->platform_data))->regulator_id);
|
||
twi_soft_reset(i2c->base_addr, TWI_SRST_REG, TWI_SRST_SRST);
|
||
twi_disable_irq(i2c->base_addr); /* disable irq */
|
||
i2c->status = I2C_XFER_IDLE;
|
||
ret = SUNXI_I2C_RETRY;
|
||
goto out;
|
||
}
|
||
|
||
i2c->status = I2C_XFER_RUNNING;
|
||
/* sleep and wait,do the transfer at interrupt handler,timeout = 5*HZ */
|
||
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0,
|
||
i2c->adap.timeout);
|
||
/* return code,if(msg_idx == num) succeed */
|
||
ret = i2c->msg_idx;
|
||
if (timeout == 0) {
|
||
I2C_ERR("[i2c%d] xfer timeout (dev addr:0x%x)\n",
|
||
i2c->bus_num, msgs->addr);
|
||
spin_lock_irqsave(&i2c->lock, flags);
|
||
i2c->msg = NULL;
|
||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||
ret = -ETIME;
|
||
} else if (ret != num) {
|
||
I2C_ERR("[i2c%d] incomplete xfer (status: 0x%x, dev addr: 0x%x)\n",
|
||
i2c->bus_num, ret, msgs->addr);
|
||
ret = -ECOMM;
|
||
}
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
static unsigned int sunxi_i2c_functionality(struct i2c_adapter *adap)
|
||
{
|
||
return I2C_FUNC_I2C|I2C_FUNC_10BIT_ADDR|I2C_FUNC_SMBUS_EMUL;
|
||
}
|
||
|
||
|
||
static const struct i2c_algorithm sunxi_i2c_algorithm = {
|
||
.master_xfer = sunxi_i2c_xfer,
|
||
.functionality = sunxi_i2c_functionality,
|
||
};
|
||
|
||
static int sunxi_i2c_clk_init(struct sunxi_i2c *i2c)
|
||
{
|
||
unsigned int apb_clk = 0;
|
||
|
||
if (clk_prepare_enable(i2c->mclk)) {
|
||
I2C_ERR("[i2c%d] enable apb_twi clock failed!\n", i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
/* sunxi_periph_reset_deassert(i2c->mclk); */
|
||
|
||
/* set twi module clock */
|
||
apb_clk = clk_get_rate(i2c->mclk);
|
||
if (apb_clk == 0) {
|
||
I2C_ERR("[i2c%d] get i2c source clock frequency failed!\n",
|
||
i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
/* enable twi engine or twi driver */
|
||
if (i2c->twi_drv_used) {
|
||
twi_set_clock(i2c, TWI_DRIVER_BUSC, 24000000, i2c->bus_freq,
|
||
TWI_DRV_CLK_M, TWI_DRV_CLK_N);
|
||
dprintk(DEBUG_INFO, "[i2c%d] set twi driver clock\n",
|
||
i2c->bus_num);
|
||
twi_enable(i2c->base_addr, TWI_DRIVER_CTRL, TWI_DRV_EN);
|
||
dprintk(DEBUG_INFO, "[i2c%d] twi driver enable \n",
|
||
i2c->bus_num);
|
||
} else {
|
||
#ifdef CONFIG_EVB_PLATFORM
|
||
twi_set_clock(i2c, TWI_CLK_REG, apb_clk, i2c->bus_freq,
|
||
TWI_CLK_DIV_M, TWI_CLK_DIV_N);
|
||
#else
|
||
twi_set_clock(i2c, TWI_CLK_REG, 24000000, i2c->bus_freq,
|
||
TWI_CLK_DIV_M, TWI_CLK_DIV_N);
|
||
#endif
|
||
dprintk(DEBUG_INFO, "[i2c%d] set twi engine clock\n",
|
||
i2c->bus_num);
|
||
twi_enable(i2c->base_addr, TWI_CTL_REG, TWI_CTL_BUSEN);
|
||
dprintk(DEBUG_INFO, "[i2c%d] twi engine enable \n",
|
||
i2c->bus_num);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_clk_exit(struct sunxi_i2c *i2c)
|
||
{
|
||
/* disable twi bus */
|
||
if (i2c->twi_drv_used) {
|
||
twi_disable(i2c->base_addr, TWI_DRIVER_CTRL, TWI_DRV_EN);
|
||
dprintk(DEBUG_INFO, "[i2c%d] disable twi driver\n",
|
||
i2c->bus_num);
|
||
} else
|
||
twi_disable(i2c->base_addr, TWI_CTL_REG, TWI_CTL_BUSEN);
|
||
|
||
/* disable clk */
|
||
if (!IS_ERR_OR_NULL(i2c->mclk))
|
||
clk_disable_unprepare(i2c->mclk);
|
||
else {
|
||
clk_disable_unprepare(i2c->mclk);
|
||
I2C_ERR("[i2c%d] i2c mclk handle is invalid, just return!\n",
|
||
i2c->bus_num);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_hw_init(struct sunxi_i2c *i2c,
|
||
struct sunxi_i2c_platform_data *pdata)
|
||
{
|
||
int ret = 0;
|
||
|
||
ret = twi_regulator_request(pdata);
|
||
if (ret < 0) {
|
||
I2C_ERR("[i2c%d] request regulator failed!\n", i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
ret = twi_request_gpio(i2c);
|
||
if (ret < 0) {
|
||
I2C_ERR("[i2c%d] request i2c gpio failed!\n", i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
if (sunxi_i2c_clk_init(i2c)) {
|
||
I2C_ERR("[i2c%d] init i2c clock failed!\n", i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
if (!(i2c->twi_drv_used))
|
||
twi_soft_reset(i2c->base_addr, TWI_SRST_REG, TWI_SRST_SRST);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void sunxi_i2c_hw_exit(struct sunxi_i2c *i2c,
|
||
struct sunxi_i2c_platform_data *pdata)
|
||
{
|
||
if (sunxi_i2c_clk_exit(i2c)) {
|
||
I2C_ERR("[i2c%d] exit i2c clock failed!\n", i2c->bus_num);
|
||
return;
|
||
}
|
||
twi_release_gpio(i2c);
|
||
|
||
twi_regulator_release(pdata);
|
||
}
|
||
|
||
static void sunxi_i2c_release(struct device *dev)
|
||
{
|
||
I2C_ENTER();
|
||
}
|
||
|
||
static ssize_t sunxi_i2c_info_show(struct device *dev,
|
||
struct device_attribute *attr, char *buf)
|
||
{
|
||
struct platform_device *pdev = container_of(dev,
|
||
struct platform_device, dev);
|
||
struct sunxi_i2c_platform_data *pdata = dev->platform_data;
|
||
|
||
return snprintf(buf, PAGE_SIZE,
|
||
"pdev->id = %d\n"
|
||
"pdev->name = %s\n"
|
||
"pdev->num_resources = %u\n"
|
||
"pdev->resource.mem = [%pa, %pa]\n"
|
||
"pdev->resource.irq = %pa\n"
|
||
"pdev->dev.platform_data.bus_num = %d\n"
|
||
"pdev->dev.platform_data.freqency = %u\n"
|
||
"pdev->dev.platform_data.regulator= 0x%p\n"
|
||
"pdev->dev.platform_data.regulator_id = %s\n",
|
||
pdev->id, pdev->name, pdev->num_resources,
|
||
&pdev->resource[0].start, &pdev->resource[0].end,
|
||
&pdev->resource[1].start, pdata->bus_num, pdata->frequency,
|
||
pdata->regulator, pdata->regulator_id);
|
||
}
|
||
static struct device_attribute sunxi_i2c_info_attr =
|
||
__ATTR(info, S_IRUGO, sunxi_i2c_info_show, NULL);
|
||
|
||
static ssize_t sunxi_i2c_status_show(struct device *dev,
|
||
struct device_attribute *attr, char *buf)
|
||
{
|
||
struct sunxi_i2c *i2c = dev_get_drvdata(dev);
|
||
static char const *i2c_status[] = {"Unknown", "Idle", "Start",
|
||
"Unknown", "Running"};
|
||
|
||
if (i2c == NULL)
|
||
return snprintf(buf, PAGE_SIZE, "%s\n", "sunxi_i2c is NULL!");
|
||
|
||
return snprintf(buf, PAGE_SIZE,
|
||
"i2c->bus_num = %d\n"
|
||
"i2c->status = [%u] %s\n"
|
||
"i2c->msg_num = %u, ->msg_idx = %u, ->msg_ptr = %u\n"
|
||
"i2c->bus_freq = %u\n"
|
||
"i2c->irq = %d\n"
|
||
"i2c->debug_state = %u\n"
|
||
"i2c->base_addr = 0x%p, the TWI control register:\n"
|
||
"[ADDR] 0x%02x = 0x%08x, [XADDR] 0x%02x = 0x%08x\n"
|
||
"[DATA] 0x%02x = 0x%08x, [CNTR] 0x%02x = 0x%08x\n"
|
||
"[STAT] 0x%02x = 0x%08x, [CCR] 0x%02x = 0x%08x\n"
|
||
"[SRST] 0x%02x = 0x%08x, [EFR] 0x%02x = 0x%08x\n"
|
||
"[LCR] 0x%02x = 0x%08x\n",
|
||
i2c->bus_num, i2c->status, i2c_status[i2c->status],
|
||
i2c->msg_num, i2c->msg_idx, i2c->msg_ptr,
|
||
i2c->bus_freq, i2c->irq, i2c->debug_state,
|
||
i2c->base_addr,
|
||
TWI_ADDR_REG, readl(i2c->base_addr + TWI_ADDR_REG),
|
||
TWI_XADDR_REG, readl(i2c->base_addr + TWI_XADDR_REG),
|
||
TWI_DATA_REG, readl(i2c->base_addr + TWI_DATA_REG),
|
||
TWI_CTL_REG, readl(i2c->base_addr + TWI_CTL_REG),
|
||
TWI_STAT_REG, readl(i2c->base_addr + TWI_STAT_REG),
|
||
TWI_CLK_REG, readl(i2c->base_addr + TWI_CLK_REG),
|
||
TWI_SRST_REG, readl(i2c->base_addr + TWI_SRST_REG),
|
||
TWI_EFR_REG, readl(i2c->base_addr + TWI_EFR_REG),
|
||
TWI_LCR_REG, readl(i2c->base_addr + TWI_LCR_REG));
|
||
}
|
||
static struct device_attribute sunxi_i2c_status_attr =
|
||
__ATTR(status, S_IRUGO, sunxi_i2c_status_show, NULL);
|
||
|
||
static void sunxi_i2c_sysfs(struct platform_device *_pdev)
|
||
{
|
||
device_create_file(&_pdev->dev, &sunxi_i2c_info_attr);
|
||
device_create_file(&_pdev->dev, &sunxi_i2c_status_attr);
|
||
}
|
||
|
||
static int sunxi_i2c_probe(struct platform_device *pdev)
|
||
{
|
||
struct device_node *np = pdev->dev.of_node;
|
||
struct sunxi_i2c *i2c = NULL;
|
||
struct resource *mem_res = NULL;
|
||
struct sunxi_i2c_platform_data *pdata = NULL;
|
||
int ret, irq;
|
||
unsigned long int_flag = 0;
|
||
const char *str_vcc_twi;
|
||
dma_addr_t phy_addr;
|
||
|
||
if (np == NULL) {
|
||
I2C_ERR("I2C failed to get of node\n");
|
||
return -ENODEV;
|
||
}
|
||
|
||
i2c = kzalloc(sizeof(struct sunxi_i2c), GFP_KERNEL);
|
||
if (!i2c)
|
||
return -ENOMEM;
|
||
|
||
pdata = kzalloc(sizeof(struct sunxi_i2c_platform_data), GFP_KERNEL);
|
||
if (pdata == NULL) {
|
||
kfree(i2c);
|
||
return -ENOMEM;
|
||
}
|
||
i2c->dev = &pdev->dev;
|
||
pdev->dev.platform_data = pdata;
|
||
pdev->dev.driver_data = i2c;
|
||
|
||
pdev->id = of_alias_get_id(np, "twi");
|
||
if (pdev->id < 0) {
|
||
I2C_ERR("I2C failed to get alias id\n");
|
||
ret = -EINVAL;
|
||
goto emem;
|
||
}
|
||
pdata->bus_num = pdev->id;
|
||
|
||
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
if (mem_res == NULL) {
|
||
I2C_ERR("[i2c%d] failed to get MEM res\n", pdev->id);
|
||
ret = -ENXIO;
|
||
goto emem;
|
||
}
|
||
|
||
if (!request_mem_region(mem_res->start, resource_size(mem_res),
|
||
mem_res->name)) {
|
||
I2C_ERR("[i2c%d] failed to request mem region\n", pdev->id);
|
||
ret = -EINVAL;
|
||
goto emem;
|
||
}
|
||
|
||
i2c->base_addr = ioremap(mem_res->start, resource_size(mem_res));
|
||
if (!i2c->base_addr) {
|
||
ret = -EIO;
|
||
goto eiomap;
|
||
}
|
||
|
||
irq = platform_get_irq(pdev, 0);
|
||
if (irq < 0) {
|
||
I2C_ERR("[i2c%d] failed to get irq\n", pdev->id);
|
||
ret = -EINVAL;
|
||
goto eiomap;
|
||
}
|
||
|
||
ret = of_property_read_u32(np, "clock-frequency", &pdata->frequency);
|
||
if (ret) {
|
||
I2C_ERR("[i2c%d] failed to get clock frequency\n", pdev->id);
|
||
ret = -EINVAL;
|
||
goto eiomap;
|
||
}
|
||
|
||
ret = of_property_read_string(np, "twi_regulator",
|
||
&str_vcc_twi);
|
||
if (ret)
|
||
I2C_ERR("[i2c%d] failed to get regulator id\n", pdev->id);
|
||
else {
|
||
if (strlen(str_vcc_twi) >= sizeof(pdata->regulator_id))
|
||
I2C_ERR("[i2c%d] illegal regulator id\n", pdev->id);
|
||
else {
|
||
strcpy(pdata->regulator_id, str_vcc_twi);
|
||
pr_info("[i2c%d] twi_regulator: %s\n", pdev->id,
|
||
pdata->regulator_id);
|
||
}
|
||
}
|
||
|
||
pdev->dev.release = sunxi_i2c_release;
|
||
i2c->adap.owner = THIS_MODULE;
|
||
i2c->adap.nr = pdata->bus_num;
|
||
i2c->adap.retries = 3;
|
||
i2c->adap.timeout = 5*HZ;
|
||
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
|
||
i2c->bus_freq = pdata->frequency;
|
||
i2c->irq = irq;
|
||
i2c->bus_num = pdata->bus_num;
|
||
i2c->status = I2C_XFER_IDLE;
|
||
snprintf(i2c->adap.name, sizeof(i2c->adap.name),
|
||
SUNXI_TWI_DEV_NAME"%u", i2c->adap.nr);
|
||
pdev->dev.init_name = i2c->adap.name;
|
||
|
||
spin_lock_init(&i2c->lock);
|
||
init_waitqueue_head(&i2c->wait);
|
||
|
||
i2c->mclk = of_clk_get(np, 0);
|
||
if (IS_ERR_OR_NULL(i2c->mclk)) {
|
||
I2C_ERR("[i2c%d] request TWI clock failed\n", i2c->bus_num);
|
||
ret = -EIO;
|
||
goto eremap;
|
||
}
|
||
|
||
i2c->adap.algo = &sunxi_i2c_algorithm;
|
||
|
||
#ifndef CONFIG_SUNXI_ARISC
|
||
/* SUNXI_ARISC will only use twi0, enable gic interrupt when suspend */
|
||
if (i2c->adap.nr == 0)
|
||
int_flag |= IRQF_NO_SUSPEND;
|
||
#endif
|
||
ret = request_irq(irq, sunxi_i2c_handler, int_flag,
|
||
i2c->adap.name, i2c);
|
||
if (ret) {
|
||
I2C_ERR("[i2c%d] requeset irq failed!\n", i2c->bus_num);
|
||
goto ereqirq;
|
||
}
|
||
|
||
i2c->adap.algo_data = i2c;
|
||
i2c->adap.dev.parent = &pdev->dev;
|
||
i2c->adap.dev.of_node = pdev->dev.of_node;
|
||
twi_used_mask |= SUNXI_TWI_CHAN_MASK(pdev->id);
|
||
|
||
#if defined(CONFIG_ARCH_SUN8IW18)
|
||
i2c->twi_drv_used = of_property_read_bool(np, "twi_drv_used");
|
||
dprintk(DEBUG_INIT, "[i2c%d] twi_drv_used = %d\n", i2c->bus_num,
|
||
i2c->twi_drv_used);
|
||
#endif
|
||
|
||
if (sunxi_i2c_hw_init(i2c, pdata)) {
|
||
ret = -EIO;
|
||
goto ehwinit;
|
||
}
|
||
pm_runtime_enable(i2c->dev);
|
||
pm_runtime_set_autosuspend_delay(i2c->dev, AUTOSUSPEND_TIMEOUT);
|
||
pm_runtime_use_autosuspend(i2c->dev);
|
||
pm_runtime_set_active(i2c->dev);
|
||
ret = i2c_add_numbered_adapter(&i2c->adap);
|
||
if (ret < 0) {
|
||
I2C_ERR("[i2c%d] failed to add adapter\n", i2c->bus_num);
|
||
pm_runtime_set_suspended(i2c->dev);
|
||
pm_runtime_disable(i2c->dev);
|
||
goto eadapt;
|
||
}
|
||
|
||
phy_addr = (dma_addr_t)mem_res->start;
|
||
|
||
/* Init DMA config if supported */
|
||
if (i2c->twi_drv_used)
|
||
sunxi_i2c_dma_request(i2c, phy_addr);
|
||
|
||
i2c->pdev = pdev;
|
||
|
||
platform_set_drvdata(pdev, i2c);
|
||
|
||
sunxi_i2c_sysfs(pdev);
|
||
|
||
dprintk(DEBUG_INIT, "I2C: %s: sunxi I2C adapter\n",
|
||
dev_name(&i2c->adap.dev));
|
||
dprintk(DEBUG_INFO, "TWI_CTL 0x%p: 0x%08x\n", i2c->base_addr + 0x0c,
|
||
readl(i2c->base_addr + 0x0c));
|
||
dprintk(DEBUG_INFO, "TWI_STAT 0x%p: 0x%08x\n", i2c->base_addr + 0x10,
|
||
readl(i2c->base_addr + 0x10));
|
||
dprintk(DEBUG_INFO, "TWI_CLK 0x%p: 0x%08x\n", i2c->base_addr + 0x14,
|
||
readl(i2c->base_addr + 0x14));
|
||
dprintk(DEBUG_INFO, "TWI_SRST 0x%p: 0x%08x\n", i2c->base_addr + 0x18,
|
||
readl(i2c->base_addr + 0x18));
|
||
dprintk(DEBUG_INFO, "TWI_EFR 0x%p: 0x%08x\n", i2c->base_addr + 0x1c,
|
||
readl(i2c->base_addr + 0x1c));
|
||
return 0;
|
||
|
||
eadapt:
|
||
clk_disable_unprepare(i2c->mclk);
|
||
|
||
ehwinit:
|
||
free_irq(irq, i2c);
|
||
|
||
ereqirq:
|
||
iounmap(i2c->base_addr);
|
||
|
||
eremap:
|
||
if (!IS_ERR_OR_NULL(i2c->mclk)) {
|
||
clk_put(i2c->mclk);
|
||
i2c->mclk = NULL;
|
||
}
|
||
|
||
eiomap:
|
||
release_mem_region(mem_res->start, resource_size(mem_res));
|
||
|
||
emem:
|
||
kfree(pdata);
|
||
kfree(i2c);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int sunxi_i2c_remove(struct platform_device *pdev)
|
||
{
|
||
struct sunxi_i2c *i2c = platform_get_drvdata(pdev);
|
||
|
||
dprintk(DEBUG_INIT, "[i2c.%d] remove ...\n", i2c->bus_num);
|
||
|
||
if (i2c->dma_tx)
|
||
sunxi_i2c_dma_free(i2c->dma_tx);
|
||
if (i2c->dma_rx)
|
||
sunxi_i2c_dma_free(i2c->dma_rx);
|
||
|
||
platform_set_drvdata(pdev, NULL);
|
||
i2c_del_adapter(&i2c->adap);
|
||
free_irq(i2c->irq, i2c);
|
||
pm_runtime_set_suspended(i2c->dev);
|
||
pm_runtime_disable(i2c->dev);
|
||
/* disable clock and release gpio */
|
||
sunxi_i2c_hw_exit(i2c, pdev->dev.platform_data);
|
||
if (!IS_ERR_OR_NULL(i2c->mclk)) {
|
||
clk_put(i2c->mclk);
|
||
i2c->mclk = NULL;
|
||
} else {
|
||
I2C_ERR("i2c mclk handle is invalid, just return!\n");
|
||
return -1;
|
||
}
|
||
|
||
iounmap(i2c->base_addr);
|
||
kfree(i2c);
|
||
kfree(pdev->dev.platform_data);
|
||
|
||
I2C_EXIT();
|
||
|
||
return 0;
|
||
}
|
||
|
||
#ifdef CONFIG_PM
|
||
static int twi_regulator_enable(struct sunxi_i2c_platform_data *pdata)
|
||
{
|
||
if (pdata->regulator == NULL)
|
||
return 0;
|
||
|
||
if (regulator_enable(pdata->regulator) != 0) {
|
||
I2C_ERR("[i2c%d] enable regulator %s failed!\n",
|
||
pdata->bus_num, pdata->regulator_id);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int twi_regulator_disable(struct sunxi_i2c_platform_data *pdata)
|
||
{
|
||
if (pdata->regulator == NULL)
|
||
return 0;
|
||
|
||
if (regulator_disable(pdata->regulator) != 0) {
|
||
I2C_ERR("[i2c%d] enable regulator %s failed!\n",
|
||
pdata->bus_num, pdata->regulator_id);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_runtime_suspend(struct device *dev)
|
||
{
|
||
struct platform_device *pdev = to_platform_device(dev);
|
||
struct sunxi_i2c *i2c = platform_get_drvdata(pdev);
|
||
|
||
#ifndef CONFIG_SUNXI_ARISC
|
||
/* SUNXI_ARISC will only use twi0 */
|
||
if (i2c->adap.nr == 0)
|
||
return 0;
|
||
#endif
|
||
|
||
if (sunxi_i2c_clk_exit(i2c)) {
|
||
I2C_ERR("[i2c%d] suspend failed..\n", i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
twi_select_gpio_state(i2c->pctrl, PINCTRL_STATE_SLEEP, i2c->bus_num);
|
||
twi_regulator_disable(dev->platform_data);
|
||
dprintk(DEBUG_SUSPEND, "[i2c%d] finish\n", i2c->bus_num);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_runtime_resume(struct device *dev)
|
||
{
|
||
struct platform_device *pdev = to_platform_device(dev);
|
||
struct sunxi_i2c *i2c = platform_get_drvdata(pdev);
|
||
|
||
#ifndef CONFIG_SUNXI_ARISC
|
||
/* SUNXI_ARISC will only use twi0 */
|
||
if (i2c->adap.nr == 0)
|
||
return 0;
|
||
#endif
|
||
|
||
twi_regulator_enable(dev->platform_data);
|
||
twi_select_gpio_state(i2c->pctrl, PINCTRL_STATE_DEFAULT, i2c->bus_num);
|
||
|
||
if (sunxi_i2c_clk_init(i2c)) {
|
||
I2C_ERR("[i2c%d] resume failed..\n", i2c->bus_num);
|
||
return -1;
|
||
}
|
||
|
||
if (!(i2c->twi_drv_used))
|
||
twi_soft_reset(i2c->base_addr, TWI_SRST_REG, TWI_SRST_SRST);
|
||
|
||
dprintk(DEBUG_SUSPEND, "[i2c%d] finish\n", i2c->bus_num);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_suspend_noirq(struct device *dev)
|
||
{
|
||
struct platform_device *pdev = to_platform_device(dev);
|
||
struct sunxi_i2c *i2c = platform_get_drvdata(pdev);
|
||
|
||
if (!pm_runtime_status_suspended(dev)) {
|
||
sunxi_i2c_runtime_suspend(dev);
|
||
dprintk(DEBUG_SUSPEND, "[i2c%d] finish\n", i2c->bus_num);
|
||
|
||
pm_runtime_disable(dev);
|
||
pm_runtime_set_suspended(dev);
|
||
pm_runtime_enable(dev);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int sunxi_i2c_resume_noirq(struct device *dev)
|
||
{
|
||
struct platform_device *pdev = to_platform_device(dev);
|
||
struct sunxi_i2c *i2c = platform_get_drvdata(pdev);
|
||
|
||
if (pm_runtime_active(dev)) {
|
||
sunxi_i2c_runtime_resume(dev);
|
||
dprintk(DEBUG_SUSPEND, "[i2c%d] finish\n", i2c->bus_num);
|
||
|
||
pm_runtime_disable(dev);
|
||
pm_runtime_set_active(dev);
|
||
pm_runtime_enable(dev);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static const struct dev_pm_ops sunxi_i2c_dev_pm_ops = {
|
||
.suspend_noirq = sunxi_i2c_suspend_noirq,
|
||
.resume_noirq = sunxi_i2c_resume_noirq,
|
||
.runtime_suspend = sunxi_i2c_runtime_suspend,
|
||
.runtime_resume = sunxi_i2c_runtime_resume,
|
||
};
|
||
|
||
#define SUNXI_I2C_DEV_PM_OPS (&sunxi_i2c_dev_pm_ops)
|
||
#else
|
||
#define SUNXI_I2C_DEV_PM_OPS NULL
|
||
#endif
|
||
|
||
static const struct of_device_id sunxi_i2c_match[] = {
|
||
{ .compatible = "allwinner,sun8i-twi", },
|
||
{ .compatible = "allwinner,sun50i-twi", },
|
||
{},
|
||
};
|
||
MODULE_DEVICE_TABLE(of, sunxi_i2c_match);
|
||
|
||
static struct platform_driver sunxi_i2c_driver = {
|
||
.probe = sunxi_i2c_probe,
|
||
.remove = sunxi_i2c_remove,
|
||
.driver = {
|
||
.name = SUNXI_TWI_DEV_NAME,
|
||
.owner = THIS_MODULE,
|
||
.pm = SUNXI_I2C_DEV_PM_OPS,
|
||
.of_match_table = sunxi_i2c_match,
|
||
},
|
||
};
|
||
|
||
static int __init sunxi_i2c_adap_init(void)
|
||
{
|
||
dprintk(DEBUG_INIT, "Sunxi I2C adapt init\n");
|
||
|
||
return platform_driver_register(&sunxi_i2c_driver);
|
||
}
|
||
|
||
static void __exit sunxi_i2c_adap_exit(void)
|
||
{
|
||
|
||
I2C_ENTER();
|
||
|
||
platform_driver_unregister(&sunxi_i2c_driver);
|
||
}
|
||
|
||
fs_initcall(sunxi_i2c_adap_init);
|
||
module_exit(sunxi_i2c_adap_exit);
|
||
module_param_named(debug, debug_mask, int, 0664);
|
||
|
||
MODULE_LICENSE("GPL");
|
||
MODULE_ALIAS("platform:i2c-sunxi");
|
||
MODULE_DESCRIPTION("SUNXI I2C Bus Driver");
|
||
MODULE_AUTHOR("pannan");
|