2014-10-07 05:04:12 +04:00
/*
* Copyright ( c ) 2014 Linaro Ltd .
* Copyright ( c ) 2014 Hisilicon Limited .
*
* 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 .
*
* Now only support 7 bit address .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/io.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
/* Register Map */
# define HIX5I2C_CTRL 0x00
# define HIX5I2C_COM 0x04
# define HIX5I2C_ICR 0x08
# define HIX5I2C_SR 0x0c
# define HIX5I2C_SCL_H 0x10
# define HIX5I2C_SCL_L 0x14
# define HIX5I2C_TXR 0x18
# define HIX5I2C_RXR 0x1c
/* I2C_CTRL_REG */
# define I2C_ENABLE BIT(8)
# define I2C_UNMASK_TOTAL BIT(7)
# define I2C_UNMASK_START BIT(6)
# define I2C_UNMASK_END BIT(5)
# define I2C_UNMASK_SEND BIT(4)
# define I2C_UNMASK_RECEIVE BIT(3)
# define I2C_UNMASK_ACK BIT(2)
# define I2C_UNMASK_ARBITRATE BIT(1)
# define I2C_UNMASK_OVER BIT(0)
# define I2C_UNMASK_ALL (I2C_UNMASK_ACK | I2C_UNMASK_OVER)
/* I2C_COM_REG */
# define I2C_NO_ACK BIT(4)
# define I2C_START BIT(3)
# define I2C_READ BIT(2)
# define I2C_WRITE BIT(1)
# define I2C_STOP BIT(0)
/* I2C_ICR_REG */
# define I2C_CLEAR_START BIT(6)
# define I2C_CLEAR_END BIT(5)
# define I2C_CLEAR_SEND BIT(4)
# define I2C_CLEAR_RECEIVE BIT(3)
# define I2C_CLEAR_ACK BIT(2)
# define I2C_CLEAR_ARBITRATE BIT(1)
# define I2C_CLEAR_OVER BIT(0)
# define I2C_CLEAR_ALL (I2C_CLEAR_START | I2C_CLEAR_END | \
I2C_CLEAR_SEND | I2C_CLEAR_RECEIVE | \
I2C_CLEAR_ACK | I2C_CLEAR_ARBITRATE | \
I2C_CLEAR_OVER )
/* I2C_SR_REG */
# define I2C_BUSY BIT(7)
# define I2C_START_INTR BIT(6)
# define I2C_END_INTR BIT(5)
# define I2C_SEND_INTR BIT(4)
# define I2C_RECEIVE_INTR BIT(3)
# define I2C_ACK_INTR BIT(2)
# define I2C_ARBITRATE_INTR BIT(1)
# define I2C_OVER_INTR BIT(0)
# define HIX5I2C_MAX_FREQ 400000 /* 400k */
# define HIX5I2C_READ_OPERATION 0x01
enum hix5hd2_i2c_state {
HIX5I2C_STAT_RW_ERR = - 1 ,
HIX5I2C_STAT_INIT ,
HIX5I2C_STAT_RW ,
HIX5I2C_STAT_SND_STOP ,
HIX5I2C_STAT_RW_SUCCESS ,
} ;
struct hix5hd2_i2c_priv {
struct i2c_adapter adap ;
struct i2c_msg * msg ;
struct completion msg_complete ;
unsigned int msg_idx ;
unsigned int msg_len ;
int stop ;
void __iomem * regs ;
struct clk * clk ;
struct device * dev ;
spinlock_t lock ; /* IRQ synchronization */
int err ;
unsigned int freq ;
enum hix5hd2_i2c_state state ;
} ;
static u32 hix5hd2_i2c_clr_pend_irq ( struct hix5hd2_i2c_priv * priv )
{
u32 val = readl_relaxed ( priv - > regs + HIX5I2C_SR ) ;
writel_relaxed ( val , priv - > regs + HIX5I2C_ICR ) ;
return val ;
}
static void hix5hd2_i2c_clr_all_irq ( struct hix5hd2_i2c_priv * priv )
{
writel_relaxed ( I2C_CLEAR_ALL , priv - > regs + HIX5I2C_ICR ) ;
}
static void hix5hd2_i2c_disable_irq ( struct hix5hd2_i2c_priv * priv )
{
writel_relaxed ( 0 , priv - > regs + HIX5I2C_CTRL ) ;
}
static void hix5hd2_i2c_enable_irq ( struct hix5hd2_i2c_priv * priv )
{
writel_relaxed ( I2C_ENABLE | I2C_UNMASK_TOTAL | I2C_UNMASK_ALL ,
priv - > regs + HIX5I2C_CTRL ) ;
}
static void hix5hd2_i2c_drv_setrate ( struct hix5hd2_i2c_priv * priv )
{
u32 rate , val ;
u32 scl , sysclock ;
/* close all i2c interrupt */
val = readl_relaxed ( priv - > regs + HIX5I2C_CTRL ) ;
writel_relaxed ( val & ( ~ I2C_UNMASK_TOTAL ) , priv - > regs + HIX5I2C_CTRL ) ;
rate = priv - > freq ;
sysclock = clk_get_rate ( priv - > clk ) ;
scl = ( sysclock / ( rate * 2 ) ) / 2 - 1 ;
writel_relaxed ( scl , priv - > regs + HIX5I2C_SCL_H ) ;
writel_relaxed ( scl , priv - > regs + HIX5I2C_SCL_L ) ;
/* restore original interrupt*/
writel_relaxed ( val , priv - > regs + HIX5I2C_CTRL ) ;
dev_dbg ( priv - > dev , " %s: sysclock=%d, rate=%d, scl=%d \n " ,
__func__ , sysclock , rate , scl ) ;
}
static void hix5hd2_i2c_init ( struct hix5hd2_i2c_priv * priv )
{
hix5hd2_i2c_disable_irq ( priv ) ;
hix5hd2_i2c_drv_setrate ( priv ) ;
hix5hd2_i2c_clr_all_irq ( priv ) ;
hix5hd2_i2c_enable_irq ( priv ) ;
}
static void hix5hd2_i2c_reset ( struct hix5hd2_i2c_priv * priv )
{
clk_disable_unprepare ( priv - > clk ) ;
msleep ( 20 ) ;
clk_prepare_enable ( priv - > clk ) ;
hix5hd2_i2c_init ( priv ) ;
}
static int hix5hd2_i2c_wait_bus_idle ( struct hix5hd2_i2c_priv * priv )
{
unsigned long stop_time ;
u32 int_status ;
/* wait for 100 milli seconds for the bus to be idle */
stop_time = jiffies + msecs_to_jiffies ( 100 ) ;
do {
int_status = hix5hd2_i2c_clr_pend_irq ( priv ) ;
if ( ! ( int_status & I2C_BUSY ) )
return 0 ;
usleep_range ( 50 , 200 ) ;
} while ( time_before ( jiffies , stop_time ) ) ;
return - EBUSY ;
}
static void hix5hd2_rw_over ( struct hix5hd2_i2c_priv * priv )
{
if ( priv - > state = = HIX5I2C_STAT_SND_STOP )
dev_dbg ( priv - > dev , " %s: rw and send stop over \n " , __func__ ) ;
else
dev_dbg ( priv - > dev , " %s: have not data to send \n " , __func__ ) ;
priv - > state = HIX5I2C_STAT_RW_SUCCESS ;
priv - > err = 0 ;
}
static void hix5hd2_rw_handle_stop ( struct hix5hd2_i2c_priv * priv )
{
if ( priv - > stop ) {
priv - > state = HIX5I2C_STAT_SND_STOP ;
writel_relaxed ( I2C_STOP , priv - > regs + HIX5I2C_COM ) ;
} else {
hix5hd2_rw_over ( priv ) ;
}
}
static void hix5hd2_read_handle ( struct hix5hd2_i2c_priv * priv )
{
if ( priv - > msg_len = = 1 ) {
/* the last byte don't need send ACK */
writel_relaxed ( I2C_READ | I2C_NO_ACK , priv - > regs + HIX5I2C_COM ) ;
} else if ( priv - > msg_len > 1 ) {
/* if i2c master receive data will send ACK */
writel_relaxed ( I2C_READ , priv - > regs + HIX5I2C_COM ) ;
} else {
hix5hd2_rw_handle_stop ( priv ) ;
}
}
static void hix5hd2_write_handle ( struct hix5hd2_i2c_priv * priv )
{
u8 data ;
if ( priv - > msg_len > 0 ) {
data = priv - > msg - > buf [ priv - > msg_idx + + ] ;
writel_relaxed ( data , priv - > regs + HIX5I2C_TXR ) ;
writel_relaxed ( I2C_WRITE , priv - > regs + HIX5I2C_COM ) ;
} else {
hix5hd2_rw_handle_stop ( priv ) ;
}
}
static int hix5hd2_rw_preprocess ( struct hix5hd2_i2c_priv * priv )
{
u8 data ;
if ( priv - > state = = HIX5I2C_STAT_INIT ) {
priv - > state = HIX5I2C_STAT_RW ;
} else if ( priv - > state = = HIX5I2C_STAT_RW ) {
if ( priv - > msg - > flags & I2C_M_RD ) {
data = readl_relaxed ( priv - > regs + HIX5I2C_RXR ) ;
priv - > msg - > buf [ priv - > msg_idx + + ] = data ;
}
priv - > msg_len - - ;
} else {
dev_dbg ( priv - > dev , " %s: error: priv->state = %d, msg_len = %d \n " ,
__func__ , priv - > state , priv - > msg_len ) ;
return - EAGAIN ;
}
return 0 ;
}
static irqreturn_t hix5hd2_i2c_irq ( int irqno , void * dev_id )
{
struct hix5hd2_i2c_priv * priv = dev_id ;
u32 int_status ;
int ret ;
spin_lock ( & priv - > lock ) ;
int_status = hix5hd2_i2c_clr_pend_irq ( priv ) ;
/* handle error */
if ( int_status & I2C_ARBITRATE_INTR ) {
/* bus error */
dev_dbg ( priv - > dev , " ARB bus loss \n " ) ;
priv - > err = - EAGAIN ;
priv - > state = HIX5I2C_STAT_RW_ERR ;
goto stop ;
} else if ( int_status & I2C_ACK_INTR ) {
/* ack error */
dev_dbg ( priv - > dev , " No ACK from device \n " ) ;
priv - > err = - ENXIO ;
priv - > state = HIX5I2C_STAT_RW_ERR ;
goto stop ;
}
if ( int_status & I2C_OVER_INTR ) {
if ( priv - > msg_len > 0 ) {
ret = hix5hd2_rw_preprocess ( priv ) ;
if ( ret ) {
priv - > err = ret ;
priv - > state = HIX5I2C_STAT_RW_ERR ;
goto stop ;
}
if ( priv - > msg - > flags & I2C_M_RD )
hix5hd2_read_handle ( priv ) ;
else
hix5hd2_write_handle ( priv ) ;
} else {
hix5hd2_rw_over ( priv ) ;
}
}
stop :
if ( ( priv - > state = = HIX5I2C_STAT_RW_SUCCESS & &
priv - > msg - > len = = priv - > msg_idx ) | |
( priv - > state = = HIX5I2C_STAT_RW_ERR ) ) {
hix5hd2_i2c_disable_irq ( priv ) ;
hix5hd2_i2c_clr_pend_irq ( priv ) ;
complete ( & priv - > msg_complete ) ;
}
spin_unlock ( & priv - > lock ) ;
return IRQ_HANDLED ;
}
static void hix5hd2_i2c_message_start ( struct hix5hd2_i2c_priv * priv , int stop )
{
unsigned long flags ;
spin_lock_irqsave ( & priv - > lock , flags ) ;
hix5hd2_i2c_clr_all_irq ( priv ) ;
hix5hd2_i2c_enable_irq ( priv ) ;
if ( priv - > msg - > flags & I2C_M_RD )
writel_relaxed ( ( priv - > msg - > addr < < 1 ) | HIX5I2C_READ_OPERATION ,
priv - > regs + HIX5I2C_TXR ) ;
else
writel_relaxed ( priv - > msg - > addr < < 1 ,
priv - > regs + HIX5I2C_TXR ) ;
writel_relaxed ( I2C_WRITE | I2C_START , priv - > regs + HIX5I2C_COM ) ;
spin_unlock_irqrestore ( & priv - > lock , flags ) ;
}
static int hix5hd2_i2c_xfer_msg ( struct hix5hd2_i2c_priv * priv ,
struct i2c_msg * msgs , int stop )
{
unsigned long timeout ;
int ret ;
priv - > msg = msgs ;
priv - > msg_idx = 0 ;
priv - > msg_len = priv - > msg - > len ;
priv - > stop = stop ;
priv - > err = 0 ;
priv - > state = HIX5I2C_STAT_INIT ;
reinit_completion ( & priv - > msg_complete ) ;
hix5hd2_i2c_message_start ( priv , stop ) ;
timeout = wait_for_completion_timeout ( & priv - > msg_complete ,
priv - > adap . timeout ) ;
if ( timeout = = 0 ) {
priv - > state = HIX5I2C_STAT_RW_ERR ;
priv - > err = - ETIMEDOUT ;
dev_warn ( priv - > dev , " %s timeout=%d \n " ,
msgs - > flags & I2C_M_RD ? " rx " : " tx " ,
priv - > adap . timeout ) ;
}
ret = priv - > state ;
/*
* If this is the last message to be transfered ( stop = = 1 )
* Then check if the bus can be brought back to idle .
*/
if ( priv - > state = = HIX5I2C_STAT_RW_SUCCESS & & stop )
ret = hix5hd2_i2c_wait_bus_idle ( priv ) ;
if ( ret < 0 )
hix5hd2_i2c_reset ( priv ) ;
return priv - > err ;
}
static int hix5hd2_i2c_xfer ( struct i2c_adapter * adap ,
struct i2c_msg * msgs , int num )
{
struct hix5hd2_i2c_priv * priv = i2c_get_adapdata ( adap ) ;
int i , ret , stop ;
pm_runtime_get_sync ( priv - > dev ) ;
for ( i = 0 ; i < num ; i + + , msgs + + ) {
stop = ( i = = num - 1 ) ;
ret = hix5hd2_i2c_xfer_msg ( priv , msgs , stop ) ;
if ( ret < 0 )
goto out ;
}
if ( i = = num ) {
ret = num ;
} else {
/* Only one message, cannot access the device */
if ( i = = 1 )
ret = - EREMOTEIO ;
else
ret = i ;
dev_warn ( priv - > dev , " xfer message failed \n " ) ;
}
out :
pm_runtime_mark_last_busy ( priv - > dev ) ;
pm_runtime_put_autosuspend ( priv - > dev ) ;
return ret ;
}
static u32 hix5hd2_i2c_func ( struct i2c_adapter * adap )
{
return I2C_FUNC_I2C | ( I2C_FUNC_SMBUS_EMUL & ~ I2C_FUNC_SMBUS_QUICK ) ;
}
static const struct i2c_algorithm hix5hd2_i2c_algorithm = {
. master_xfer = hix5hd2_i2c_xfer ,
. functionality = hix5hd2_i2c_func ,
} ;
static int hix5hd2_i2c_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct hix5hd2_i2c_priv * priv ;
struct resource * mem ;
unsigned int freq ;
int irq , ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
if ( of_property_read_u32 ( np , " clock-frequency " , & freq ) ) {
/* use 100k as default value */
priv - > freq = 100000 ;
} else {
if ( freq > HIX5I2C_MAX_FREQ ) {
priv - > freq = HIX5I2C_MAX_FREQ ;
dev_warn ( priv - > dev , " use max freq %d instead \n " ,
HIX5I2C_MAX_FREQ ) ;
} else {
priv - > freq = freq ;
}
}
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > regs = devm_ioremap_resource ( & pdev - > dev , mem ) ;
if ( IS_ERR ( priv - > regs ) )
return PTR_ERR ( priv - > regs ) ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
dev_err ( & pdev - > dev , " cannot find HS-I2C IRQ \n " ) ;
return irq ;
}
priv - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( priv - > clk ) ) {
dev_err ( & pdev - > dev , " cannot get clock \n " ) ;
return PTR_ERR ( priv - > clk ) ;
}
clk_prepare_enable ( priv - > clk ) ;
strlcpy ( priv - > adap . name , " hix5hd2-i2c " , sizeof ( priv - > adap . name ) ) ;
priv - > dev = & pdev - > dev ;
priv - > adap . owner = THIS_MODULE ;
priv - > adap . algo = & hix5hd2_i2c_algorithm ;
priv - > adap . retries = 3 ;
priv - > adap . dev . of_node = np ;
priv - > adap . algo_data = priv ;
priv - > adap . dev . parent = & pdev - > dev ;
i2c_set_adapdata ( & priv - > adap , priv ) ;
platform_set_drvdata ( pdev , priv ) ;
spin_lock_init ( & priv - > lock ) ;
init_completion ( & priv - > msg_complete ) ;
hix5hd2_i2c_init ( priv ) ;
ret = devm_request_irq ( & pdev - > dev , irq , hix5hd2_i2c_irq ,
IRQF_NO_SUSPEND | IRQF_ONESHOT ,
dev_name ( & pdev - > dev ) , priv ) ;
if ( ret ! = 0 ) {
dev_err ( & pdev - > dev , " cannot request HS-I2C IRQ %d \n " , irq ) ;
goto err_clk ;
}
pm_suspend_ignore_children ( & pdev - > dev , true ) ;
pm_runtime_set_autosuspend_delay ( priv - > dev , MSEC_PER_SEC ) ;
pm_runtime_use_autosuspend ( priv - > dev ) ;
pm_runtime_set_active ( priv - > dev ) ;
pm_runtime_enable ( priv - > dev ) ;
ret = i2c_add_adapter ( & priv - > adap ) ;
2016-08-09 14:36:17 +03:00
if ( ret < 0 )
2014-10-07 05:04:12 +04:00
goto err_runtime ;
return ret ;
err_runtime :
pm_runtime_disable ( priv - > dev ) ;
pm_runtime_set_suspended ( priv - > dev ) ;
err_clk :
clk_disable_unprepare ( priv - > clk ) ;
return ret ;
}
static int hix5hd2_i2c_remove ( struct platform_device * pdev )
{
struct hix5hd2_i2c_priv * priv = platform_get_drvdata ( pdev ) ;
i2c_del_adapter ( & priv - > adap ) ;
pm_runtime_disable ( priv - > dev ) ;
pm_runtime_set_suspended ( priv - > dev ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int hix5hd2_i2c_runtime_suspend ( struct device * dev )
{
2017-07-27 19:16:24 +03:00
struct hix5hd2_i2c_priv * priv = dev_get_drvdata ( dev ) ;
2014-10-07 05:04:12 +04:00
clk_disable_unprepare ( priv - > clk ) ;
return 0 ;
}
static int hix5hd2_i2c_runtime_resume ( struct device * dev )
{
2017-07-27 19:16:24 +03:00
struct hix5hd2_i2c_priv * priv = dev_get_drvdata ( dev ) ;
2014-10-07 05:04:12 +04:00
clk_prepare_enable ( priv - > clk ) ;
hix5hd2_i2c_init ( priv ) ;
return 0 ;
}
# endif
static const struct dev_pm_ops hix5hd2_i2c_pm_ops = {
2014-12-04 02:34:11 +03:00
SET_RUNTIME_PM_OPS ( hix5hd2_i2c_runtime_suspend ,
2014-10-07 05:04:12 +04:00
hix5hd2_i2c_runtime_resume ,
NULL )
} ;
static const struct of_device_id hix5hd2_i2c_match [ ] = {
{ . compatible = " hisilicon,hix5hd2-i2c " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , hix5hd2_i2c_match ) ;
static struct platform_driver hix5hd2_i2c_driver = {
. probe = hix5hd2_i2c_probe ,
. remove = hix5hd2_i2c_remove ,
. driver = {
. name = " hix5hd2-i2c " ,
. pm = & hix5hd2_i2c_pm_ops ,
. of_match_table = hix5hd2_i2c_match ,
} ,
} ;
module_platform_driver ( hix5hd2_i2c_driver ) ;
MODULE_DESCRIPTION ( " Hix5hd2 I2C Bus driver " ) ;
MODULE_AUTHOR ( " Wei Yan <sledge.yanwei@huawei.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
2015-05-08 03:50:11 +03:00
MODULE_ALIAS ( " platform:hix5hd2-i2c " ) ;