cregit-Linux how code gets into the kernel

Release 4.14 drivers/power/supply/da9030_battery.c

/*
 * Battery charger driver for Dialog Semiconductor DA9030
 *
 * Copyright (C) 2008 Compulab, Ltd.
 *      Mike Rapoport <mike@compulab.co.il>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/mfd/da903x.h>

#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/notifier.h>


#define DA9030_FAULT_LOG		0x0a

#define DA9030_FAULT_LOG_OVER_TEMP	(1 << 7)

#define DA9030_FAULT_LOG_VBAT_OVER	(1 << 4)


#define DA9030_CHARGE_CONTROL		0x28

#define DA9030_CHRG_CHARGER_ENABLE	(1 << 7)


#define DA9030_ADC_MAN_CONTROL		0x30

#define DA9030_ADC_TBATREF_ENABLE	(1 << 5)

#define DA9030_ADC_LDO_INT_ENABLE	(1 << 4)


#define DA9030_ADC_AUTO_CONTROL		0x31

#define DA9030_ADC_TBAT_ENABLE		(1 << 5)

#define DA9030_ADC_VBAT_IN_TXON		(1 << 4)

#define DA9030_ADC_VCH_ENABLE		(1 << 3)

#define DA9030_ADC_ICH_ENABLE		(1 << 2)

#define DA9030_ADC_VBAT_ENABLE		(1 << 1)

#define DA9030_ADC_AUTO_SLEEP_ENABLE	(1 << 0)


#define DA9030_VBATMON		0x32

#define DA9030_VBATMONTXON	0x33

#define DA9030_TBATHIGHP	0x34

#define DA9030_TBATHIGHN	0x35

#define DA9030_TBATLOW		0x36


#define DA9030_VBAT_RES		0x41

#define DA9030_VBATMIN_RES	0x42

#define DA9030_VBATMINTXON_RES	0x43

#define DA9030_ICHMAX_RES	0x44

#define DA9030_ICHMIN_RES	0x45

#define DA9030_ICHAVERAGE_RES	0x46

#define DA9030_VCHMAX_RES	0x47

#define DA9030_VCHMIN_RES	0x48

#define DA9030_TBAT_RES		0x49


struct da9030_adc_res {
	
uint8_t vbat_res;
	
uint8_t vbatmin_res;
	
uint8_t vbatmintxon;
	
uint8_t ichmax_res;
	
uint8_t ichmin_res;
	
uint8_t ichaverage_res;
	
uint8_t vchmax_res;
	
uint8_t vchmin_res;
	
uint8_t tbat_res;
	
uint8_t adc_in4_res;
	
uint8_t adc_in5_res;
};


struct da9030_battery_thresholds {
	
int tbat_low;
	
int tbat_high;
	
int tbat_restart;

	
int vbat_low;
	
int vbat_crit;
	
int vbat_charge_start;
	
int vbat_charge_stop;
	
int vbat_charge_restart;

	
int vcharge_min;
	
int vcharge_max;
};


struct da9030_charger {
	
struct power_supply *psy;
	
struct power_supply_desc psy_desc;

	
struct device *master;

	
struct da9030_adc_res adc;
	
struct delayed_work work;
	
unsigned int interval;

	
struct power_supply_info *battery_info;

	
struct da9030_battery_thresholds thresholds;

	
unsigned int charge_milliamp;
	
unsigned int charge_millivolt;

	/* charger status */
	
bool chdet;
	
uint8_t fault;
	
int mA;
	
int mV;
	
bool is_on;

	
struct notifier_block nb;

	/* platform callbacks for battery low and critical events */
	
void (*battery_low)(void);
	
void (*battery_critical)(void);

	
struct dentry *debug_file;
};


