2021-07-23 08:44:35 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Virtio I2C Bus Driver
*
* The Virtio I2C Specification :
* https : //raw.githubusercontent.com/oasis-tcs/virtio-spec/master/virtio-i2c.tex
*
* Copyright ( c ) 2021 Intel Corporation . All rights reserved .
*/
# include <linux/acpi.h>
# include <linux/completion.h>
# include <linux/err.h>
# include <linux/i2c.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/virtio.h>
# include <linux/virtio_ids.h>
# include <linux/virtio_config.h>
# include <linux/virtio_i2c.h>
/**
* struct virtio_i2c - virtio I2C data
* @ vdev : virtio device for this controller
* @ adap : I2C adapter for this controller
* @ vq : the virtio virtqueue for communication
*/
struct virtio_i2c {
struct virtio_device * vdev ;
struct i2c_adapter adap ;
struct virtqueue * vq ;
} ;
/**
* struct virtio_i2c_req - the virtio I2C request structure
2021-12-02 18:32:14 +03:00
* @ completion : completion of virtio I2C message
2021-07-23 08:44:35 +03:00
* @ out_hdr : the OUT header of the virtio I2C message
* @ buf : the buffer into which data is read , or from which it ' s written
* @ in_hdr : the IN header of the virtio I2C message
*/
struct virtio_i2c_req {
2021-12-02 18:32:14 +03:00
struct completion completion ;
2021-07-23 08:44:35 +03:00
struct virtio_i2c_out_hdr out_hdr ____cacheline_aligned ;
uint8_t * buf ____cacheline_aligned ;
struct virtio_i2c_in_hdr in_hdr ____cacheline_aligned ;
} ;
static void virtio_i2c_msg_done ( struct virtqueue * vq )
{
2021-12-02 18:32:14 +03:00
struct virtio_i2c_req * req ;
unsigned int len ;
2021-07-23 08:44:35 +03:00
2021-12-02 18:32:14 +03:00
while ( ( req = virtqueue_get_buf ( vq , & len ) ) )
complete ( & req - > completion ) ;
2021-07-23 08:44:35 +03:00
}
static int virtio_i2c_prepare_reqs ( struct virtqueue * vq ,
struct virtio_i2c_req * reqs ,
struct i2c_msg * msgs , int num )
{
struct scatterlist * sgs [ 3 ] , out_hdr , msg_buf , in_hdr ;
int i ;
for ( i = 0 ; i < num ; i + + ) {
int outcnt = 0 , incnt = 0 ;
2021-12-02 18:32:14 +03:00
init_completion ( & reqs [ i ] . completion ) ;
2021-07-23 08:44:35 +03:00
/*
* Only 7 - bit mode supported for this moment . For the address
* format , Please check the Virtio I2C Specification .
*/
reqs [ i ] . out_hdr . addr = cpu_to_le16 ( msgs [ i ] . addr < < 1 ) ;
2021-10-21 12:47:49 +03:00
if ( msgs [ i ] . flags & I2C_M_RD )
reqs [ i ] . out_hdr . flags | = cpu_to_le32 ( VIRTIO_I2C_FLAGS_M_RD ) ;
2021-07-23 08:44:35 +03:00
if ( i ! = num - 1 )
2021-10-21 12:47:49 +03:00
reqs [ i ] . out_hdr . flags | = cpu_to_le32 ( VIRTIO_I2C_FLAGS_FAIL_NEXT ) ;
2021-07-23 08:44:35 +03:00
sg_init_one ( & out_hdr , & reqs [ i ] . out_hdr , sizeof ( reqs [ i ] . out_hdr ) ) ;
sgs [ outcnt + + ] = & out_hdr ;
2021-10-21 12:47:49 +03:00
if ( msgs [ i ] . len ) {
reqs [ i ] . buf = i2c_get_dma_safe_msg_buf ( & msgs [ i ] , 1 ) ;
if ( ! reqs [ i ] . buf )
break ;
2021-07-23 08:44:35 +03:00
2021-10-21 12:47:49 +03:00
sg_init_one ( & msg_buf , reqs [ i ] . buf , msgs [ i ] . len ) ;
2021-07-23 08:44:35 +03:00
2021-10-21 12:47:49 +03:00
if ( msgs [ i ] . flags & I2C_M_RD )
sgs [ outcnt + incnt + + ] = & msg_buf ;
else
sgs [ outcnt + + ] = & msg_buf ;
}
2021-07-23 08:44:35 +03:00
sg_init_one ( & in_hdr , & reqs [ i ] . in_hdr , sizeof ( reqs [ i ] . in_hdr ) ) ;
sgs [ outcnt + incnt + + ] = & in_hdr ;
if ( virtqueue_add_sgs ( vq , sgs , outcnt , incnt , & reqs [ i ] , GFP_KERNEL ) ) {
i2c_put_dma_safe_msg_buf ( reqs [ i ] . buf , & msgs [ i ] , false ) ;
break ;
}
}
return i ;
}
static int virtio_i2c_complete_reqs ( struct virtqueue * vq ,
struct virtio_i2c_req * reqs ,
2021-11-11 19:04:11 +03:00
struct i2c_msg * msgs , int num )
2021-07-23 08:44:35 +03:00
{
2021-11-11 19:04:11 +03:00
bool failed = false ;
2021-07-23 08:44:35 +03:00
int i , j = 0 ;
for ( i = 0 ; i < num ; i + + ) {
2021-12-02 18:32:14 +03:00
struct virtio_i2c_req * req = & reqs [ i ] ;
2021-07-23 08:44:35 +03:00
2021-12-02 18:32:14 +03:00
wait_for_completion ( & req - > completion ) ;
if ( ! failed & & req - > in_hdr . status ! = VIRTIO_I2C_MSG_OK )
2021-07-23 08:44:35 +03:00
failed = true ;
i2c_put_dma_safe_msg_buf ( reqs [ i ] . buf , & msgs [ i ] , ! failed ) ;
if ( ! failed )
j + + ;
}
2021-11-11 19:04:11 +03:00
return j ;
2021-07-23 08:44:35 +03:00
}
static int virtio_i2c_xfer ( struct i2c_adapter * adap , struct i2c_msg * msgs ,
int num )
{
struct virtio_i2c * vi = i2c_get_adapdata ( adap ) ;
struct virtqueue * vq = vi - > vq ;
struct virtio_i2c_req * reqs ;
int count ;
reqs = kcalloc ( num , sizeof ( * reqs ) , GFP_KERNEL ) ;
if ( ! reqs )
return - ENOMEM ;
count = virtio_i2c_prepare_reqs ( vq , reqs , msgs , num ) ;
if ( ! count )
goto err_free ;
/*
* For the case where count < num , i . e . we weren ' t able to queue all the
* msgs , ideally we should abort right away and return early , but some
* of the messages are already sent to the remote I2C controller and the
* virtqueue will be left in undefined state in that case . We kick the
* remote here to clear the virtqueue , so we can try another set of
* messages later on .
*/
virtqueue_kick ( vq ) ;
2021-11-11 19:04:11 +03:00
count = virtio_i2c_complete_reqs ( vq , reqs , msgs , count ) ;
2021-07-23 08:44:35 +03:00
err_free :
kfree ( reqs ) ;
return count ;
}
static void virtio_i2c_del_vqs ( struct virtio_device * vdev )
{
2021-10-13 13:55:44 +03:00
virtio_reset_device ( vdev ) ;
2021-07-23 08:44:35 +03:00
vdev - > config - > del_vqs ( vdev ) ;
}
static int virtio_i2c_setup_vqs ( struct virtio_i2c * vi )
{
struct virtio_device * vdev = vi - > vdev ;
vi - > vq = virtio_find_single_vq ( vdev , virtio_i2c_msg_done , " msg " ) ;
return PTR_ERR_OR_ZERO ( vi - > vq ) ;
}
static u32 virtio_i2c_func ( struct i2c_adapter * adap )
{
2021-10-21 12:47:49 +03:00
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
2021-07-23 08:44:35 +03:00
}
static struct i2c_algorithm virtio_algorithm = {
. master_xfer = virtio_i2c_xfer ,
. functionality = virtio_i2c_func ,
} ;
static int virtio_i2c_probe ( struct virtio_device * vdev )
{
struct virtio_i2c * vi ;
int ret ;
2021-10-21 12:47:49 +03:00
if ( ! virtio_has_feature ( vdev , VIRTIO_I2C_F_ZERO_LENGTH_REQUEST ) ) {
dev_err ( & vdev - > dev , " Zero-length request feature is mandatory \n " ) ;
return - EINVAL ;
}
2021-07-23 08:44:35 +03:00
vi = devm_kzalloc ( & vdev - > dev , sizeof ( * vi ) , GFP_KERNEL ) ;
if ( ! vi )
return - ENOMEM ;
vdev - > priv = vi ;
vi - > vdev = vdev ;
ret = virtio_i2c_setup_vqs ( vi ) ;
if ( ret )
return ret ;
vi - > adap . owner = THIS_MODULE ;
snprintf ( vi - > adap . name , sizeof ( vi - > adap . name ) ,
" i2c_virtio at virtio bus %d " , vdev - > index ) ;
vi - > adap . algo = & virtio_algorithm ;
vi - > adap . dev . parent = & vdev - > dev ;
vi - > adap . dev . of_node = vdev - > dev . of_node ;
i2c_set_adapdata ( & vi - > adap , vi ) ;
/*
* Setup ACPI node for controlled devices which will be probed through
* ACPI .
*/
ACPI_COMPANION_SET ( & vi - > adap . dev , ACPI_COMPANION ( vdev - > dev . parent ) ) ;
ret = i2c_add_adapter ( & vi - > adap ) ;
if ( ret )
virtio_i2c_del_vqs ( vdev ) ;
return ret ;
}
static void virtio_i2c_remove ( struct virtio_device * vdev )
{
struct virtio_i2c * vi = vdev - > priv ;
i2c_del_adapter ( & vi - > adap ) ;
virtio_i2c_del_vqs ( vdev ) ;
}
static struct virtio_device_id id_table [ ] = {
{ VIRTIO_ID_I2C_ADAPTER , VIRTIO_DEV_ANY_ID } ,
{ }
} ;
MODULE_DEVICE_TABLE ( virtio , id_table ) ;
static int virtio_i2c_freeze ( struct virtio_device * vdev )
{
virtio_i2c_del_vqs ( vdev ) ;
return 0 ;
}
static int virtio_i2c_restore ( struct virtio_device * vdev )
{
return virtio_i2c_setup_vqs ( vdev - > priv ) ;
}
2021-10-21 12:47:49 +03:00
static const unsigned int features [ ] = {
VIRTIO_I2C_F_ZERO_LENGTH_REQUEST ,
} ;
2021-07-23 08:44:35 +03:00
static struct virtio_driver virtio_i2c_driver = {
2021-10-21 12:47:49 +03:00
. feature_table = features ,
. feature_table_size = ARRAY_SIZE ( features ) ,
. id_table = id_table ,
. probe = virtio_i2c_probe ,
. remove = virtio_i2c_remove ,
. driver = {
2021-07-23 08:44:35 +03:00
. name = " i2c_virtio " ,
} ,
2023-07-22 14:53:09 +03:00
. freeze = pm_sleep_ptr ( virtio_i2c_freeze ) ,
. restore = pm_sleep_ptr ( virtio_i2c_restore ) ,
2021-07-23 08:44:35 +03:00
} ;
module_virtio_driver ( virtio_i2c_driver ) ;
MODULE_AUTHOR ( " Jie Deng <jie.deng@intel.com> " ) ;
MODULE_AUTHOR ( " Conghui Chen <conghui.chen@intel.com> " ) ;
MODULE_DESCRIPTION ( " Virtio i2c bus driver " ) ;
MODULE_LICENSE ( " GPL " ) ;