2012-11-18 18:36:19 +02:00
/*
* CBUS I2C driver for Nokia Internet Tablets .
*
* Copyright ( C ) 2004 - 2010 Nokia Corporation
*
* Based on code written by Juha Yrjölä , David Weinehall , Mikko Ylinen and
* Felipe Balbi . Converted to I2C driver by Aaro Koskinen .
*
* This file is subject to the terms and conditions of the GNU General
* Public License . See the file " COPYING " in the main directory of this
* archive for more details .
*
* 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/io.h>
# include <linux/i2c.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/module.h>
2019-02-06 08:16:51 +01:00
# include <linux/gpio/consumer.h>
2012-11-18 18:36:19 +02:00
# include <linux/interrupt.h>
# include <linux/platform_device.h>
/*
* Bit counts are derived from Nokia implementation . These should be checked
* if other CBUS implementations appear .
*/
# define CBUS_ADDR_BITS 3
# define CBUS_REG_BITS 5
struct cbus_host {
spinlock_t lock ; /* host lock */
struct device * dev ;
2019-02-06 08:16:51 +01:00
struct gpio_desc * clk ;
struct gpio_desc * dat ;
struct gpio_desc * sel ;
2012-11-18 18:36:19 +02:00
} ;
/**
* cbus_send_bit - sends one bit over the bus
* @ host : the host we ' re using
* @ bit : one bit of information to send
*/
static void cbus_send_bit ( struct cbus_host * host , unsigned bit )
{
2019-02-06 08:16:51 +01:00
gpiod_set_value ( host - > dat , bit ? 1 : 0 ) ;
gpiod_set_value ( host - > clk , 1 ) ;
gpiod_set_value ( host - > clk , 0 ) ;
2012-11-18 18:36:19 +02:00
}
/**
* cbus_send_data - sends @ len amount of data over the bus
* @ host : the host we ' re using
* @ data : the data to send
* @ len : size of the transfer
*/
static void cbus_send_data ( struct cbus_host * host , unsigned data , unsigned len )
{
int i ;
for ( i = len ; i > 0 ; i - - )
cbus_send_bit ( host , data & ( 1 < < ( i - 1 ) ) ) ;
}
/**
* cbus_receive_bit - receives one bit from the bus
* @ host : the host we ' re using
*/
static int cbus_receive_bit ( struct cbus_host * host )
{
int ret ;
2019-02-06 08:16:51 +01:00
gpiod_set_value ( host - > clk , 1 ) ;
ret = gpiod_get_value ( host - > dat ) ;
gpiod_set_value ( host - > clk , 0 ) ;
2012-11-18 18:36:19 +02:00
return ret ;
}
/**
* cbus_receive_word - receives 16 - bit word from the bus
* @ host : the host we ' re using
*/
static int cbus_receive_word ( struct cbus_host * host )
{
int ret = 0 ;
int i ;
for ( i = 16 ; i > 0 ; i - - ) {
int bit = cbus_receive_bit ( host ) ;
if ( bit < 0 )
return bit ;
if ( bit )
ret | = 1 < < ( i - 1 ) ;
}
return ret ;
}
/**
* cbus_transfer - transfers data over the bus
* @ host : the host we ' re using
* @ rw : read / write flag
* @ dev : device address
* @ reg : register address
* @ data : if @ rw = = I2C_SBUS_WRITE data to send otherwise 0
*/
static int cbus_transfer ( struct cbus_host * host , char rw , unsigned dev ,
unsigned reg , unsigned data )
{
unsigned long flags ;
int ret ;
/* We don't want interrupts disturbing our transfer */
spin_lock_irqsave ( & host - > lock , flags ) ;
/* Reset state and start of transfer, SEL stays down during transfer */
2019-02-06 08:16:51 +01:00
gpiod_set_value ( host - > sel , 0 ) ;
2012-11-18 18:36:19 +02:00
/* Set the DAT pin to output */
2019-02-06 08:16:51 +01:00
gpiod_direction_output ( host - > dat , 1 ) ;
2012-11-18 18:36:19 +02:00
/* Send the device address */
cbus_send_data ( host , dev , CBUS_ADDR_BITS ) ;
/* Send the rw flag */
cbus_send_bit ( host , rw = = I2C_SMBUS_READ ) ;
/* Send the register address */
cbus_send_data ( host , reg , CBUS_REG_BITS ) ;
if ( rw = = I2C_SMBUS_WRITE ) {
cbus_send_data ( host , data , 16 ) ;
ret = 0 ;
} else {
2019-02-06 08:16:51 +01:00
ret = gpiod_direction_input ( host - > dat ) ;
2012-11-18 18:36:19 +02:00
if ( ret ) {
dev_dbg ( host - > dev , " failed setting direction \n " ) ;
goto out ;
}
2019-02-06 08:16:51 +01:00
gpiod_set_value ( host - > clk , 1 ) ;
2012-11-18 18:36:19 +02:00
ret = cbus_receive_word ( host ) ;
if ( ret < 0 ) {
dev_dbg ( host - > dev , " failed receiving data \n " ) ;
goto out ;
}
}
/* Indicate end of transfer, SEL goes up until next transfer */
2019-02-06 08:16:51 +01:00
gpiod_set_value ( host - > sel , 1 ) ;
gpiod_set_value ( host - > clk , 1 ) ;
gpiod_set_value ( host - > clk , 0 ) ;
2012-11-18 18:36:19 +02:00
out :
spin_unlock_irqrestore ( & host - > lock , flags ) ;
return ret ;
}
static int cbus_i2c_smbus_xfer ( struct i2c_adapter * adapter ,
u16 addr ,
unsigned short flags ,
char read_write ,
u8 command ,
int size ,
union i2c_smbus_data * data )
{
struct cbus_host * chost = i2c_get_adapdata ( adapter ) ;
int ret ;
if ( size ! = I2C_SMBUS_WORD_DATA )
return - EINVAL ;
ret = cbus_transfer ( chost , read_write = = I2C_SMBUS_READ , addr ,
command , data - > word ) ;
if ( ret < 0 )
return ret ;
if ( read_write = = I2C_SMBUS_READ )
data - > word = ret ;
return 0 ;
}
static u32 cbus_i2c_func ( struct i2c_adapter * adapter )
{
return I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_WRITE_WORD_DATA ;
}
static const struct i2c_algorithm cbus_i2c_algo = {
. smbus_xfer = cbus_i2c_smbus_xfer ,
. functionality = cbus_i2c_func ,
} ;
static int cbus_i2c_remove ( struct platform_device * pdev )
{
struct i2c_adapter * adapter = platform_get_drvdata ( pdev ) ;
2013-03-09 08:16:46 +00:00
i2c_del_adapter ( adapter ) ;
return 0 ;
2012-11-18 18:36:19 +02:00
}
static int cbus_i2c_probe ( struct platform_device * pdev )
{
struct i2c_adapter * adapter ;
struct cbus_host * chost ;
adapter = devm_kzalloc ( & pdev - > dev , sizeof ( struct i2c_adapter ) ,
GFP_KERNEL ) ;
if ( ! adapter )
return - ENOMEM ;
chost = devm_kzalloc ( & pdev - > dev , sizeof ( * chost ) , GFP_KERNEL ) ;
if ( ! chost )
return - ENOMEM ;
2019-02-06 08:16:51 +01:00
if ( gpiod_count ( & pdev - > dev , NULL ) ! = 3 )
2012-11-18 18:36:19 +02:00
return - ENODEV ;
2019-02-06 08:16:51 +01:00
chost - > clk = devm_gpiod_get_index ( & pdev - > dev , NULL , 0 , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( chost - > clk ) )
return PTR_ERR ( chost - > clk ) ;
chost - > dat = devm_gpiod_get_index ( & pdev - > dev , NULL , 1 , GPIOD_IN ) ;
if ( IS_ERR ( chost - > dat ) )
return PTR_ERR ( chost - > dat ) ;
chost - > sel = devm_gpiod_get_index ( & pdev - > dev , NULL , 2 , GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( chost - > sel ) )
return PTR_ERR ( chost - > sel ) ;
gpiod_set_consumer_name ( chost - > clk , " CBUS clk " ) ;
gpiod_set_consumer_name ( chost - > dat , " CBUS dat " ) ;
gpiod_set_consumer_name ( chost - > sel , " CBUS sel " ) ;
2012-11-18 18:36:19 +02:00
adapter - > owner = THIS_MODULE ;
adapter - > class = I2C_CLASS_HWMON ;
adapter - > dev . parent = & pdev - > dev ;
2013-11-14 15:08:42 -08:00
adapter - > dev . of_node = pdev - > dev . of_node ;
2012-11-18 18:36:19 +02:00
adapter - > nr = pdev - > id ;
adapter - > timeout = HZ ;
adapter - > algo = & cbus_i2c_algo ;
strlcpy ( adapter - > name , " CBUS I2C adapter " , sizeof ( adapter - > name ) ) ;
spin_lock_init ( & chost - > lock ) ;
chost - > dev = & pdev - > dev ;
i2c_set_adapdata ( adapter , chost ) ;
platform_set_drvdata ( pdev , adapter ) ;
return i2c_add_numbered_adapter ( adapter ) ;
}
# if defined(CONFIG_OF)
static const struct of_device_id i2c_cbus_dt_ids [ ] = {
{ . compatible = " i2c-cbus-gpio " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , i2c_cbus_dt_ids ) ;
# endif
static struct platform_driver cbus_i2c_driver = {
. probe = cbus_i2c_probe ,
. remove = cbus_i2c_remove ,
. driver = {
. name = " i2c-cbus-gpio " ,
2013-11-14 15:08:42 -08:00
. of_match_table = of_match_ptr ( i2c_cbus_dt_ids ) ,
2012-11-18 18:36:19 +02:00
} ,
} ;
module_platform_driver ( cbus_i2c_driver ) ;
MODULE_ALIAS ( " platform:i2c-cbus-gpio " ) ;
MODULE_DESCRIPTION ( " CBUS I2C driver " ) ;
MODULE_AUTHOR ( " Juha Yrjölä " ) ;
MODULE_AUTHOR ( " David Weinehall " ) ;
MODULE_AUTHOR ( " Mikko Ylinen " ) ;
MODULE_AUTHOR ( " Felipe Balbi " ) ;
MODULE_AUTHOR ( " Aaro Koskinen <aaro.koskinen@iki.fi> " ) ;
MODULE_LICENSE ( " GPL " ) ;