2018-06-05 20:54:27 +03:00
// SPDX-License-Identifier: GPL-2.0
2016-07-16 02:28:44 +03:00
/*
* Expose a PWM controlled by the ChromeOS EC to the host processor .
2018-06-05 20:54:27 +03:00
*
* Copyright ( C ) 2016 Google , Inc .
2016-07-16 02:28:44 +03:00
*/
# include <linux/module.h>
2019-09-02 12:53:05 +03:00
# include <linux/platform_data/cros_ec_commands.h>
# include <linux/platform_data/cros_ec_proto.h>
2016-07-16 02:28:44 +03:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
/**
* struct cros_ec_pwm_device - Driver data for EC PWM
*
* @ dev : Device node
* @ ec : Pointer to EC device
* @ chip : PWM controller chip
*/
struct cros_ec_pwm_device {
struct device * dev ;
struct cros_ec_device * ec ;
struct pwm_chip chip ;
} ;
2019-10-17 14:21:15 +03:00
/**
* struct cros_ec_pwm - per - PWM driver data
* @ duty_cycle : cached duty cycle
*/
struct cros_ec_pwm {
u16 duty_cycle ;
} ;
2016-07-16 02:28:44 +03:00
static inline struct cros_ec_pwm_device * pwm_to_cros_ec_pwm ( struct pwm_chip * c )
{
return container_of ( c , struct cros_ec_pwm_device , chip ) ;
}
2019-10-17 14:21:15 +03:00
static int cros_ec_pwm_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct cros_ec_pwm * channel ;
channel = kzalloc ( sizeof ( * channel ) , GFP_KERNEL ) ;
if ( ! channel )
return - ENOMEM ;
pwm_set_chip_data ( pwm , channel ) ;
return 0 ;
}
static void cros_ec_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct cros_ec_pwm * channel = pwm_get_chip_data ( pwm ) ;
kfree ( channel ) ;
}
2016-07-16 02:28:44 +03:00
static int cros_ec_pwm_set_duty ( struct cros_ec_device * ec , u8 index , u16 duty )
{
struct {
struct cros_ec_command msg ;
struct ec_params_pwm_set_duty params ;
2016-07-26 21:22:13 +03:00
} __packed buf ;
2016-07-16 02:28:44 +03:00
struct ec_params_pwm_set_duty * params = & buf . params ;
struct cros_ec_command * msg = & buf . msg ;
memset ( & buf , 0 , sizeof ( buf ) ) ;
msg - > version = 0 ;
msg - > command = EC_CMD_PWM_SET_DUTY ;
msg - > insize = 0 ;
msg - > outsize = sizeof ( * params ) ;
params - > duty = duty ;
params - > pwm_type = EC_PWM_TYPE_GENERIC ;
params - > index = index ;
return cros_ec_cmd_xfer_status ( ec , msg ) ;
}
static int __cros_ec_pwm_get_duty ( struct cros_ec_device * ec , u8 index ,
u32 * result )
{
struct {
struct cros_ec_command msg ;
union {
struct ec_params_pwm_get_duty params ;
struct ec_response_pwm_get_duty resp ;
} ;
2016-07-26 21:22:13 +03:00
} __packed buf ;
2016-07-16 02:28:44 +03:00
struct ec_params_pwm_get_duty * params = & buf . params ;
struct ec_response_pwm_get_duty * resp = & buf . resp ;
struct cros_ec_command * msg = & buf . msg ;
int ret ;
memset ( & buf , 0 , sizeof ( buf ) ) ;
msg - > version = 0 ;
msg - > command = EC_CMD_PWM_GET_DUTY ;
2017-06-24 00:52:47 +03:00
msg - > insize = sizeof ( * resp ) ;
msg - > outsize = sizeof ( * params ) ;
2016-07-16 02:28:44 +03:00
params - > pwm_type = EC_PWM_TYPE_GENERIC ;
params - > index = index ;
ret = cros_ec_cmd_xfer_status ( ec , msg ) ;
if ( result )
* result = msg - > result ;
if ( ret < 0 )
return ret ;
return resp - > duty ;
}
static int cros_ec_pwm_get_duty ( struct cros_ec_device * ec , u8 index )
{
return __cros_ec_pwm_get_duty ( ec , index , NULL ) ;
}
static int cros_ec_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
2019-08-24 18:37:07 +03:00
const struct pwm_state * state )
2016-07-16 02:28:44 +03:00
{
struct cros_ec_pwm_device * ec_pwm = pwm_to_cros_ec_pwm ( chip ) ;
2019-10-17 14:21:15 +03:00
struct cros_ec_pwm * channel = pwm_get_chip_data ( pwm ) ;
u16 duty_cycle ;
int ret ;
2016-07-16 02:28:44 +03:00
/* The EC won't let us change the period */
if ( state - > period ! = EC_PWM_MAX_DUTY )
return - EINVAL ;
/*
* EC doesn ' t separate the concept of duty cycle and enabled , but
* kernel does . Translate .
*/
duty_cycle = state - > enabled ? state - > duty_cycle : 0 ;
2019-10-17 14:21:15 +03:00
ret = cros_ec_pwm_set_duty ( ec_pwm - > ec , pwm - > hwpwm , duty_cycle ) ;
if ( ret < 0 )
return ret ;
channel - > duty_cycle = state - > duty_cycle ;
return 0 ;
2016-07-16 02:28:44 +03:00
}
static void cros_ec_pwm_get_state ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
{
struct cros_ec_pwm_device * ec_pwm = pwm_to_cros_ec_pwm ( chip ) ;
2019-10-17 14:21:15 +03:00
struct cros_ec_pwm * channel = pwm_get_chip_data ( pwm ) ;
2016-07-16 02:28:44 +03:00
int ret ;
ret = cros_ec_pwm_get_duty ( ec_pwm - > ec , pwm - > hwpwm ) ;
if ( ret < 0 ) {
dev_err ( chip - > dev , " error getting initial duty: %d \n " , ret ) ;
return ;
}
state - > enabled = ( ret > 0 ) ;
state - > period = EC_PWM_MAX_DUTY ;
2019-10-17 14:21:15 +03:00
/*
* Note that " disabled " and " duty cycle == 0 " are treated the same . If
* the cached duty cycle is not zero , used the cached duty cycle . This
* ensures that the configured duty cycle is kept across a disable and
* enable operation and avoids potentially confusing consumers .
*
* For the case of the initial hardware readout , channel - > duty_cycle
* will be 0 and the actual duty cycle read from the EC is used .
*/
if ( ret = = 0 & & channel - > duty_cycle > 0 )
state - > duty_cycle = channel - > duty_cycle ;
else
state - > duty_cycle = ret ;
2016-07-16 02:28:44 +03:00
}
static struct pwm_device *
cros_ec_pwm_xlate ( struct pwm_chip * pc , const struct of_phandle_args * args )
{
struct pwm_device * pwm ;
if ( args - > args [ 0 ] > = pc - > npwm )
return ERR_PTR ( - EINVAL ) ;
pwm = pwm_request_from_chip ( pc , args - > args [ 0 ] , NULL ) ;
if ( IS_ERR ( pwm ) )
return pwm ;
/* The EC won't let us change the period */
pwm - > args . period = EC_PWM_MAX_DUTY ;
return pwm ;
}
static const struct pwm_ops cros_ec_pwm_ops = {
2019-10-17 14:21:15 +03:00
. request = cros_ec_pwm_request ,
. free = cros_ec_pwm_free ,
2016-07-16 02:28:44 +03:00
. get_state = cros_ec_pwm_get_state ,
. apply = cros_ec_pwm_apply ,
. owner = THIS_MODULE ,
} ;
static int cros_ec_num_pwms ( struct cros_ec_device * ec )
{
int i , ret ;
/* The index field is only 8 bits */
for ( i = 0 ; i < = U8_MAX ; i + + ) {
u32 result = 0 ;
ret = __cros_ec_pwm_get_duty ( ec , i , & result ) ;
/* We want to parse EC protocol errors */
if ( ret < 0 & & ! ( ret = = - EPROTO & & result ) )
return ret ;
/*
* We look for SUCCESS , INVALID_COMMAND , or INVALID_PARAM
* responses ; everything else is treated as an error .
*/
if ( result = = EC_RES_INVALID_COMMAND )
return - ENODEV ;
else if ( result = = EC_RES_INVALID_PARAM )
return i ;
else if ( result )
return - EPROTO ;
}
return U8_MAX ;
}
static int cros_ec_pwm_probe ( struct platform_device * pdev )
{
struct cros_ec_device * ec = dev_get_drvdata ( pdev - > dev . parent ) ;
struct device * dev = & pdev - > dev ;
struct cros_ec_pwm_device * ec_pwm ;
struct pwm_chip * chip ;
int ret ;
if ( ! ec ) {
dev_err ( dev , " no parent EC device \n " ) ;
return - EINVAL ;
}
ec_pwm = devm_kzalloc ( dev , sizeof ( * ec_pwm ) , GFP_KERNEL ) ;
if ( ! ec_pwm )
return - ENOMEM ;
chip = & ec_pwm - > chip ;
ec_pwm - > ec = ec ;
/* PWM chip */
chip - > dev = dev ;
chip - > ops = & cros_ec_pwm_ops ;
chip - > of_xlate = cros_ec_pwm_xlate ;
chip - > of_pwm_n_cells = 1 ;
chip - > base = - 1 ;
ret = cros_ec_num_pwms ( ec ) ;
if ( ret < 0 ) {
dev_err ( dev , " Couldn't find PWMs: %d \n " , ret ) ;
return ret ;
}
chip - > npwm = ret ;
dev_dbg ( dev , " Probed %u PWMs \n " , chip - > npwm ) ;
ret = pwmchip_add ( chip ) ;
if ( ret < 0 ) {
dev_err ( dev , " cannot register PWM: %d \n " , ret ) ;
return ret ;
}
platform_set_drvdata ( pdev , ec_pwm ) ;
return ret ;
}
static int cros_ec_pwm_remove ( struct platform_device * dev )
{
struct cros_ec_pwm_device * ec_pwm = platform_get_drvdata ( dev ) ;
struct pwm_chip * chip = & ec_pwm - > chip ;
return pwmchip_remove ( chip ) ;
}
# ifdef CONFIG_OF
static const struct of_device_id cros_ec_pwm_of_match [ ] = {
{ . compatible = " google,cros-ec-pwm " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cros_ec_pwm_of_match ) ;
# endif
static struct platform_driver cros_ec_pwm_driver = {
. probe = cros_ec_pwm_probe ,
. remove = cros_ec_pwm_remove ,
. driver = {
. name = " cros-ec-pwm " ,
. of_match_table = of_match_ptr ( cros_ec_pwm_of_match ) ,
} ,
} ;
module_platform_driver ( cros_ec_pwm_driver ) ;
MODULE_ALIAS ( " platform:cros-ec-pwm " ) ;
MODULE_DESCRIPTION ( " ChromeOS EC PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;