2017-12-19 12:00:13 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2015 - 2017 Pengutronix , Uwe Kleine - König < kernel @ pengutronix . de >
*/
# include <linux/gpio/consumer.h>
# include <linux/module.h>
2018-06-20 08:47:28 +03:00
# include <linux/mod_devicetable.h>
2017-12-19 12:00:13 +03:00
# include <linux/platform_device.h>
# include <linux/delay.h>
# include "siox.h"
# define DRIVER_NAME "siox-gpio"
struct siox_gpio_ddata {
struct gpio_desc * din ;
struct gpio_desc * dout ;
struct gpio_desc * dclk ;
struct gpio_desc * dld ;
} ;
static unsigned int siox_clkhigh_ns = 1000 ;
static unsigned int siox_loadhigh_ns ;
static unsigned int siox_bytegap_ns ;
static int siox_gpio_pushpull ( struct siox_master * smaster ,
size_t setbuf_len , const u8 setbuf [ ] ,
size_t getbuf_len , u8 getbuf [ ] )
{
struct siox_gpio_ddata * ddata = siox_master_get_devdata ( smaster ) ;
size_t i ;
size_t cycles = max ( setbuf_len , getbuf_len ) ;
/* reset data and clock */
gpiod_set_value_cansleep ( ddata - > dout , 0 ) ;
gpiod_set_value_cansleep ( ddata - > dclk , 0 ) ;
gpiod_set_value_cansleep ( ddata - > dld , 1 ) ;
ndelay ( siox_loadhigh_ns ) ;
gpiod_set_value_cansleep ( ddata - > dld , 0 ) ;
for ( i = 0 ; i < cycles ; + + i ) {
u8 set = 0 , get = 0 ;
size_t j ;
if ( i > = cycles - setbuf_len )
set = setbuf [ i - ( cycles - setbuf_len ) ] ;
for ( j = 0 ; j < 8 ; + + j ) {
get < < = 1 ;
if ( gpiod_get_value_cansleep ( ddata - > din ) )
get | = 1 ;
/* DOUT is logically inverted */
gpiod_set_value_cansleep ( ddata - > dout , ! ( set & 0x80 ) ) ;
set < < = 1 ;
gpiod_set_value_cansleep ( ddata - > dclk , 1 ) ;
ndelay ( siox_clkhigh_ns ) ;
gpiod_set_value_cansleep ( ddata - > dclk , 0 ) ;
}
if ( i < getbuf_len )
getbuf [ i ] = get ;
ndelay ( siox_bytegap_ns ) ;
}
gpiod_set_value_cansleep ( ddata - > dld , 1 ) ;
ndelay ( siox_loadhigh_ns ) ;
gpiod_set_value_cansleep ( ddata - > dld , 0 ) ;
/*
* Resetting dout isn ' t necessary protocol wise , but it makes the
* signals more pretty because the dout level is deterministic between
* cycles . Note that this only affects dout between the master and the
* first siox device . dout for the later devices depend on the output of
* the previous siox device .
*/
gpiod_set_value_cansleep ( ddata - > dout , 0 ) ;
return 0 ;
}
static int siox_gpio_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct siox_gpio_ddata * ddata ;
int ret ;
struct siox_master * smaster ;
smaster = siox_master_alloc ( & pdev - > dev , sizeof ( * ddata ) ) ;
if ( ! smaster ) {
dev_err ( dev , " failed to allocate siox master \n " ) ;
return - ENOMEM ;
}
platform_set_drvdata ( pdev , smaster ) ;
ddata = siox_master_get_devdata ( smaster ) ;
ddata - > din = devm_gpiod_get ( dev , " din " , GPIOD_IN ) ;
if ( IS_ERR ( ddata - > din ) ) {
ret = PTR_ERR ( ddata - > din ) ;
dev_err ( dev , " Failed to get %s GPIO: %d \n " , " din " , ret ) ;
goto err ;
}
ddata - > dout = devm_gpiod_get ( dev , " dout " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( ddata - > dout ) ) {
ret = PTR_ERR ( ddata - > dout ) ;
dev_err ( dev , " Failed to get %s GPIO: %d \n " , " dout " , ret ) ;
goto err ;
}
ddata - > dclk = devm_gpiod_get ( dev , " dclk " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( ddata - > dclk ) ) {
ret = PTR_ERR ( ddata - > dclk ) ;
dev_err ( dev , " Failed to get %s GPIO: %d \n " , " dclk " , ret ) ;
goto err ;
}
ddata - > dld = devm_gpiod_get ( dev , " dld " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( ddata - > dld ) ) {
ret = PTR_ERR ( ddata - > dld ) ;
dev_err ( dev , " Failed to get %s GPIO: %d \n " , " dld " , ret ) ;
goto err ;
}
smaster - > pushpull = siox_gpio_pushpull ;
/* XXX: determine automatically like spi does */
smaster - > busno = 0 ;
ret = siox_master_register ( smaster ) ;
if ( ret ) {
dev_err ( dev , " Failed to register siox master: %d \n " , ret ) ;
err :
siox_master_put ( smaster ) ;
}
return ret ;
}
static int siox_gpio_remove ( struct platform_device * pdev )
{
struct siox_master * master = platform_get_drvdata ( pdev ) ;
siox_master_unregister ( master ) ;
return 0 ;
}
static const struct of_device_id siox_gpio_dt_ids [ ] = {
{ . compatible = " eckelmann,siox-gpio " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , siox_gpio_dt_ids ) ;
static struct platform_driver siox_gpio_driver = {
. probe = siox_gpio_probe ,
. remove = siox_gpio_remove ,
. driver = {
. name = DRIVER_NAME ,
. of_match_table = siox_gpio_dt_ids ,
} ,
} ;
module_platform_driver ( siox_gpio_driver ) ;
MODULE_AUTHOR ( " Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;