cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

s3c_adc_battery.c (11325B)


      1// SPDX-License-Identifier: GPL-2.0
      2//
      3// iPAQ h1930/h1940/rx1950 battery controller driver
      4// Copyright (c) Vasily Khoruzhick
      5// Based on h1940_battery.c by Arnaud Patard
      6
      7#include <linux/interrupt.h>
      8#include <linux/platform_device.h>
      9#include <linux/power_supply.h>
     10#include <linux/leds.h>
     11#include <linux/gpio/consumer.h>
     12#include <linux/err.h>
     13#include <linux/timer.h>
     14#include <linux/jiffies.h>
     15#include <linux/s3c_adc_battery.h>
     16#include <linux/errno.h>
     17#include <linux/init.h>
     18#include <linux/module.h>
     19
     20#include <linux/soc/samsung/s3c-adc.h>
     21
     22#define BAT_POLL_INTERVAL		10000 /* ms */
     23#define JITTER_DELAY			500 /* ms */
     24
     25struct s3c_adc_bat {
     26	struct power_supply		*psy;
     27	struct s3c_adc_client		*client;
     28	struct s3c_adc_bat_pdata	*pdata;
     29	struct gpio_desc		*charge_finished;
     30	int				volt_value;
     31	int				cur_value;
     32	unsigned int			timestamp;
     33	int				level;
     34	int				status;
     35	int				cable_plugged:1;
     36};
     37
     38static struct delayed_work bat_work;
     39
     40static void s3c_adc_bat_ext_power_changed(struct power_supply *psy)
     41{
     42	schedule_delayed_work(&bat_work,
     43		msecs_to_jiffies(JITTER_DELAY));
     44}
     45
     46static int gather_samples(struct s3c_adc_client *client, int num, int channel)
     47{
     48	int value, i;
     49
     50	/* default to 1 if nothing is set */
     51	if (num < 1)
     52		num = 1;
     53
     54	value = 0;
     55	for (i = 0; i < num; i++)
     56		value += s3c_adc_read(client, channel);
     57	value /= num;
     58
     59	return value;
     60}
     61
     62static enum power_supply_property s3c_adc_backup_bat_props[] = {
     63	POWER_SUPPLY_PROP_VOLTAGE_NOW,
     64	POWER_SUPPLY_PROP_VOLTAGE_MIN,
     65	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
     66};
     67
     68static int s3c_adc_backup_bat_get_property(struct power_supply *psy,
     69				enum power_supply_property psp,
     70				union power_supply_propval *val)
     71{
     72	struct s3c_adc_bat *bat = power_supply_get_drvdata(psy);
     73
     74	if (!bat) {
     75		dev_err(&psy->dev, "%s: no battery infos ?!\n", __func__);
     76		return -EINVAL;
     77	}
     78
     79	if (bat->volt_value < 0 ||
     80		jiffies_to_msecs(jiffies - bat->timestamp) >
     81			BAT_POLL_INTERVAL) {
     82		bat->volt_value = gather_samples(bat->client,
     83			bat->pdata->backup_volt_samples,
     84			bat->pdata->backup_volt_channel);
     85		bat->volt_value *= bat->pdata->backup_volt_mult;
     86		bat->timestamp = jiffies;
     87	}
     88
     89	switch (psp) {
     90	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
     91		val->intval = bat->volt_value;
     92		return 0;
     93	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
     94		val->intval = bat->pdata->backup_volt_min;
     95		return 0;
     96	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
     97		val->intval = bat->pdata->backup_volt_max;
     98		return 0;
     99	default:
    100		return -EINVAL;
    101	}
    102}
    103
    104static const struct power_supply_desc backup_bat_desc = {
    105	.name		= "backup-battery",
    106	.type		= POWER_SUPPLY_TYPE_BATTERY,
    107	.properties	= s3c_adc_backup_bat_props,
    108	.num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props),
    109	.get_property	= s3c_adc_backup_bat_get_property,
    110	.use_for_apm	= 1,
    111};
    112
    113static struct s3c_adc_bat backup_bat;
    114
    115static enum power_supply_property s3c_adc_main_bat_props[] = {
    116	POWER_SUPPLY_PROP_STATUS,
    117	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
    118	POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
    119	POWER_SUPPLY_PROP_CHARGE_NOW,
    120	POWER_SUPPLY_PROP_VOLTAGE_NOW,
    121	POWER_SUPPLY_PROP_CURRENT_NOW,
    122};
    123
    124static int calc_full_volt(int volt_val, int cur_val, int impedance)
    125{
    126	return volt_val + cur_val * impedance / 1000;
    127}
    128
    129static int charge_finished(struct s3c_adc_bat *bat)
    130{
    131	return gpiod_get_value(bat->charge_finished);
    132}
    133
    134static int s3c_adc_bat_get_property(struct power_supply *psy,
    135				    enum power_supply_property psp,
    136				    union power_supply_propval *val)
    137{
    138	struct s3c_adc_bat *bat = power_supply_get_drvdata(psy);
    139
    140	int new_level;
    141	int full_volt;
    142	const struct s3c_adc_bat_thresh *lut;
    143	unsigned int lut_size;
    144
    145	if (!bat) {
    146		dev_err(&psy->dev, "no battery infos ?!\n");
    147		return -EINVAL;
    148	}
    149
    150	lut = bat->pdata->lut_noac;
    151	lut_size = bat->pdata->lut_noac_cnt;
    152
    153	if (bat->volt_value < 0 || bat->cur_value < 0 ||
    154		jiffies_to_msecs(jiffies - bat->timestamp) >
    155			BAT_POLL_INTERVAL) {
    156		bat->volt_value = gather_samples(bat->client,
    157			bat->pdata->volt_samples,
    158			bat->pdata->volt_channel) * bat->pdata->volt_mult;
    159		bat->cur_value = gather_samples(bat->client,
    160			bat->pdata->current_samples,
    161			bat->pdata->current_channel) * bat->pdata->current_mult;
    162		bat->timestamp = jiffies;
    163	}
    164
    165	if (bat->cable_plugged &&
    166		(!bat->charge_finished ||
    167		!charge_finished(bat))) {
    168		lut = bat->pdata->lut_acin;
    169		lut_size = bat->pdata->lut_acin_cnt;
    170	}
    171
    172	new_level = 100000;
    173	full_volt = calc_full_volt((bat->volt_value / 1000),
    174		(bat->cur_value / 1000), bat->pdata->internal_impedance);
    175
    176	if (full_volt < calc_full_volt(lut->volt, lut->cur,
    177		bat->pdata->internal_impedance)) {
    178		lut_size--;
    179		while (lut_size--) {
    180			int lut_volt1;
    181			int lut_volt2;
    182
    183			lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur,
    184				bat->pdata->internal_impedance);
    185			lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur,
    186				bat->pdata->internal_impedance);
    187			if (full_volt < lut_volt1 && full_volt >= lut_volt2) {
    188				new_level = (lut[1].level +
    189					(lut[0].level - lut[1].level) *
    190					(full_volt - lut_volt2) /
    191					(lut_volt1 - lut_volt2)) * 1000;
    192				break;
    193			}
    194			new_level = lut[1].level * 1000;
    195			lut++;
    196		}
    197	}
    198
    199	bat->level = new_level;
    200
    201	switch (psp) {
    202	case POWER_SUPPLY_PROP_STATUS:
    203		if (!bat->charge_finished)
    204			val->intval = bat->level == 100000 ?
    205				POWER_SUPPLY_STATUS_FULL : bat->status;
    206		else
    207			val->intval = bat->status;
    208		return 0;
    209	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
    210		val->intval = 100000;
    211		return 0;
    212	case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
    213		val->intval = 0;
    214		return 0;
    215	case POWER_SUPPLY_PROP_CHARGE_NOW:
    216		val->intval = bat->level;
    217		return 0;
    218	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
    219		val->intval = bat->volt_value;
    220		return 0;
    221	case POWER_SUPPLY_PROP_CURRENT_NOW:
    222		val->intval = bat->cur_value;
    223		return 0;
    224	default:
    225		return -EINVAL;
    226	}
    227}
    228
    229static const struct power_supply_desc main_bat_desc = {
    230	.name			= "main-battery",
    231	.type			= POWER_SUPPLY_TYPE_BATTERY,
    232	.properties		= s3c_adc_main_bat_props,
    233	.num_properties		= ARRAY_SIZE(s3c_adc_main_bat_props),
    234	.get_property		= s3c_adc_bat_get_property,
    235	.external_power_changed = s3c_adc_bat_ext_power_changed,
    236	.use_for_apm		= 1,
    237};
    238
    239static struct s3c_adc_bat main_bat;
    240
    241static void s3c_adc_bat_work(struct work_struct *work)
    242{
    243	struct s3c_adc_bat *bat = &main_bat;
    244	int is_charged;
    245	int is_plugged;
    246	static int was_plugged;
    247
    248	is_plugged = power_supply_am_i_supplied(bat->psy);
    249	bat->cable_plugged = is_plugged;
    250	if (is_plugged != was_plugged) {
    251		was_plugged = is_plugged;
    252		if (is_plugged) {
    253			if (bat->pdata->enable_charger)
    254				bat->pdata->enable_charger();
    255			bat->status = POWER_SUPPLY_STATUS_CHARGING;
    256		} else {
    257			if (bat->pdata->disable_charger)
    258				bat->pdata->disable_charger();
    259			bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
    260		}
    261	} else {
    262		if (bat->charge_finished && is_plugged) {
    263			is_charged = charge_finished(&main_bat);
    264			if (is_charged) {
    265				if (bat->pdata->disable_charger)
    266					bat->pdata->disable_charger();
    267				bat->status = POWER_SUPPLY_STATUS_FULL;
    268			} else {
    269				if (bat->pdata->enable_charger)
    270					bat->pdata->enable_charger();
    271				bat->status = POWER_SUPPLY_STATUS_CHARGING;
    272			}
    273		}
    274	}
    275
    276	power_supply_changed(bat->psy);
    277}
    278
    279static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id)
    280{
    281	schedule_delayed_work(&bat_work,
    282		msecs_to_jiffies(JITTER_DELAY));
    283	return IRQ_HANDLED;
    284}
    285
    286static int s3c_adc_bat_probe(struct platform_device *pdev)
    287{
    288	struct s3c_adc_client	*client;
    289	struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
    290	struct power_supply_config psy_cfg = {};
    291	struct gpio_desc *gpiod;
    292	int ret;
    293
    294	client = s3c_adc_register(pdev, NULL, NULL, 0);
    295	if (IS_ERR(client)) {
    296		dev_err(&pdev->dev, "cannot register adc\n");
    297		return PTR_ERR(client);
    298	}
    299
    300	platform_set_drvdata(pdev, client);
    301
    302	gpiod = devm_gpiod_get_optional(&pdev->dev, "charge-status", GPIOD_IN);
    303	if (IS_ERR(gpiod)) {
    304		/* Could be probe deferral etc */
    305		ret = PTR_ERR(gpiod);
    306		dev_err(&pdev->dev, "no GPIO %d\n", ret);
    307		return ret;
    308	}
    309
    310	main_bat.client = client;
    311	main_bat.pdata = pdata;
    312	main_bat.charge_finished = gpiod;
    313	main_bat.volt_value = -1;
    314	main_bat.cur_value = -1;
    315	main_bat.cable_plugged = 0;
    316	main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING;
    317	psy_cfg.drv_data = &main_bat;
    318
    319	main_bat.psy = power_supply_register(&pdev->dev, &main_bat_desc, &psy_cfg);
    320	if (IS_ERR(main_bat.psy)) {
    321		ret = PTR_ERR(main_bat.psy);
    322		goto err_reg_main;
    323	}
    324	if (pdata->backup_volt_mult) {
    325		const struct power_supply_config backup_psy_cfg
    326						= { .drv_data = &backup_bat, };
    327
    328		backup_bat.client = client;
    329		backup_bat.pdata = pdev->dev.platform_data;
    330		backup_bat.charge_finished = gpiod;
    331		backup_bat.volt_value = -1;
    332		backup_bat.psy = power_supply_register(&pdev->dev,
    333						       &backup_bat_desc,
    334						       &backup_psy_cfg);
    335		if (IS_ERR(backup_bat.psy)) {
    336			ret = PTR_ERR(backup_bat.psy);
    337			goto err_reg_backup;
    338		}
    339	}
    340
    341	INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work);
    342
    343	if (gpiod) {
    344		ret = request_irq(gpiod_to_irq(gpiod),
    345				s3c_adc_bat_charged,
    346				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
    347				"battery charged", NULL);
    348		if (ret)
    349			goto err_irq;
    350	}
    351
    352	if (pdata->init) {
    353		ret = pdata->init();
    354		if (ret)
    355			goto err_platform;
    356	}
    357
    358	dev_info(&pdev->dev, "successfully loaded\n");
    359	device_init_wakeup(&pdev->dev, 1);
    360
    361	/* Schedule timer to check current status */
    362	schedule_delayed_work(&bat_work,
    363		msecs_to_jiffies(JITTER_DELAY));
    364
    365	return 0;
    366
    367err_platform:
    368	if (gpiod)
    369		free_irq(gpiod_to_irq(gpiod), NULL);
    370err_irq:
    371	if (pdata->backup_volt_mult)
    372		power_supply_unregister(backup_bat.psy);
    373err_reg_backup:
    374	power_supply_unregister(main_bat.psy);
    375err_reg_main:
    376	return ret;
    377}
    378
    379static int s3c_adc_bat_remove(struct platform_device *pdev)
    380{
    381	struct s3c_adc_client *client = platform_get_drvdata(pdev);
    382	struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
    383
    384	power_supply_unregister(main_bat.psy);
    385	if (pdata->backup_volt_mult)
    386		power_supply_unregister(backup_bat.psy);
    387
    388	s3c_adc_release(client);
    389
    390	if (main_bat.charge_finished)
    391		free_irq(gpiod_to_irq(main_bat.charge_finished), NULL);
    392
    393	cancel_delayed_work_sync(&bat_work);
    394
    395	if (pdata->exit)
    396		pdata->exit();
    397
    398	return 0;
    399}
    400
    401#ifdef CONFIG_PM
    402static int s3c_adc_bat_suspend(struct platform_device *pdev,
    403	pm_message_t state)
    404{
    405	if (main_bat.charge_finished) {
    406		if (device_may_wakeup(&pdev->dev))
    407			enable_irq_wake(
    408				gpiod_to_irq(main_bat.charge_finished));
    409		else {
    410			disable_irq(gpiod_to_irq(main_bat.charge_finished));
    411			main_bat.pdata->disable_charger();
    412		}
    413	}
    414
    415	return 0;
    416}
    417
    418static int s3c_adc_bat_resume(struct platform_device *pdev)
    419{
    420	if (main_bat.charge_finished) {
    421		if (device_may_wakeup(&pdev->dev))
    422			disable_irq_wake(
    423				gpiod_to_irq(main_bat.charge_finished));
    424		else
    425			enable_irq(gpiod_to_irq(main_bat.charge_finished));
    426	}
    427
    428	/* Schedule timer to check current status */
    429	schedule_delayed_work(&bat_work,
    430		msecs_to_jiffies(JITTER_DELAY));
    431
    432	return 0;
    433}
    434#else
    435#define s3c_adc_bat_suspend NULL
    436#define s3c_adc_bat_resume NULL
    437#endif
    438
    439static struct platform_driver s3c_adc_bat_driver = {
    440	.driver		= {
    441		.name	= "s3c-adc-battery",
    442	},
    443	.probe		= s3c_adc_bat_probe,
    444	.remove		= s3c_adc_bat_remove,
    445	.suspend	= s3c_adc_bat_suspend,
    446	.resume		= s3c_adc_bat_resume,
    447};
    448
    449module_platform_driver(s3c_adc_bat_driver);
    450
    451MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>");
    452MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver");
    453MODULE_LICENSE("GPL");