2015-10-23 19:51:59 +09:00
/*
* Copyright ( C ) 2015 Masahiro Yamada < yamada . masahiro @ socionext . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/clk.h>
# include <linux/i2c.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# define UNIPHIER_I2C_DTRM 0x00 /* TX register */
# define UNIPHIER_I2C_DTRM_IRQEN BIT(11) /* enable interrupt */
# define UNIPHIER_I2C_DTRM_STA BIT(10) /* start condition */
# define UNIPHIER_I2C_DTRM_STO BIT(9) /* stop condition */
# define UNIPHIER_I2C_DTRM_NACK BIT(8) /* do not return ACK */
# define UNIPHIER_I2C_DTRM_RD BIT(0) /* read transaction */
# define UNIPHIER_I2C_DREC 0x04 /* RX register */
# define UNIPHIER_I2C_DREC_MST BIT(14) /* 1 = master, 0 = slave */
# define UNIPHIER_I2C_DREC_TX BIT(13) /* 1 = transmit, 0 = receive */
# define UNIPHIER_I2C_DREC_STS BIT(12) /* stop condition detected */
# define UNIPHIER_I2C_DREC_LRB BIT(11) /* no ACK */
# define UNIPHIER_I2C_DREC_LAB BIT(9) /* arbitration lost */
# define UNIPHIER_I2C_DREC_BBN BIT(8) /* bus not busy */
# define UNIPHIER_I2C_MYAD 0x08 /* slave address */
# define UNIPHIER_I2C_CLK 0x0c /* clock frequency control */
# define UNIPHIER_I2C_BRST 0x10 /* bus reset */
# define UNIPHIER_I2C_BRST_FOEN BIT(1) /* normal operation */
# define UNIPHIER_I2C_BRST_RSCL BIT(0) /* release SCL */
# define UNIPHIER_I2C_HOLD 0x14 /* hold time control */
# define UNIPHIER_I2C_BSTS 0x18 /* bus status monitor */
# define UNIPHIER_I2C_BSTS_SDA BIT(1) /* readback of SDA line */
# define UNIPHIER_I2C_BSTS_SCL BIT(0) /* readback of SCL line */
# define UNIPHIER_I2C_NOISE 0x1c /* noise filter control */
# define UNIPHIER_I2C_SETUP 0x20 /* setup time control */
# define UNIPHIER_I2C_DEFAULT_SPEED 100000
# define UNIPHIER_I2C_MAX_SPEED 400000
struct uniphier_i2c_priv {
struct completion comp ;
struct i2c_adapter adap ;
void __iomem * membase ;
struct clk * clk ;
unsigned int busy_cnt ;
} ;
static irqreturn_t uniphier_i2c_interrupt ( int irq , void * dev_id )
{
struct uniphier_i2c_priv * priv = dev_id ;
/*
* This hardware uses edge triggered interrupt . Do not touch the
* hardware registers in this handler to make sure to catch the next
* interrupt edge . Just send a complete signal and return .
*/
complete ( & priv - > comp ) ;
return IRQ_HANDLED ;
}
static int uniphier_i2c_xfer_byte ( struct i2c_adapter * adap , u32 txdata ,
u32 * rxdatap )
{
struct uniphier_i2c_priv * priv = i2c_get_adapdata ( adap ) ;
unsigned long time_left ;
u32 rxdata ;
reinit_completion ( & priv - > comp ) ;
txdata | = UNIPHIER_I2C_DTRM_IRQEN ;
dev_dbg ( & adap - > dev , " write data: 0x%04x \n " , txdata ) ;
writel ( txdata , priv - > membase + UNIPHIER_I2C_DTRM ) ;
time_left = wait_for_completion_timeout ( & priv - > comp , adap - > timeout ) ;
if ( unlikely ( ! time_left ) ) {
dev_err ( & adap - > dev , " transaction timeout \n " ) ;
return - ETIMEDOUT ;
}
rxdata = readl ( priv - > membase + UNIPHIER_I2C_DREC ) ;
dev_dbg ( & adap - > dev , " read data: 0x%04x \n " , rxdata ) ;
if ( rxdatap )
* rxdatap = rxdata ;
return 0 ;
}
static int uniphier_i2c_send_byte ( struct i2c_adapter * adap , u32 txdata )
{
u32 rxdata ;
int ret ;
ret = uniphier_i2c_xfer_byte ( adap , txdata , & rxdata ) ;
if ( ret )
return ret ;
if ( unlikely ( rxdata & UNIPHIER_I2C_DREC_LAB ) ) {
dev_dbg ( & adap - > dev , " arbitration lost \n " ) ;
return - EAGAIN ;
}
if ( unlikely ( rxdata & UNIPHIER_I2C_DREC_LRB ) ) {
dev_dbg ( & adap - > dev , " could not get ACK \n " ) ;
return - ENXIO ;
}
return 0 ;
}
static int uniphier_i2c_tx ( struct i2c_adapter * adap , u16 addr , u16 len ,
const u8 * buf )
{
int ret ;
dev_dbg ( & adap - > dev , " start condition \n " ) ;
ret = uniphier_i2c_send_byte ( adap , addr < < 1 |
UNIPHIER_I2C_DTRM_STA |
UNIPHIER_I2C_DTRM_NACK ) ;
if ( ret )
return ret ;
while ( len - - ) {
ret = uniphier_i2c_send_byte ( adap ,
UNIPHIER_I2C_DTRM_NACK | * buf + + ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int uniphier_i2c_rx ( struct i2c_adapter * adap , u16 addr , u16 len ,
u8 * buf )
{
int ret ;
dev_dbg ( & adap - > dev , " start condition \n " ) ;
ret = uniphier_i2c_send_byte ( adap , addr < < 1 |
UNIPHIER_I2C_DTRM_STA |
UNIPHIER_I2C_DTRM_NACK |
UNIPHIER_I2C_DTRM_RD ) ;
if ( ret )
return ret ;
while ( len - - ) {
u32 rxdata ;
ret = uniphier_i2c_xfer_byte ( adap ,
len ? 0 : UNIPHIER_I2C_DTRM_NACK ,
& rxdata ) ;
if ( ret )
return ret ;
* buf + + = rxdata ;
}
return 0 ;
}
static int uniphier_i2c_stop ( struct i2c_adapter * adap )
{
dev_dbg ( & adap - > dev , " stop condition \n " ) ;
return uniphier_i2c_send_byte ( adap , UNIPHIER_I2C_DTRM_STO |
UNIPHIER_I2C_DTRM_NACK ) ;
}
static int uniphier_i2c_master_xfer_one ( struct i2c_adapter * adap ,
struct i2c_msg * msg , bool stop )
{
bool is_read = msg - > flags & I2C_M_RD ;
bool recovery = false ;
int ret ;
dev_dbg ( & adap - > dev , " %s: addr=0x%02x, len=%d, stop=%d \n " ,
is_read ? " receive " : " transmit " , msg - > addr , msg - > len , stop ) ;
if ( is_read )
ret = uniphier_i2c_rx ( adap , msg - > addr , msg - > len , msg - > buf ) ;
else
ret = uniphier_i2c_tx ( adap , msg - > addr , msg - > len , msg - > buf ) ;
if ( ret = = - EAGAIN ) /* could not acquire bus. bail out without STOP */
return ret ;
if ( ret = = - ETIMEDOUT ) {
/* This error is fatal. Needs recovery. */
stop = false ;
recovery = true ;
}
if ( stop ) {
int ret2 = uniphier_i2c_stop ( adap ) ;
if ( ret2 ) {
/* Failed to issue STOP. The bus needs recovery. */
recovery = true ;
ret = ret ? : ret2 ;
}
}
if ( recovery )
i2c_recover_bus ( adap ) ;
return ret ;
}
static int uniphier_i2c_check_bus_busy ( struct i2c_adapter * adap )
{
struct uniphier_i2c_priv * priv = i2c_get_adapdata ( adap ) ;
if ( ! ( readl ( priv - > membase + UNIPHIER_I2C_DREC ) &
UNIPHIER_I2C_DREC_BBN ) ) {
if ( priv - > busy_cnt + + > 3 ) {
/*
* If bus busy continues too long , it is probably
* in a wrong state . Try bus recovery .
*/
i2c_recover_bus ( adap ) ;
priv - > busy_cnt = 0 ;
}
return - EAGAIN ;
}
priv - > busy_cnt = 0 ;
return 0 ;
}
static int uniphier_i2c_master_xfer ( struct i2c_adapter * adap ,
struct i2c_msg * msgs , int num )
{
struct i2c_msg * msg , * emsg = msgs + num ;
int ret ;
ret = uniphier_i2c_check_bus_busy ( adap ) ;
if ( ret )
return ret ;
for ( msg = msgs ; msg < emsg ; msg + + ) {
/* If next message is read, skip the stop condition */
bool stop = ! ( msg + 1 < emsg & & msg [ 1 ] . flags & I2C_M_RD ) ;
/* but, force it if I2C_M_STOP is set */
if ( msg - > flags & I2C_M_STOP )
stop = true ;
ret = uniphier_i2c_master_xfer_one ( adap , msg , stop ) ;
if ( ret )
return ret ;
}
return num ;
}
static u32 uniphier_i2c_functionality ( struct i2c_adapter * adap )
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
}
static const struct i2c_algorithm uniphier_i2c_algo = {
. master_xfer = uniphier_i2c_master_xfer ,
. functionality = uniphier_i2c_functionality ,
} ;
static void uniphier_i2c_reset ( struct uniphier_i2c_priv * priv , bool reset_on )
{
u32 val = UNIPHIER_I2C_BRST_RSCL ;
val | = reset_on ? 0 : UNIPHIER_I2C_BRST_FOEN ;
writel ( val , priv - > membase + UNIPHIER_I2C_BRST ) ;
}
static int uniphier_i2c_get_scl ( struct i2c_adapter * adap )
{
struct uniphier_i2c_priv * priv = i2c_get_adapdata ( adap ) ;
return ! ! ( readl ( priv - > membase + UNIPHIER_I2C_BSTS ) &
UNIPHIER_I2C_BSTS_SCL ) ;
}
static void uniphier_i2c_set_scl ( struct i2c_adapter * adap , int val )
{
struct uniphier_i2c_priv * priv = i2c_get_adapdata ( adap ) ;
writel ( val ? UNIPHIER_I2C_BRST_RSCL : 0 ,
priv - > membase + UNIPHIER_I2C_BRST ) ;
}
static int uniphier_i2c_get_sda ( struct i2c_adapter * adap )
{
struct uniphier_i2c_priv * priv = i2c_get_adapdata ( adap ) ;
return ! ! ( readl ( priv - > membase + UNIPHIER_I2C_BSTS ) &
UNIPHIER_I2C_BSTS_SDA ) ;
}
static void uniphier_i2c_unprepare_recovery ( struct i2c_adapter * adap )
{
uniphier_i2c_reset ( i2c_get_adapdata ( adap ) , false ) ;
}
static struct i2c_bus_recovery_info uniphier_i2c_bus_recovery_info = {
. recover_bus = i2c_generic_scl_recovery ,
. get_scl = uniphier_i2c_get_scl ,
. set_scl = uniphier_i2c_set_scl ,
. get_sda = uniphier_i2c_get_sda ,
. unprepare_recovery = uniphier_i2c_unprepare_recovery ,
} ;
2016-09-01 20:46:28 +09:00
static void uniphier_i2c_hw_init ( struct uniphier_i2c_priv * priv ,
u32 bus_speed , unsigned long clk_rate )
2015-10-23 19:51:59 +09:00
{
uniphier_i2c_reset ( priv , true ) ;
writel ( ( clk_rate / bus_speed / 2 < < 16 ) | ( clk_rate / bus_speed ) ,
priv - > membase + UNIPHIER_I2C_CLK ) ;
uniphier_i2c_reset ( priv , false ) ;
}
static int uniphier_i2c_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct uniphier_i2c_priv * priv ;
struct resource * regs ;
2016-09-01 20:46:28 +09:00
u32 bus_speed ;
unsigned long clk_rate ;
int irq , ret ;
2015-10-23 19:51:59 +09:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
regs = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > membase = devm_ioremap_resource ( dev , regs ) ;
if ( IS_ERR ( priv - > membase ) )
return PTR_ERR ( priv - > membase ) ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
2016-04-21 15:12:44 +09:00
dev_err ( dev , " failed to get IRQ number \n " ) ;
2015-10-23 19:51:59 +09:00
return irq ;
}
2016-09-01 20:46:28 +09:00
if ( of_property_read_u32 ( dev - > of_node , " clock-frequency " , & bus_speed ) )
bus_speed = UNIPHIER_I2C_DEFAULT_SPEED ;
if ( ! bus_speed | | bus_speed > UNIPHIER_I2C_MAX_SPEED ) {
dev_err ( dev , " invalid clock-frequency %d \n " , bus_speed ) ;
return - EINVAL ;
}
priv - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( priv - > clk ) ) {
dev_err ( dev , " failed to get clock \n " ) ;
return PTR_ERR ( priv - > clk ) ;
}
ret = clk_prepare_enable ( priv - > clk ) ;
if ( ret )
return ret ;
clk_rate = clk_get_rate ( priv - > clk ) ;
if ( ! clk_rate ) {
dev_err ( dev , " input clock rate should not be zero \n " ) ;
ret = - EINVAL ;
2016-11-04 19:49:20 +09:00
goto disable_clk ;
2016-09-01 20:46:28 +09:00
}
2015-10-23 19:51:59 +09:00
init_completion ( & priv - > comp ) ;
priv - > adap . owner = THIS_MODULE ;
priv - > adap . algo = & uniphier_i2c_algo ;
priv - > adap . dev . parent = dev ;
priv - > adap . dev . of_node = dev - > of_node ;
strlcpy ( priv - > adap . name , " UniPhier I2C " , sizeof ( priv - > adap . name ) ) ;
priv - > adap . bus_recovery_info = & uniphier_i2c_bus_recovery_info ;
i2c_set_adapdata ( & priv - > adap , priv ) ;
platform_set_drvdata ( pdev , priv ) ;
2016-09-01 20:46:28 +09:00
uniphier_i2c_hw_init ( priv , bus_speed , clk_rate ) ;
2015-10-23 19:51:59 +09:00
ret = devm_request_irq ( dev , irq , uniphier_i2c_interrupt , 0 , pdev - > name ,
priv ) ;
if ( ret ) {
dev_err ( dev , " failed to request irq %d \n " , irq ) ;
2016-11-04 19:49:20 +09:00
goto disable_clk ;
2015-10-23 19:51:59 +09:00
}
ret = i2c_add_adapter ( & priv - > adap ) ;
2016-11-04 19:49:20 +09:00
disable_clk :
2015-10-23 19:51:59 +09:00
if ( ret )
clk_disable_unprepare ( priv - > clk ) ;
return ret ;
}
static int uniphier_i2c_remove ( struct platform_device * pdev )
{
struct uniphier_i2c_priv * priv = platform_get_drvdata ( pdev ) ;
i2c_del_adapter ( & priv - > adap ) ;
clk_disable_unprepare ( priv - > clk ) ;
return 0 ;
}
static const struct of_device_id uniphier_i2c_match [ ] = {
{ . compatible = " socionext,uniphier-i2c " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , uniphier_i2c_match ) ;
static struct platform_driver uniphier_i2c_drv = {
. probe = uniphier_i2c_probe ,
. remove = uniphier_i2c_remove ,
. driver = {
. name = " uniphier-i2c " ,
. of_match_table = uniphier_i2c_match ,
} ,
} ;
module_platform_driver ( uniphier_i2c_drv ) ;
MODULE_AUTHOR ( " Masahiro Yamada <yamada.masahiro@socionext.com> " ) ;
MODULE_DESCRIPTION ( " UniPhier I2C bus driver " ) ;
MODULE_LICENSE ( " GPL " ) ;