static inline int da9030_reg_to_mV(int reg) { return ((reg * 2650) >> 8) + 2650; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport23100.00%1100.00%
Total23100.00%1100.00%


static inline int da9030_millivolt_to_reg(int mV) { return ((mV - 2650) << 8) / 2650; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport23100.00%1100.00%
Total23100.00%1100.00%


static inline int da9030_reg_to_mA(int reg) { return ((reg * 24000) >> 8) / 15; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport23100.00%1100.00%
Total23100.00%1100.00%

#ifdef CONFIG_DEBUG_FS
static int bat_debug_show(struct seq_file *s, void *data) { struct da9030_charger *charger = s->private; seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); if (charger->chdet) { seq_printf(s, "iset = %dmA, vset = %dmV\n", charger->mA, charger->mV); } seq_printf(s, "vbat_res = %d (%dmV)\n", charger->adc.vbat_res, da9030_reg_to_mV(charger->adc.vbat_res)); seq_printf(s, "vbatmin_res = %d (%dmV)\n", charger->adc.vbatmin_res, da9030_reg_to_mV(charger->adc.vbatmin_res)); seq_printf(s, "vbatmintxon = %d (%dmV)\n", charger->adc.vbatmintxon, da9030_reg_to_mV(charger->adc.vbatmintxon)); seq_printf(s, "ichmax_res = %d (%dmA)\n", charger->adc.ichmax_res, da9030_reg_to_mV(charger->adc.ichmax_res)); seq_printf(s, "ichmin_res = %d (%dmA)\n", charger->adc.ichmin_res, da9030_reg_to_mA(charger->adc.ichmin_res)); seq_printf(s, "ichaverage_res = %d (%dmA)\n", charger->adc.ichaverage_res, da9030_reg_to_mA(charger->adc.ichaverage_res)); seq_printf(s, "vchmax_res = %d (%dmV)\n", charger->adc.vchmax_res, da9030_reg_to_mA(charger->adc.vchmax_res)); seq_printf(s, "vchmin_res = %d (%dmV)\n", charger->adc.vchmin_res, da9030_reg_to_mV(charger->adc.vchmin_res)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport241100.00%1100.00%
Total241100.00%1100.00%


static int debug_open(struct inode *inode, struct file *file) { return single_open(file, bat_debug_show, inode->i_private); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport28100.00%1100.00%
Total28100.00%1100.00%

static const struct file_operations bat_debug_fops = { .open = debug_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, };
static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) { charger->debug_file = debugfs_create_file("charger", 0666, NULL, charger, &bat_debug_fops); return charger->debug_file; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport3597.22%150.00%
Anton Vorontsov12.78%150.00%
Total36100.00%2100.00%


static void da9030_bat_remove_debugfs(struct da9030_charger *charger) { debugfs_remove(charger->debug_file); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport18100.00%1100.00%
Total18100.00%1100.00%

#else
static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) { return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport17100.00%1100.00%
Total17100.00%1100.00%


static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger) { }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport11100.00%1100.00%
Total11100.00%1100.00%

#endif
static inline void da9030_read_adc(struct da9030_charger *charger, struct da9030_adc_res *adc) { da903x_reads(charger->master, DA9030_VBAT_RES, sizeof(*adc), (uint8_t *)adc); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport38100.00%1100.00%
Total38100.00%1100.00%


static void da9030_charger_update_state(struct da9030_charger *charger) { uint8_t val; da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val); charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0; charger->mA = ((val >> 3) & 0xf) * 100; charger->mV = (val & 0x7) * 50 + 4000; da9030_read_adc(charger, &charger->adc); da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault); charger->chdet = da903x_query_status(charger->master, DA9030_STATUS_CHDET); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport107100.00%1100.00%
Total107100.00%1100.00%


static void da9030_set_charge(struct da9030_charger *charger, int on) { uint8_t val; if (on) { val = DA9030_CHRG_CHARGER_ENABLE; val |= (charger->charge_milliamp / 100) << 3; val |= (charger->charge_millivolt - 4000) / 50; charger->is_on = 1; } else { val = 0; charger->is_on = 0; } da903x_write(charger->master, DA9030_CHARGE_CONTROL, val); power_supply_changed(charger->psy); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport88100.00%2100.00%
Total88100.00%2100.00%


static void da9030_charger_check_state(struct da9030_charger *charger) { da9030_charger_update_state(charger); /* we wake or boot with external power on */ if (!charger->is_on) { if ((charger->chdet) && (charger->adc.vbat_res < charger->thresholds.vbat_charge_start)) { da9030_set_charge(charger, 1); } } else { /* Charger has been pulled out */ if (!charger->chdet) { da9030_set_charge(charger, 0); return; } if (charger->adc.vbat_res >= charger->thresholds.vbat_charge_stop) { da9030_set_charge(charger, 0); da903x_write(charger->master, DA9030_VBATMON, charger->thresholds.vbat_charge_restart); } else if (charger->adc.vbat_res > charger->thresholds.vbat_low) { /* we are charging and passed LOW_THRESH, so upate DA9030 VBAT threshold */ da903x_write(charger->master, DA9030_VBATMON, charger->thresholds.vbat_low); } if (charger->adc.vchmax_res > charger->thresholds.vcharge_max || charger->adc.vchmin_res < charger->thresholds.vcharge_min || /* Tempreture readings are negative */ charger->adc.tbat_res < charger->thresholds.tbat_high || charger->adc.tbat_res > charger->thresholds.tbat_low) { /* disable charger */ da9030_set_charge(charger, 0); } } }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport210100.00%2100.00%
Total210100.00%2100.00%


static void da9030_charging_monitor(struct work_struct *work) { struct da9030_charger *charger; charger = container_of(work, struct da9030_charger, work.work); da9030_charger_check_state(charger); /* reschedule for the next time */ schedule_delayed_work(&charger->work, charger->interval); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport48100.00%1100.00%
Total48100.00%1100.00%

static enum power_supply_property da9030_battery_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, };
static void da9030_battery_check_status(struct da9030_charger *charger, union power_supply_propval *val) { if (charger->chdet) { if (charger->is_on) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; } else { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; } }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport52100.00%1100.00%
Total52100.00%1100.00%


static void da9030_battery_check_health(struct da9030_charger *charger, union power_supply_propval *val) { if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP) val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER) val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else val->intval = POWER_SUPPLY_HEALTH_GOOD; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport52100.00%1100.00%
Total52100.00%1100.00%


static int da9030_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct da9030_charger *charger = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: da9030_battery_check_status(charger, val); break; case POWER_SUPPLY_PROP_HEALTH: da9030_battery_check_health(charger, val); break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = charger->battery_info->technology; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = charger->battery_info->voltage_max_design; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = charger->battery_info->voltage_min_design; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000; break; case POWER_SUPPLY_PROP_CURRENT_AVG: val->intval = da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000; break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = charger->battery_info->name; break; default: break; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport15498.09%150.00%
Krzysztof Kozlowski31.91%150.00%
Total157100.00%2100.00%


static void da9030_battery_vbat_event(struct da9030_charger *charger) { da9030_read_adc(charger, &charger->adc); if (charger->is_on) return; if (charger->adc.vbat_res < charger->thresholds.vbat_low) { /* set VBAT threshold for critical */ da903x_write(charger->master, DA9030_VBATMON, charger->thresholds.vbat_crit); if (charger->battery_low) charger->battery_low(); } else if (charger->adc.vbat_res < charger->thresholds.vbat_crit) { /* notify the system of battery critical */ if (charger->battery_critical) charger->battery_critical(); } }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport100100.00%1100.00%
Total100100.00%1100.00%


static int da9030_battery_event(struct notifier_block *nb, unsigned long event, void *data) { struct da9030_charger *charger = container_of(nb, struct da9030_charger, nb); switch (event) { case DA9030_EVENT_CHDET: cancel_delayed_work_sync(&charger->work); schedule_work(&charger->work.work); break; case DA9030_EVENT_VBATMON: da9030_battery_vbat_event(charger); break; case DA9030_EVENT_CHIOVER: case DA9030_EVENT_TBAT: da9030_set_charge(charger, 0); break; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport88100.00%2100.00%
Total88100.00%2100.00%


static void da9030_battery_convert_thresholds(struct da9030_charger *charger, struct da9030_battery_info *pdata) { charger->thresholds.tbat_low = pdata->tbat_low; charger->thresholds.tbat_high = pdata->tbat_high; charger->thresholds.tbat_restart = pdata->tbat_restart; charger->thresholds.vbat_low = da9030_millivolt_to_reg(pdata->vbat_low); charger->thresholds.vbat_crit = da9030_millivolt_to_reg(pdata->vbat_crit); charger->thresholds.vbat_charge_start = da9030_millivolt_to_reg(pdata->vbat_charge_start); charger->thresholds.vbat_charge_stop = da9030_millivolt_to_reg(pdata->vbat_charge_stop); charger->thresholds.vbat_charge_restart = da9030_millivolt_to_reg(pdata->vbat_charge_restart); charger->thresholds.vcharge_min = da9030_millivolt_to_reg(pdata->vcharge_min); charger->thresholds.vcharge_max = da9030_millivolt_to_reg(pdata->vcharge_max); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport137100.00%1100.00%
Total137100.00%1100.00%


static void da9030_battery_setup_psy(struct da9030_charger *charger) { struct power_supply_desc *psy_desc = &charger->psy_desc; struct power_supply_info *info = charger->battery_info; psy_desc->name = info->name; psy_desc->use_for_apm = info->use_for_apm; psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; psy_desc->get_property = da9030_battery_get_property; psy_desc->properties = da9030_battery_props; psy_desc->num_properties = ARRAY_SIZE(da9030_battery_props); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport6487.67%150.00%
Krzysztof Kozlowski912.33%150.00%
Total73100.00%2100.00%

;
static int da9030_battery_charger_init(struct da9030_charger *charger) { char v[5]; int ret; v[0] = v[1] = charger->thresholds.vbat_low; v[2] = charger->thresholds.tbat_high; v[3] = charger->thresholds.tbat_restart; v[4] = charger->thresholds.tbat_low; ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v); if (ret) return ret; /* * Enable reference voltage supply for ADC from the LDO_INTERNAL * regulator. Must be set before ADC measurements can be made. */ ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL, DA9030_ADC_LDO_INT_ENABLE | DA9030_ADC_TBATREF_ENABLE); if (ret) return ret; /* enable auto ADC measuremnts */ return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL, DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON | DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE | DA9030_ADC_VBAT_ENABLE | DA9030_ADC_AUTO_SLEEP_ENABLE); }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport137100.00%1100.00%
Total137100.00%1100.00%


static int da9030_battery_probe(struct platform_device *pdev) { struct da9030_charger *charger; struct power_supply_config psy_cfg = {}; struct da9030_battery_info *pdata = pdev->dev.platform_data; int ret; if (pdata == NULL) return -EINVAL; if (pdata->charge_milliamp >= 1500 || pdata->charge_millivolt < 4000 || pdata->charge_millivolt > 4350) return -EINVAL; charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); if (charger == NULL) return -ENOMEM; charger->master = pdev->dev.parent; /* 10 seconds between monitor runs unless platform defines other interval */ charger->interval = msecs_to_jiffies( (pdata->batmon_interval ? : 10) * 1000); charger->charge_milliamp = pdata->charge_milliamp; charger->charge_millivolt = pdata->charge_millivolt; charger->battery_info = pdata->battery_info; charger->battery_low = pdata->battery_low; charger->battery_critical = pdata->battery_critical; da9030_battery_convert_thresholds(charger, pdata); ret = da9030_battery_charger_init(charger); if (ret) goto err_charger_init; INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor); schedule_delayed_work(&charger->work, charger->interval); charger->nb.notifier_call = da9030_battery_event; ret = da903x_register_notifier(charger->master, &charger->nb, DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); if (ret) goto err_notifier; da9030_battery_setup_psy(charger); psy_cfg.drv_data = charger; charger->psy = power_supply_register(&pdev->dev, &charger->psy_desc, &psy_cfg); if (IS_ERR(charger->psy)) { ret = PTR_ERR(charger->psy); goto err_ps_register; } charger->debug_file = da9030_bat_create_debugfs(charger); platform_set_drvdata(pdev, charger); return 0; err_ps_register: da903x_unregister_notifier(charger->master, &charger->nb, DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); err_notifier: cancel_delayed_work(&charger->work); err_charger_init: return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport31487.96%120.00%
Krzysztof Kozlowski3610.08%240.00%
Jingoo Han61.68%120.00%
Stefan Weil10.28%120.00%
Total357100.00%5100.00%


static int da9030_battery_remove(struct platform_device *dev) { struct da9030_charger *charger = platform_get_drvdata(dev); da9030_bat_remove_debugfs(charger); da903x_unregister_notifier(charger->master, &charger->nb, DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); cancel_delayed_work_sync(&charger->work); da9030_set_charge(charger, 0); power_supply_unregister(charger->psy); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport71100.00%2100.00%
Total71100.00%2100.00%

static struct platform_driver da903x_battery_driver = { .driver = { .name = "da903x-battery", }, .probe = da9030_battery_probe, .remove = da9030_battery_remove, }; module_platform_driver(da903x_battery_driver); MODULE_DESCRIPTION("DA9030 battery charger driver"); MODULE_AUTHOR("Mike Rapoport, CompuLab"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Mike Rapoport249497.31%220.00%
Krzysztof Kozlowski532.07%220.00%
Jingoo Han60.23%110.00%
Tejun Heo30.12%110.00%
Michal Hocko30.12%110.00%
Axel Lin20.08%110.00%
Stefan Weil10.04%110.00%
Anton Vorontsov10.04%110.00%
Total2563100.00%10100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.