2018-06-05 19:54:27 +02:00
// SPDX-License-Identifier: GPL-2.0
2016-07-15 16:28:44 -07:00
/*
* Expose a PWM controlled by the ChromeOS EC to the host processor .
2018-06-05 19:54:27 +02:00
*
* Copyright ( C ) 2016 Google , Inc .
2016-07-15 16:28:44 -07:00
*/
# include <linux/module.h>
2019-09-02 11:53:05 +02:00
# include <linux/platform_data/cros_ec_commands.h>
# include <linux/platform_data/cros_ec_proto.h>
2016-07-15 16:28:44 -07:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
2022-04-28 10:04:19 +00:00
# include <dt-bindings/mfd/cros_ec.h>
2016-07-15 16:28:44 -07:00
/**
* struct cros_ec_pwm_device - Driver data for EC PWM
*
* @ dev : Device node
* @ ec : Pointer to EC device
* @ chip : PWM controller chip
2022-04-28 10:04:19 +00:00
* @ use_pwm_type : Use PWM types instead of generic channels
2016-07-15 16:28:44 -07:00
*/
struct cros_ec_pwm_device {
struct device * dev ;
struct cros_ec_device * ec ;
struct pwm_chip chip ;
2022-04-28 10:04:19 +00:00
bool use_pwm_type ;
2016-07-15 16:28:44 -07:00
} ;
2019-10-17 13:21:15 +02:00
/**
* struct cros_ec_pwm - per - PWM driver data
* @ duty_cycle : cached duty cycle
*/
struct cros_ec_pwm {
u16 duty_cycle ;
} ;
2016-07-15 16:28:44 -07: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 13:21:15 +02: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 ) ;
}
2022-04-28 10:04:19 +00:00
static int cros_ec_dt_type_to_pwm_type ( u8 dt_index , u8 * pwm_type )
2016-07-15 16:28:44 -07:00
{
2022-04-28 10:04:19 +00:00
switch ( dt_index ) {
case CROS_EC_PWM_DT_KB_LIGHT :
* pwm_type = EC_PWM_TYPE_KB_LIGHT ;
return 0 ;
case CROS_EC_PWM_DT_DISPLAY_LIGHT :
* pwm_type = EC_PWM_TYPE_DISPLAY_LIGHT ;
return 0 ;
default :
return - EINVAL ;
}
}
static int cros_ec_pwm_set_duty ( struct cros_ec_pwm_device * ec_pwm , u8 index ,
u16 duty )
{
struct cros_ec_device * ec = ec_pwm - > ec ;
2016-07-15 16:28:44 -07:00
struct {
struct cros_ec_command msg ;
struct ec_params_pwm_set_duty params ;
2016-07-26 11:22:13 -07:00
} __packed buf ;
2016-07-15 16:28:44 -07:00
struct ec_params_pwm_set_duty * params = & buf . params ;
struct cros_ec_command * msg = & buf . msg ;
2022-04-28 10:04:19 +00:00
int ret ;
2016-07-15 16:28:44 -07:00
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 ;
2022-04-28 10:04:19 +00:00
if ( ec_pwm - > use_pwm_type ) {
ret = cros_ec_dt_type_to_pwm_type ( index , & params - > pwm_type ) ;
if ( ret ) {
dev_err ( ec - > dev , " Invalid PWM type index: %d \n " , index ) ;
return ret ;
}
params - > index = 0 ;
} else {
params - > pwm_type = EC_PWM_TYPE_GENERIC ;
params - > index = index ;
}
2016-07-15 16:28:44 -07:00
return cros_ec_cmd_xfer_status ( ec , msg ) ;
}
2022-04-28 10:04:19 +00:00
static int cros_ec_pwm_get_duty ( struct cros_ec_pwm_device * ec_pwm , u8 index )
2016-07-15 16:28:44 -07:00
{
2022-04-28 10:04:19 +00:00
struct cros_ec_device * ec = ec_pwm - > ec ;
2016-07-15 16:28:44 -07:00
struct {
struct cros_ec_command msg ;
union {
struct ec_params_pwm_get_duty params ;
struct ec_response_pwm_get_duty resp ;
} ;
2016-07-26 11:22:13 -07:00
} __packed buf ;
2016-07-15 16:28:44 -07: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-23 14:52:47 -07:00
msg - > insize = sizeof ( * resp ) ;
msg - > outsize = sizeof ( * params ) ;
2016-07-15 16:28:44 -07:00
2022-04-28 10:04:19 +00:00
if ( ec_pwm - > use_pwm_type ) {
ret = cros_ec_dt_type_to_pwm_type ( index , & params - > pwm_type ) ;
if ( ret ) {
dev_err ( ec - > dev , " Invalid PWM type index: %d \n " , index ) ;
return ret ;
}
params - > index = 0 ;
} else {
params - > pwm_type = EC_PWM_TYPE_GENERIC ;
params - > index = index ;
}
2016-07-15 16:28:44 -07:00
ret = cros_ec_cmd_xfer_status ( ec , msg ) ;
if ( ret < 0 )
return ret ;
return resp - > duty ;
}
static int cros_ec_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
2019-08-24 17:37:07 +02:00
const struct pwm_state * state )
2016-07-15 16:28:44 -07:00
{
struct cros_ec_pwm_device * ec_pwm = pwm_to_cros_ec_pwm ( chip ) ;
2019-10-17 13:21:15 +02:00
struct cros_ec_pwm * channel = pwm_get_chip_data ( pwm ) ;
u16 duty_cycle ;
int ret ;
2016-07-15 16:28:44 -07:00
/* The EC won't let us change the period */
if ( state - > period ! = EC_PWM_MAX_DUTY )
return - EINVAL ;
2021-03-12 10:00:58 +01:00
if ( state - > polarity ! = PWM_POLARITY_NORMAL )
return - EINVAL ;
2016-07-15 16:28:44 -07:00
/*
* EC doesn ' t separate the concept of duty cycle and enabled , but
* kernel does . Translate .
*/
duty_cycle = state - > enabled ? state - > duty_cycle : 0 ;
2022-04-28 10:04:19 +00:00
ret = cros_ec_pwm_set_duty ( ec_pwm , pwm - > hwpwm , duty_cycle ) ;
2019-10-17 13:21:15 +02:00
if ( ret < 0 )
return ret ;
channel - > duty_cycle = state - > duty_cycle ;
return 0 ;
2016-07-15 16:28:44 -07: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 13:21:15 +02:00
struct cros_ec_pwm * channel = pwm_get_chip_data ( pwm ) ;
2016-07-15 16:28:44 -07:00
int ret ;
2022-04-28 10:04:19 +00:00
ret = cros_ec_pwm_get_duty ( ec_pwm , pwm - > hwpwm ) ;
2016-07-15 16:28:44 -07:00
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 13:21:15 +02: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-15 16:28:44 -07: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 13:21:15 +02:00
. request = cros_ec_pwm_request ,
. free = cros_ec_pwm_free ,
2016-07-15 16:28:44 -07:00
. get_state = cros_ec_pwm_get_state ,
. apply = cros_ec_pwm_apply ,
. owner = THIS_MODULE ,
} ;
2020-08-22 08:08:54 -07:00
/*
* Determine the number of supported PWMs . The EC does not return the number
* of PWMs it supports directly , so we have to read the pwm duty cycle for
* subsequent channels until we get an error .
*/
2022-04-28 10:04:19 +00:00
static int cros_ec_num_pwms ( struct cros_ec_pwm_device * ec_pwm )
2016-07-15 16:28:44 -07:00
{
int i , ret ;
/* The index field is only 8 bits */
for ( i = 0 ; i < = U8_MAX ; i + + ) {
2022-04-28 10:04:19 +00:00
ret = cros_ec_pwm_get_duty ( ec_pwm , i ) ;
2016-07-15 16:28:44 -07:00
/*
* We look for SUCCESS , INVALID_COMMAND , or INVALID_PARAM
* responses ; everything else is treated as an error .
2020-08-22 08:08:57 -07:00
* The EC error codes map to - EOPNOTSUPP and - EINVAL ,
* so check for those .
2016-07-15 16:28:44 -07:00
*/
2020-08-22 08:08:54 -07:00
switch ( ret ) {
case - EOPNOTSUPP : /* invalid command */
2016-07-15 16:28:44 -07:00
return - ENODEV ;
2020-08-22 08:08:54 -07:00
case - EINVAL : /* invalid parameter */
2016-07-15 16:28:44 -07:00
return i ;
2020-08-22 08:08:54 -07:00
default :
if ( ret < 0 )
return ret ;
break ;
}
2016-07-15 16:28:44 -07:00
}
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 ;
2022-04-28 10:04:19 +00:00
struct device_node * np = pdev - > dev . of_node ;
2016-07-15 16:28:44 -07:00
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 ;
2022-04-28 10:04:19 +00:00
if ( of_device_is_compatible ( np , " google,cros-ec-pwm-type " ) )
ec_pwm - > use_pwm_type = true ;
2016-07-15 16:28:44 -07:00
/* PWM chip */
chip - > dev = dev ;
chip - > ops = & cros_ec_pwm_ops ;
chip - > of_xlate = cros_ec_pwm_xlate ;
chip - > of_pwm_n_cells = 1 ;
2022-04-28 10:04:19 +00:00
if ( ec_pwm - > use_pwm_type ) {
chip - > npwm = CROS_EC_PWM_DT_COUNT ;
} else {
ret = cros_ec_num_pwms ( ec_pwm ) ;
if ( ret < 0 ) {
dev_err ( dev , " Couldn't find PWMs: %d \n " , ret ) ;
return ret ;
}
chip - > npwm = ret ;
2016-07-15 16:28:44 -07:00
}
2022-04-28 10:04:19 +00:00
2016-07-15 16:28:44 -07:00
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 ;
2021-07-07 18:28:24 +02:00
pwmchip_remove ( chip ) ;
return 0 ;
2016-07-15 16:28:44 -07:00
}
# ifdef CONFIG_OF
static const struct of_device_id cros_ec_pwm_of_match [ ] = {
{ . compatible = " google,cros-ec-pwm " } ,
2022-04-28 10:04:19 +00:00
{ . compatible = " google,cros-ec-pwm-type " } ,
2016-07-15 16:28:44 -07:00
{ } ,
} ;
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 " ) ;