brintos

brintos / linux-shallow public Read only

0
0
Text · 11.8 KiB · 68390bd Raw
486 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Battery and Power Management code for the Sharp SL-5x004 *5 * Copyright (C) 2009 Thomas Kunze6 *7 * based on tosa_battery.c8 */9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/power_supply.h>12#include <linux/delay.h>13#include <linux/spinlock.h>14#include <linux/interrupt.h>15#include <linux/gpio/driver.h>16#include <linux/gpio/machine.h>17#include <linux/gpio/consumer.h>18#include <linux/mfd/ucb1x00.h>19 20#include <asm/mach/sharpsl_param.h>21#include <asm/mach-types.h>22#include <mach/collie.h>23 24static DEFINE_MUTEX(bat_lock); /* protects gpio pins */25static struct work_struct bat_work;26static struct ucb1x00 *ucb;27 28struct collie_bat {29	int status;30	struct power_supply *psy;31	int full_chrg;32 33	struct mutex work_lock; /* protects data */34 35	bool (*is_present)(struct collie_bat *bat);36	struct gpio_desc *gpio_full;37	struct gpio_desc *gpio_charge_on;38 39	int technology;40 41	struct gpio_desc *gpio_bat;42	int adc_bat;43	int adc_bat_divider;44	int bat_max;45	int bat_min;46 47	struct gpio_desc *gpio_temp;48	int adc_temp;49	int adc_temp_divider;50};51 52static struct collie_bat collie_bat_main;53 54static unsigned long collie_read_bat(struct collie_bat *bat)55{56	unsigned long value = 0;57 58	if (!bat->gpio_bat || bat->adc_bat < 0)59		return 0;60	mutex_lock(&bat_lock);61	gpiod_set_value(bat->gpio_bat, 1);62	msleep(5);63	ucb1x00_adc_enable(ucb);64	value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC);65	ucb1x00_adc_disable(ucb);66	gpiod_set_value(bat->gpio_bat, 0);67	mutex_unlock(&bat_lock);68	value = value * 1000000 / bat->adc_bat_divider;69 70	return value;71}72 73static unsigned long collie_read_temp(struct collie_bat *bat)74{75	unsigned long value = 0;76	if (!bat->gpio_temp || bat->adc_temp < 0)77		return 0;78 79	mutex_lock(&bat_lock);80	gpiod_set_value(bat->gpio_temp, 1);81	msleep(5);82	ucb1x00_adc_enable(ucb);83	value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC);84	ucb1x00_adc_disable(ucb);85	gpiod_set_value(bat->gpio_temp, 0);86	mutex_unlock(&bat_lock);87 88	value = value * 10000 / bat->adc_temp_divider;89 90	return value;91}92 93static int collie_bat_get_property(struct power_supply *psy,94			    enum power_supply_property psp,95			    union power_supply_propval *val)96{97	int ret = 0;98	struct collie_bat *bat = power_supply_get_drvdata(psy);99 100	if (bat->is_present && !bat->is_present(bat)101			&& psp != POWER_SUPPLY_PROP_PRESENT) {102		return -ENODEV;103	}104 105	switch (psp) {106	case POWER_SUPPLY_PROP_STATUS:107		val->intval = bat->status;108		break;109	case POWER_SUPPLY_PROP_TECHNOLOGY:110		val->intval = bat->technology;111		break;112	case POWER_SUPPLY_PROP_VOLTAGE_NOW:113		val->intval = collie_read_bat(bat);114		break;115	case POWER_SUPPLY_PROP_VOLTAGE_MAX:116		if (bat->full_chrg == -1)117			val->intval = bat->bat_max;118		else119			val->intval = bat->full_chrg;120		break;121	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:122		val->intval = bat->bat_max;123		break;124	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:125		val->intval = bat->bat_min;126		break;127	case POWER_SUPPLY_PROP_TEMP:128		val->intval = collie_read_temp(bat);129		break;130	case POWER_SUPPLY_PROP_PRESENT:131		val->intval = bat->is_present ? bat->is_present(bat) : 1;132		break;133	default:134		ret = -EINVAL;135		break;136	}137	return ret;138}139 140static void collie_bat_external_power_changed(struct power_supply *psy)141{142	schedule_work(&bat_work);143}144 145static irqreturn_t collie_bat_gpio_isr(int irq, void *data)146{147	pr_info("collie_bat_gpio irq\n");148	schedule_work(&bat_work);149	return IRQ_HANDLED;150}151 152static void collie_bat_update(struct collie_bat *bat)153{154	int old;155	struct power_supply *psy = bat->psy;156 157	mutex_lock(&bat->work_lock);158 159	old = bat->status;160 161	if (bat->is_present && !bat->is_present(bat)) {162		printk(KERN_NOTICE "%s not present\n", psy->desc->name);163		bat->status = POWER_SUPPLY_STATUS_UNKNOWN;164		bat->full_chrg = -1;165	} else if (power_supply_am_i_supplied(psy)) {166		if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {167			gpiod_set_value(bat->gpio_charge_on, 1);168			mdelay(15);169		}170 171		if (gpiod_get_value(bat->gpio_full)) {172			if (old == POWER_SUPPLY_STATUS_CHARGING ||173					bat->full_chrg == -1)174				bat->full_chrg = collie_read_bat(bat);175 176			gpiod_set_value(bat->gpio_charge_on, 0);177			bat->status = POWER_SUPPLY_STATUS_FULL;178		} else {179			gpiod_set_value(bat->gpio_charge_on, 1);180			bat->status = POWER_SUPPLY_STATUS_CHARGING;181		}182	} else {183		gpiod_set_value(bat->gpio_charge_on, 0);184		bat->status = POWER_SUPPLY_STATUS_DISCHARGING;185	}186 187	if (old != bat->status)188		power_supply_changed(psy);189 190	mutex_unlock(&bat->work_lock);191}192 193static void collie_bat_work(struct work_struct *work)194{195	collie_bat_update(&collie_bat_main);196}197 198 199static enum power_supply_property collie_bat_main_props[] = {200	POWER_SUPPLY_PROP_STATUS,201	POWER_SUPPLY_PROP_TECHNOLOGY,202	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,203	POWER_SUPPLY_PROP_VOLTAGE_NOW,204	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,205	POWER_SUPPLY_PROP_VOLTAGE_MAX,206	POWER_SUPPLY_PROP_PRESENT,207	POWER_SUPPLY_PROP_TEMP,208};209 210static enum power_supply_property collie_bat_bu_props[] = {211	POWER_SUPPLY_PROP_STATUS,212	POWER_SUPPLY_PROP_TECHNOLOGY,213	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,214	POWER_SUPPLY_PROP_VOLTAGE_NOW,215	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,216	POWER_SUPPLY_PROP_VOLTAGE_MAX,217	POWER_SUPPLY_PROP_PRESENT,218};219 220static const struct power_supply_desc collie_bat_main_desc = {221	.name		= "main-battery",222	.type		= POWER_SUPPLY_TYPE_BATTERY,223	.properties	= collie_bat_main_props,224	.num_properties	= ARRAY_SIZE(collie_bat_main_props),225	.get_property	= collie_bat_get_property,226	.external_power_changed = collie_bat_external_power_changed,227	.use_for_apm	= 1,228};229 230static struct collie_bat collie_bat_main = {231	.status = POWER_SUPPLY_STATUS_DISCHARGING,232	.full_chrg = -1,233	.psy = NULL,234 235	.gpio_full = NULL,236	.gpio_charge_on = NULL,237 238	.technology = POWER_SUPPLY_TECHNOLOGY_LIPO,239 240	.gpio_bat = NULL,241	.adc_bat = UCB_ADC_INP_AD1,242	.adc_bat_divider = 155,243	.bat_max = 4310000,244	.bat_min = 1551 * 1000000 / 414,245 246	.gpio_temp = NULL,247	.adc_temp = UCB_ADC_INP_AD0,248	.adc_temp_divider = 10000,249};250 251static const struct power_supply_desc collie_bat_bu_desc = {252	.name		= "backup-battery",253	.type		= POWER_SUPPLY_TYPE_BATTERY,254	.properties	= collie_bat_bu_props,255	.num_properties	= ARRAY_SIZE(collie_bat_bu_props),256	.get_property	= collie_bat_get_property,257	.external_power_changed = collie_bat_external_power_changed,258};259 260static struct collie_bat collie_bat_bu = {261	.status = POWER_SUPPLY_STATUS_UNKNOWN,262	.full_chrg = -1,263	.psy = NULL,264 265	.gpio_full = NULL,266	.gpio_charge_on = NULL,267 268	.technology = POWER_SUPPLY_TECHNOLOGY_LiMn,269 270	.gpio_bat = NULL,271	.adc_bat = UCB_ADC_INP_AD1,272	.adc_bat_divider = 155,273	.bat_max = 3000000,274	.bat_min = 1900000,275 276	.gpio_temp = NULL,277	.adc_temp = -1,278	.adc_temp_divider = -1,279};280 281/* Obtained but unused GPIO */282static struct gpio_desc *collie_mbat_low;283 284#ifdef CONFIG_PM285static int wakeup_enabled;286 287static int collie_bat_suspend(struct ucb1x00_dev *dev)288{289	/* flush all pending status updates */290	flush_work(&bat_work);291 292	if (device_may_wakeup(&dev->ucb->dev) &&293	    collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING)294		wakeup_enabled = !enable_irq_wake(gpiod_to_irq(collie_bat_main.gpio_full));295	else296		wakeup_enabled = 0;297 298	return 0;299}300 301static int collie_bat_resume(struct ucb1x00_dev *dev)302{303	if (wakeup_enabled)304		disable_irq_wake(gpiod_to_irq(collie_bat_main.gpio_full));305 306	/* things may have changed while we were away */307	schedule_work(&bat_work);308	return 0;309}310#else311#define collie_bat_suspend NULL312#define collie_bat_resume NULL313#endif314 315static int collie_bat_probe(struct ucb1x00_dev *dev)316{317	int ret;318	struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {};319	struct gpio_chip *gc = &dev->ucb->gpio;320 321	if (!machine_is_collie())322		return -ENODEV;323 324	ucb = dev->ucb;325 326	/* Obtain all the main battery GPIOs */327	collie_bat_main.gpio_full = gpiod_get(&dev->ucb->dev,328					      "main battery full",329					      GPIOD_IN);330	if (IS_ERR(collie_bat_main.gpio_full))331		return PTR_ERR(collie_bat_main.gpio_full);332 333	collie_mbat_low = gpiod_get(&dev->ucb->dev,334				    "main battery low",335				    GPIOD_IN);336	if (IS_ERR(collie_mbat_low)) {337		ret = PTR_ERR(collie_mbat_low);338		goto err_put_gpio_full;339	}340 341	collie_bat_main.gpio_charge_on = gpiod_get(&dev->ucb->dev,342						   "main charge on",343						   GPIOD_OUT_LOW);344	if (IS_ERR(collie_bat_main.gpio_charge_on)) {345		ret = PTR_ERR(collie_bat_main.gpio_charge_on);346		goto err_put_mbat_low;347	}348 349	/* COLLIE_GPIO_MBAT_ON = GPIO 7 on the UCB (TC35143) */350	collie_bat_main.gpio_bat = gpiochip_request_own_desc(gc,351						7,352						"main battery",353						GPIO_ACTIVE_HIGH,354						GPIOD_OUT_LOW);355	if (IS_ERR(collie_bat_main.gpio_bat)) {356		ret = PTR_ERR(collie_bat_main.gpio_bat);357		goto err_put_gpio_charge_on;358	}359 360	/* COLLIE_GPIO_TMP_ON = GPIO 9 on the UCB (TC35143) */361	collie_bat_main.gpio_temp = gpiochip_request_own_desc(gc,362						9,363						"main battery temp",364						GPIO_ACTIVE_HIGH,365						GPIOD_OUT_LOW);366	if (IS_ERR(collie_bat_main.gpio_temp)) {367		ret = PTR_ERR(collie_bat_main.gpio_temp);368		goto err_free_gpio_bat;369	}370 371	/*372	 * Obtain the backup battery COLLIE_GPIO_BBAT_ON which is373	 * GPIO 8 on the UCB (TC35143)374	 */375	collie_bat_bu.gpio_bat = gpiochip_request_own_desc(gc,376						8,377						"backup battery",378						GPIO_ACTIVE_HIGH,379						GPIOD_OUT_LOW);380	if (IS_ERR(collie_bat_bu.gpio_bat)) {381		ret = PTR_ERR(collie_bat_bu.gpio_bat);382		goto err_free_gpio_temp;383	}384 385	mutex_init(&collie_bat_main.work_lock);386 387	INIT_WORK(&bat_work, collie_bat_work);388 389	psy_main_cfg.drv_data = &collie_bat_main;390	collie_bat_main.psy = power_supply_register(&dev->ucb->dev,391						    &collie_bat_main_desc,392						    &psy_main_cfg);393	if (IS_ERR(collie_bat_main.psy)) {394		ret = PTR_ERR(collie_bat_main.psy);395		goto err_psy_reg_main;396	}397 398	psy_bu_cfg.drv_data = &collie_bat_bu;399	collie_bat_bu.psy = power_supply_register(&dev->ucb->dev,400						  &collie_bat_bu_desc,401						  &psy_bu_cfg);402	if (IS_ERR(collie_bat_bu.psy)) {403		ret = PTR_ERR(collie_bat_bu.psy);404		goto err_psy_reg_bu;405	}406 407	ret = request_irq(gpiod_to_irq(collie_bat_main.gpio_full),408				collie_bat_gpio_isr,409				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,410				"main full", &collie_bat_main);411	if (ret)412		goto err_irq;413 414	device_init_wakeup(&ucb->dev, 1);415	schedule_work(&bat_work);416 417	return 0;418 419err_irq:420	power_supply_unregister(collie_bat_bu.psy);421err_psy_reg_bu:422	power_supply_unregister(collie_bat_main.psy);423err_psy_reg_main:424	/* see comment in collie_bat_remove */425	cancel_work_sync(&bat_work);426	gpiochip_free_own_desc(collie_bat_bu.gpio_bat);427err_free_gpio_temp:428	gpiochip_free_own_desc(collie_bat_main.gpio_temp);429err_free_gpio_bat:430	gpiochip_free_own_desc(collie_bat_main.gpio_bat);431err_put_gpio_charge_on:432	gpiod_put(collie_bat_main.gpio_charge_on);433err_put_mbat_low:434	gpiod_put(collie_mbat_low);435err_put_gpio_full:436	gpiod_put(collie_bat_main.gpio_full);437 438	return ret;439}440 441static void collie_bat_remove(struct ucb1x00_dev *dev)442{443	free_irq(gpiod_to_irq(collie_bat_main.gpio_full), &collie_bat_main);444	power_supply_unregister(collie_bat_bu.psy);445	power_supply_unregister(collie_bat_main.psy);446 447	/* These are obtained from the machine */448	gpiod_put(collie_bat_main.gpio_full);449	gpiod_put(collie_mbat_low);450	gpiod_put(collie_bat_main.gpio_charge_on);451	/* These are directly from the UCB so let's free them */452	gpiochip_free_own_desc(collie_bat_main.gpio_bat);453	gpiochip_free_own_desc(collie_bat_main.gpio_temp);454	gpiochip_free_own_desc(collie_bat_bu.gpio_bat);455	/*456	 * Now cancel the bat_work.  We won't get any more schedules,457	 * since all sources (isr and external_power_changed) are458	 * unregistered now.459	 */460	cancel_work_sync(&bat_work);461}462 463static struct ucb1x00_driver collie_bat_driver = {464	.add		= collie_bat_probe,465	.remove		= collie_bat_remove,466	.suspend	= collie_bat_suspend,467	.resume		= collie_bat_resume,468};469 470static int __init collie_bat_init(void)471{472	return ucb1x00_register_driver(&collie_bat_driver);473}474 475static void __exit collie_bat_exit(void)476{477	ucb1x00_unregister_driver(&collie_bat_driver);478}479 480module_init(collie_bat_init);481module_exit(collie_bat_exit);482 483MODULE_LICENSE("GPL");484MODULE_AUTHOR("Thomas Kunze");485MODULE_DESCRIPTION("Collie battery driver");486