2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2009-02-10 14:12:29 +01:00
/*
* Battery and Power Management code for the Sharp SL - 5 x00
*
* Copyright ( C ) 2009 Thomas Kunze
*
* based on tosa_battery . c
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/power_supply.h>
# include <linux/delay.h>
# include <linux/spinlock.h>
# include <linux/interrupt.h>
2020-10-30 15:34:01 +01:00
# include <linux/gpio/driver.h>
# include <linux/gpio/machine.h>
# include <linux/gpio/consumer.h>
2009-02-10 14:12:29 +01:00
# include <linux/mfd/ucb1x00.h>
# include <asm/mach/sharpsl_param.h>
# include <asm/mach-types.h>
# include <mach/collie.h>
static DEFINE_MUTEX ( bat_lock ) ; /* protects gpio pins */
static struct work_struct bat_work ;
static struct ucb1x00 * ucb ;
struct collie_bat {
int status ;
2015-03-12 08:44:11 +01:00
struct power_supply * psy ;
2009-02-10 14:12:29 +01:00
int full_chrg ;
struct mutex work_lock ; /* protects data */
bool ( * is_present ) ( struct collie_bat * bat ) ;
2020-10-30 15:34:01 +01:00
struct gpio_desc * gpio_full ;
struct gpio_desc * gpio_charge_on ;
2009-02-10 14:12:29 +01:00
int technology ;
2020-10-30 15:34:01 +01:00
struct gpio_desc * gpio_bat ;
2009-02-10 14:12:29 +01:00
int adc_bat ;
int adc_bat_divider ;
int bat_max ;
int bat_min ;
2020-10-30 15:34:01 +01:00
struct gpio_desc * gpio_temp ;
2009-02-10 14:12:29 +01:00
int adc_temp ;
int adc_temp_divider ;
} ;
static struct collie_bat collie_bat_main ;
static unsigned long collie_read_bat ( struct collie_bat * bat )
{
unsigned long value = 0 ;
2020-10-30 15:34:01 +01:00
if ( ! bat - > gpio_bat | | bat - > adc_bat < 0 )
2009-02-10 14:12:29 +01:00
return 0 ;
mutex_lock ( & bat_lock ) ;
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_bat , 1 ) ;
2009-02-10 14:12:29 +01:00
msleep ( 5 ) ;
ucb1x00_adc_enable ( ucb ) ;
value = ucb1x00_adc_read ( ucb , bat - > adc_bat , UCB_SYNC ) ;
ucb1x00_adc_disable ( ucb ) ;
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_bat , 0 ) ;
2009-02-10 14:12:29 +01:00
mutex_unlock ( & bat_lock ) ;
value = value * 1000000 / bat - > adc_bat_divider ;
return value ;
}
static unsigned long collie_read_temp ( struct collie_bat * bat )
{
unsigned long value = 0 ;
2020-10-30 15:34:01 +01:00
if ( ! bat - > gpio_temp | | bat - > adc_temp < 0 )
2009-02-10 14:12:29 +01:00
return 0 ;
mutex_lock ( & bat_lock ) ;
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_temp , 1 ) ;
2009-02-10 14:12:29 +01:00
msleep ( 5 ) ;
ucb1x00_adc_enable ( ucb ) ;
value = ucb1x00_adc_read ( ucb , bat - > adc_temp , UCB_SYNC ) ;
ucb1x00_adc_disable ( ucb ) ;
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_temp , 0 ) ;
2009-02-10 14:12:29 +01:00
mutex_unlock ( & bat_lock ) ;
value = value * 10000 / bat - > adc_temp_divider ;
return value ;
}
static int collie_bat_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
int ret = 0 ;
2015-03-12 08:44:11 +01:00
struct collie_bat * bat = power_supply_get_drvdata ( psy ) ;
2009-02-10 14:12:29 +01:00
if ( bat - > is_present & & ! bat - > is_present ( bat )
& & psp ! = POWER_SUPPLY_PROP_PRESENT ) {
return - ENODEV ;
}
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
val - > intval = bat - > status ;
break ;
case POWER_SUPPLY_PROP_TECHNOLOGY :
val - > intval = bat - > technology ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
val - > intval = collie_read_bat ( bat ) ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX :
if ( bat - > full_chrg = = - 1 )
val - > intval = bat - > bat_max ;
else
val - > intval = bat - > full_chrg ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN :
val - > intval = bat - > bat_max ;
break ;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN :
val - > intval = bat - > bat_min ;
break ;
case POWER_SUPPLY_PROP_TEMP :
val - > intval = collie_read_temp ( bat ) ;
break ;
case POWER_SUPPLY_PROP_PRESENT :
val - > intval = bat - > is_present ? bat - > is_present ( bat ) : 1 ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
static void collie_bat_external_power_changed ( struct power_supply * psy )
{
schedule_work ( & bat_work ) ;
}
static irqreturn_t collie_bat_gpio_isr ( int irq , void * data )
{
2011-11-19 19:26:37 +01:00
pr_info ( " collie_bat_gpio irq \n " ) ;
2009-02-10 14:12:29 +01:00
schedule_work ( & bat_work ) ;
return IRQ_HANDLED ;
}
static void collie_bat_update ( struct collie_bat * bat )
{
int old ;
2015-03-12 08:44:11 +01:00
struct power_supply * psy = bat - > psy ;
2009-02-10 14:12:29 +01:00
mutex_lock ( & bat - > work_lock ) ;
old = bat - > status ;
if ( bat - > is_present & & ! bat - > is_present ( bat ) ) {
2015-03-12 08:44:11 +01:00
printk ( KERN_NOTICE " %s not present \n " , psy - > desc - > name ) ;
2009-02-10 14:12:29 +01:00
bat - > status = POWER_SUPPLY_STATUS_UNKNOWN ;
bat - > full_chrg = - 1 ;
} else if ( power_supply_am_i_supplied ( psy ) ) {
if ( bat - > status = = POWER_SUPPLY_STATUS_DISCHARGING ) {
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_charge_on , 1 ) ;
2009-02-10 14:12:29 +01:00
mdelay ( 15 ) ;
}
2020-10-30 15:34:01 +01:00
if ( gpiod_get_value ( bat - > gpio_full ) ) {
2009-02-10 14:12:29 +01:00
if ( old = = POWER_SUPPLY_STATUS_CHARGING | |
bat - > full_chrg = = - 1 )
bat - > full_chrg = collie_read_bat ( bat ) ;
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_charge_on , 0 ) ;
2009-02-10 14:12:29 +01:00
bat - > status = POWER_SUPPLY_STATUS_FULL ;
} else {
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_charge_on , 1 ) ;
2009-02-10 14:12:29 +01:00
bat - > status = POWER_SUPPLY_STATUS_CHARGING ;
}
} else {
2020-10-30 15:34:01 +01:00
gpiod_set_value ( bat - > gpio_charge_on , 0 ) ;
2009-02-10 14:12:29 +01:00
bat - > status = POWER_SUPPLY_STATUS_DISCHARGING ;
}
if ( old ! = bat - > status )
power_supply_changed ( psy ) ;
mutex_unlock ( & bat - > work_lock ) ;
}
static void collie_bat_work ( struct work_struct * work )
{
collie_bat_update ( & collie_bat_main ) ;
}
static enum power_supply_property collie_bat_main_props [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_MAX ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_TEMP ,
} ;
static enum power_supply_property collie_bat_bu_props [ ] = {
POWER_SUPPLY_PROP_STATUS ,
POWER_SUPPLY_PROP_TECHNOLOGY ,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ,
POWER_SUPPLY_PROP_VOLTAGE_MAX ,
POWER_SUPPLY_PROP_PRESENT ,
} ;
2015-03-12 08:44:11 +01:00
static const struct power_supply_desc collie_bat_main_desc = {
. name = " main-battery " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = collie_bat_main_props ,
. num_properties = ARRAY_SIZE ( collie_bat_main_props ) ,
. get_property = collie_bat_get_property ,
. external_power_changed = collie_bat_external_power_changed ,
. use_for_apm = 1 ,
} ;
2009-02-10 14:12:29 +01:00
static struct collie_bat collie_bat_main = {
. status = POWER_SUPPLY_STATUS_DISCHARGING ,
. full_chrg = - 1 ,
2015-03-12 08:44:11 +01:00
. psy = NULL ,
2009-02-10 14:12:29 +01:00
2020-10-30 15:34:01 +01:00
. gpio_full = NULL ,
. gpio_charge_on = NULL ,
2009-02-10 14:12:29 +01:00
. technology = POWER_SUPPLY_TECHNOLOGY_LIPO ,
2020-10-30 15:34:01 +01:00
. gpio_bat = NULL ,
2009-02-10 14:12:29 +01:00
. adc_bat = UCB_ADC_INP_AD1 ,
. adc_bat_divider = 155 ,
. bat_max = 4310000 ,
. bat_min = 1551 * 1000000 / 414 ,
2020-10-30 15:34:01 +01:00
. gpio_temp = NULL ,
2009-02-10 14:12:29 +01:00
. adc_temp = UCB_ADC_INP_AD0 ,
. adc_temp_divider = 10000 ,
} ;
2015-03-12 08:44:11 +01:00
static const struct power_supply_desc collie_bat_bu_desc = {
. name = " backup-battery " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = collie_bat_bu_props ,
. num_properties = ARRAY_SIZE ( collie_bat_bu_props ) ,
. get_property = collie_bat_get_property ,
. external_power_changed = collie_bat_external_power_changed ,
} ;
2009-02-10 14:12:29 +01:00
static struct collie_bat collie_bat_bu = {
. status = POWER_SUPPLY_STATUS_UNKNOWN ,
. full_chrg = - 1 ,
2015-03-12 08:44:11 +01:00
. psy = NULL ,
2009-02-10 14:12:29 +01:00
2020-10-30 15:34:01 +01:00
. gpio_full = NULL ,
. gpio_charge_on = NULL ,
2009-02-10 14:12:29 +01:00
. technology = POWER_SUPPLY_TECHNOLOGY_LiMn ,
2020-10-30 15:34:01 +01:00
. gpio_bat = NULL ,
2009-02-10 14:12:29 +01:00
. adc_bat = UCB_ADC_INP_AD1 ,
. adc_bat_divider = 155 ,
. bat_max = 3000000 ,
. bat_min = 1900000 ,
2020-10-30 15:34:01 +01:00
. gpio_temp = NULL ,
2009-02-10 14:12:29 +01:00
. adc_temp = - 1 ,
. adc_temp_divider = - 1 ,
} ;
2020-10-30 15:34:01 +01:00
/* Obtained but unused GPIO */
static struct gpio_desc * collie_mbat_low ;
2009-02-10 14:12:29 +01:00
# ifdef CONFIG_PM
2016-01-25 16:45:49 +01:00
static int wakeup_enabled ;
2013-07-21 01:07:44 +02:00
static int collie_bat_suspend ( struct ucb1x00_dev * dev )
2009-02-10 14:12:29 +01:00
{
/* flush all pending status updates */
2012-08-20 14:51:24 -07:00
flush_work ( & bat_work ) ;
2015-01-15 05:00:38 +03:00
if ( device_may_wakeup ( & dev - > ucb - > dev ) & &
collie_bat_main . status = = POWER_SUPPLY_STATUS_CHARGING )
2020-10-30 15:34:01 +01:00
wakeup_enabled = ! enable_irq_wake ( gpiod_to_irq ( collie_bat_main . gpio_full ) ) ;
2015-01-15 05:00:38 +03:00
else
wakeup_enabled = 0 ;
2009-02-10 14:12:29 +01:00
return 0 ;
}
static int collie_bat_resume ( struct ucb1x00_dev * dev )
{
2015-01-15 05:00:38 +03:00
if ( wakeup_enabled )
2020-10-30 15:34:01 +01:00
disable_irq_wake ( gpiod_to_irq ( collie_bat_main . gpio_full ) ) ;
2015-01-15 05:00:38 +03:00
2009-02-10 14:12:29 +01:00
/* things may have changed while we were away */
schedule_work ( & bat_work ) ;
return 0 ;
}
# else
# define collie_bat_suspend NULL
# define collie_bat_resume NULL
# endif
2012-11-19 13:22:23 -05:00
static int collie_bat_probe ( struct ucb1x00_dev * dev )
2009-02-10 14:12:29 +01:00
{
int ret ;
2015-03-12 08:44:11 +01:00
struct power_supply_config psy_main_cfg = { } , psy_bu_cfg = { } ;
2020-10-30 15:34:01 +01:00
struct gpio_chip * gc = & dev - > ucb - > gpio ;
2009-02-10 14:12:29 +01:00
if ( ! machine_is_collie ( ) )
return - ENODEV ;
ucb = dev - > ucb ;
2020-10-30 15:34:01 +01:00
/* Obtain all the main battery GPIOs */
collie_bat_main . gpio_full = gpiod_get ( & dev - > ucb - > dev ,
" main battery full " ,
GPIOD_IN ) ;
if ( IS_ERR ( collie_bat_main . gpio_full ) )
return PTR_ERR ( collie_bat_main . gpio_full ) ;
collie_mbat_low = gpiod_get ( & dev - > ucb - > dev ,
" main battery low " ,
GPIOD_IN ) ;
if ( IS_ERR ( collie_mbat_low ) ) {
ret = PTR_ERR ( collie_mbat_low ) ;
goto err_put_gpio_full ;
}
collie_bat_main . gpio_charge_on = gpiod_get ( & dev - > ucb - > dev ,
" main charge on " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( collie_bat_main . gpio_charge_on ) ) {
ret = PTR_ERR ( collie_bat_main . gpio_charge_on ) ;
goto err_put_mbat_low ;
}
/* COLLIE_GPIO_MBAT_ON = GPIO 7 on the UCB (TC35143) */
collie_bat_main . gpio_bat = gpiochip_request_own_desc ( gc ,
7 ,
" main battery " ,
GPIO_ACTIVE_HIGH ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( collie_bat_main . gpio_bat ) ) {
ret = PTR_ERR ( collie_bat_main . gpio_bat ) ;
goto err_put_gpio_charge_on ;
}
/* COLLIE_GPIO_TMP_ON = GPIO 9 on the UCB (TC35143) */
collie_bat_main . gpio_temp = gpiochip_request_own_desc ( gc ,
9 ,
" main battery temp " ,
GPIO_ACTIVE_HIGH ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( collie_bat_main . gpio_temp ) ) {
ret = PTR_ERR ( collie_bat_main . gpio_temp ) ;
goto err_free_gpio_bat ;
}
/*
* Obtain the backup battery COLLIE_GPIO_BBAT_ON which is
* GPIO 8 on the UCB ( TC35143 )
*/
collie_bat_bu . gpio_bat = gpiochip_request_own_desc ( gc ,
8 ,
" backup battery " ,
GPIO_ACTIVE_HIGH ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( collie_bat_bu . gpio_bat ) ) {
ret = PTR_ERR ( collie_bat_bu . gpio_bat ) ;
goto err_free_gpio_temp ;
}
2009-02-10 14:12:29 +01:00
mutex_init ( & collie_bat_main . work_lock ) ;
INIT_WORK ( & bat_work , collie_bat_work ) ;
2015-03-12 08:44:11 +01:00
psy_main_cfg . drv_data = & collie_bat_main ;
collie_bat_main . psy = power_supply_register ( & dev - > ucb - > dev ,
& collie_bat_main_desc ,
& psy_main_cfg ) ;
if ( IS_ERR ( collie_bat_main . psy ) ) {
ret = PTR_ERR ( collie_bat_main . psy ) ;
2009-02-10 14:12:29 +01:00
goto err_psy_reg_main ;
2015-03-12 08:44:11 +01:00
}
2015-04-27 20:15:43 +03:00
psy_bu_cfg . drv_data = & collie_bat_bu ;
2015-03-12 08:44:11 +01:00
collie_bat_bu . psy = power_supply_register ( & dev - > ucb - > dev ,
& collie_bat_bu_desc ,
& psy_bu_cfg ) ;
if ( IS_ERR ( collie_bat_bu . psy ) ) {
ret = PTR_ERR ( collie_bat_bu . psy ) ;
2009-02-10 14:12:29 +01:00
goto err_psy_reg_bu ;
2015-03-12 08:44:11 +01:00
}
2009-02-10 14:12:29 +01:00
ret = request_irq ( gpio_to_irq ( COLLIE_GPIO_CO ) ,
collie_bat_gpio_isr ,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ,
" main full " , & collie_bat_main ) ;
2015-01-15 05:00:38 +03:00
if ( ret )
goto err_irq ;
device_init_wakeup ( & ucb - > dev , 1 ) ;
schedule_work ( & bat_work ) ;
return 0 ;
err_irq :
2015-03-12 08:44:11 +01:00
power_supply_unregister ( collie_bat_bu . psy ) ;
2009-02-10 14:12:29 +01:00
err_psy_reg_bu :
2015-03-12 08:44:11 +01:00
power_supply_unregister ( collie_bat_main . psy ) ;
2009-02-10 14:12:29 +01:00
err_psy_reg_main :
/* see comment in collie_bat_remove */
2010-12-11 17:51:45 +01:00
cancel_work_sync ( & bat_work ) ;
2020-10-30 15:34:01 +01:00
gpiochip_free_own_desc ( collie_bat_bu . gpio_bat ) ;
err_free_gpio_temp :
gpiochip_free_own_desc ( collie_bat_main . gpio_temp ) ;
err_free_gpio_bat :
gpiochip_free_own_desc ( collie_bat_main . gpio_bat ) ;
err_put_gpio_charge_on :
gpiod_put ( collie_bat_main . gpio_charge_on ) ;
err_put_mbat_low :
gpiod_put ( collie_mbat_low ) ;
err_put_gpio_full :
gpiod_put ( collie_bat_main . gpio_full ) ;
2009-02-10 14:12:29 +01:00
return ret ;
}
2012-11-19 13:26:07 -05:00
static void collie_bat_remove ( struct ucb1x00_dev * dev )
2009-02-10 14:12:29 +01:00
{
free_irq ( gpio_to_irq ( COLLIE_GPIO_CO ) , & collie_bat_main ) ;
2015-03-12 08:44:11 +01:00
power_supply_unregister ( collie_bat_bu . psy ) ;
power_supply_unregister ( collie_bat_main . psy ) ;
2009-02-10 14:12:29 +01:00
2020-10-30 15:34:01 +01:00
/* These are obtained from the machine */
gpiod_put ( collie_bat_main . gpio_full ) ;
gpiod_put ( collie_mbat_low ) ;
gpiod_put ( collie_bat_main . gpio_charge_on ) ;
/* These are directly from the UCB so let's free them */
gpiochip_free_own_desc ( collie_bat_main . gpio_bat ) ;
gpiochip_free_own_desc ( collie_bat_main . gpio_temp ) ;
gpiochip_free_own_desc ( collie_bat_bu . gpio_bat ) ;
2009-02-10 14:12:29 +01:00
/*
2010-12-11 17:51:45 +01:00
* Now cancel the bat_work . We won ' t get any more schedules ,
* since all sources ( isr and external_power_changed ) are
* unregistered now .
2009-02-10 14:12:29 +01:00
*/
2010-12-11 17:51:45 +01:00
cancel_work_sync ( & bat_work ) ;
2009-02-10 14:12:29 +01:00
}
static struct ucb1x00_driver collie_bat_driver = {
. add = collie_bat_probe ,
2012-11-19 13:20:40 -05:00
. remove = collie_bat_remove ,
2009-02-10 14:12:29 +01:00
. suspend = collie_bat_suspend ,
. resume = collie_bat_resume ,
} ;
static int __init collie_bat_init ( void )
{
return ucb1x00_register_driver ( & collie_bat_driver ) ;
}
static void __exit collie_bat_exit ( void )
{
ucb1x00_unregister_driver ( & collie_bat_driver ) ;
}
module_init ( collie_bat_init ) ;
module_exit ( collie_bat_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Thomas Kunze " ) ;
MODULE_DESCRIPTION ( " Collie battery driver " ) ;