2013-02-26 02:08:38 +04:00
/*
* ChromeOS EC multi - function device ( I2C )
*
* Copyright ( C ) 2012 Google , Inc
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* 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/kernel.h>
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/interrupt.h>
# include <linux/mfd/cros_ec.h>
# include <linux/mfd/cros_ec_commands.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
static inline struct cros_ec_device * to_ec_dev ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
return i2c_get_clientdata ( client ) ;
}
2014-06-18 22:14:00 +04:00
static int cros_ec_cmd_xfer_i2c ( struct cros_ec_device * ec_dev ,
2014-06-18 22:14:02 +04:00
struct cros_ec_command * msg )
2013-02-26 02:08:38 +04:00
{
struct i2c_client * client = ec_dev - > priv ;
int ret = - ENOMEM ;
int i ;
2014-06-27 23:56:12 +04:00
int len ;
2013-02-26 02:08:38 +04:00
int packet_len ;
u8 * out_buf = NULL ;
u8 * in_buf = NULL ;
u8 sum ;
struct i2c_msg i2c_msg [ 2 ] ;
i2c_msg [ 0 ] . addr = client - > addr ;
i2c_msg [ 0 ] . flags = 0 ;
i2c_msg [ 1 ] . addr = client - > addr ;
i2c_msg [ 1 ] . flags = I2C_M_RD ;
/*
* allocate larger packet ( one byte for checksum , one byte for
* length , and one for result code )
*/
2014-06-18 22:14:02 +04:00
packet_len = msg - > insize + 3 ;
2013-02-26 02:08:38 +04:00
in_buf = kzalloc ( packet_len , GFP_KERNEL ) ;
if ( ! in_buf )
goto done ;
i2c_msg [ 1 ] . len = packet_len ;
i2c_msg [ 1 ] . buf = ( char * ) in_buf ;
/*
* allocate larger packet ( one byte for checksum , one for
* command code , one for length , and one for command version )
*/
2014-06-18 22:14:02 +04:00
packet_len = msg - > outsize + 4 ;
2013-02-26 02:08:38 +04:00
out_buf = kzalloc ( packet_len , GFP_KERNEL ) ;
if ( ! out_buf )
goto done ;
i2c_msg [ 0 ] . len = packet_len ;
i2c_msg [ 0 ] . buf = ( char * ) out_buf ;
out_buf [ 0 ] = EC_CMD_VERSION0 + msg - > version ;
2014-06-18 22:14:02 +04:00
out_buf [ 1 ] = msg - > command ;
out_buf [ 2 ] = msg - > outsize ;
2013-02-26 02:08:38 +04:00
/* copy message payload and compute checksum */
sum = out_buf [ 0 ] + out_buf [ 1 ] + out_buf [ 2 ] ;
2014-06-18 22:14:02 +04:00
for ( i = 0 ; i < msg - > outsize ; i + + ) {
2015-06-09 14:04:42 +03:00
out_buf [ 3 + i ] = msg - > data [ i ] ;
2013-02-26 02:08:38 +04:00
sum + = out_buf [ 3 + i ] ;
}
2014-06-18 22:14:02 +04:00
out_buf [ 3 + msg - > outsize ] = sum ;
2013-02-26 02:08:38 +04:00
/* send command to EC and read answer */
ret = i2c_transfer ( client - > adapter , i2c_msg , 2 ) ;
if ( ret < 0 ) {
dev_err ( ec_dev - > dev , " i2c transfer failed: %d \n " , ret ) ;
goto done ;
} else if ( ret ! = 2 ) {
dev_err ( ec_dev - > dev , " failed to get response: %d \n " , ret ) ;
ret = - EIO ;
goto done ;
}
/* check response error code */
2014-06-18 22:14:05 +04:00
msg - > result = i2c_msg [ 1 ] . buf [ 0 ] ;
ret = cros_ec_check_result ( ec_dev , msg ) ;
if ( ret )
2013-02-26 02:08:38 +04:00
goto done ;
2014-06-27 23:56:12 +04:00
len = in_buf [ 1 ] ;
if ( len > msg - > insize ) {
dev_err ( ec_dev - > dev , " packet too long (%d bytes, expected %d) " ,
len , msg - > insize ) ;
ret = - ENOSPC ;
goto done ;
}
2013-02-26 02:08:38 +04:00
/* copy response packet payload and compute checksum */
sum = in_buf [ 0 ] + in_buf [ 1 ] ;
2014-06-27 23:56:12 +04:00
for ( i = 0 ; i < len ; i + + ) {
2015-06-09 14:04:42 +03:00
msg - > data [ i ] = in_buf [ 2 + i ] ;
2013-02-26 02:08:38 +04:00
sum + = in_buf [ 2 + i ] ;
}
dev_dbg ( ec_dev - > dev , " packet: %*ph, sum = %02x \n " ,
i2c_msg [ 1 ] . len , in_buf , sum ) ;
2014-06-27 23:56:12 +04:00
if ( sum ! = in_buf [ 2 + len ] ) {
2013-02-26 02:08:38 +04:00
dev_err ( ec_dev - > dev , " bad packet checksum \n " ) ;
ret = - EBADMSG ;
goto done ;
}
2014-06-27 23:56:12 +04:00
ret = len ;
2013-02-26 02:08:38 +04:00
done :
kfree ( in_buf ) ;
kfree ( out_buf ) ;
return ret ;
}
2013-11-25 16:22:31 +04:00
static int cros_ec_i2c_probe ( struct i2c_client * client ,
2013-02-26 02:08:38 +04:00
const struct i2c_device_id * dev_id )
{
struct device * dev = & client - > dev ;
struct cros_ec_device * ec_dev = NULL ;
int err ;
ec_dev = devm_kzalloc ( dev , sizeof ( * ec_dev ) , GFP_KERNEL ) ;
if ( ! ec_dev )
return - ENOMEM ;
i2c_set_clientdata ( client , ec_dev ) ;
ec_dev - > dev = dev ;
ec_dev - > priv = client ;
ec_dev - > irq = client - > irq ;
2014-06-18 22:14:00 +04:00
ec_dev - > cmd_xfer = cros_ec_cmd_xfer_i2c ;
2015-06-09 14:04:45 +03:00
ec_dev - > pkt_xfer = NULL ;
2013-02-26 02:08:38 +04:00
ec_dev - > ec_name = client - > name ;
ec_dev - > phys_name = client - > adapter - > name ;
2015-06-09 14:04:45 +03:00
ec_dev - > din_size = sizeof ( struct ec_host_response ) +
sizeof ( struct ec_response_get_protocol_info ) ;
ec_dev - > dout_size = sizeof ( struct ec_host_request ) ;
2013-02-26 02:08:38 +04:00
err = cros_ec_register ( ec_dev ) ;
if ( err ) {
dev_err ( dev , " cannot register EC \n " ) ;
return err ;
}
return 0 ;
}
2013-11-25 16:22:31 +04:00
static int cros_ec_i2c_remove ( struct i2c_client * client )
2013-02-26 02:08:38 +04:00
{
struct cros_ec_device * ec_dev = i2c_get_clientdata ( client ) ;
cros_ec_remove ( ec_dev ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int cros_ec_i2c_suspend ( struct device * dev )
{
struct cros_ec_device * ec_dev = to_ec_dev ( dev ) ;
return cros_ec_suspend ( ec_dev ) ;
}
static int cros_ec_i2c_resume ( struct device * dev )
{
struct cros_ec_device * ec_dev = to_ec_dev ( dev ) ;
return cros_ec_resume ( ec_dev ) ;
}
# endif
static SIMPLE_DEV_PM_OPS ( cros_ec_i2c_pm_ops , cros_ec_i2c_suspend ,
cros_ec_i2c_resume ) ;
static const struct i2c_device_id cros_ec_i2c_id [ ] = {
{ " cros-ec-i2c " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , cros_ec_i2c_id ) ;
static struct i2c_driver cros_ec_driver = {
. driver = {
. name = " cros-ec-i2c " ,
. owner = THIS_MODULE ,
. pm = & cros_ec_i2c_pm_ops ,
} ,
2013-11-25 16:22:31 +04:00
. probe = cros_ec_i2c_probe ,
. remove = cros_ec_i2c_remove ,
2013-02-26 02:08:38 +04:00
. id_table = cros_ec_i2c_id ,
} ;
module_i2c_driver ( cros_ec_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " ChromeOS EC multi function device " ) ;