cregit-Linux how code gets into the kernel

Release 4.14 drivers/power/supply/da9150-fg.c

/*
 * DA9150 Fuel-Gauge Driver
 *
 * Copyright (c) 2015 Dialog Semiconductor
 *
 * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/power_supply.h>
#include <linux/list.h>
#include <asm/div64.h>
#include <linux/mfd/da9150/core.h>
#include <linux/mfd/da9150/registers.h>

/* Core2Wire */

#define DA9150_QIF_READ		(0x0 << 7)

#define DA9150_QIF_WRITE	(0x1 << 7)

#define DA9150_QIF_CODE_MASK	0x7F


#define DA9150_QIF_BYTE_SIZE	8

#define DA9150_QIF_BYTE_MASK	0xFF

#define DA9150_QIF_SHORT_SIZE	2

#define DA9150_QIF_LONG_SIZE	4

/* QIF Codes */

#define DA9150_QIF_UAVG			6

#define DA9150_QIF_UAVG_SIZE		DA9150_QIF_LONG_SIZE

#define DA9150_QIF_IAVG			8

#define DA9150_QIF_IAVG_SIZE		DA9150_QIF_LONG_SIZE

#define DA9150_QIF_NTCAVG		12

#define DA9150_QIF_NTCAVG_SIZE		DA9150_QIF_LONG_SIZE

#define DA9150_QIF_SHUNT_VAL		36

#define DA9150_QIF_SHUNT_VAL_SIZE	DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_SD_GAIN		38

#define DA9150_QIF_SD_GAIN_SIZE		DA9150_QIF_LONG_SIZE

#define DA9150_QIF_FCC_MAH		40

#define DA9150_QIF_FCC_MAH_SIZE		DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_SOC_PCT		43

#define DA9150_QIF_SOC_PCT_SIZE		DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_CHARGE_LIMIT		44

#define DA9150_QIF_CHARGE_LIMIT_SIZE	DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_DISCHARGE_LIMIT	45

#define DA9150_QIF_DISCHARGE_LIMIT_SIZE	DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_FW_MAIN_VER		118

#define DA9150_QIF_FW_MAIN_VER_SIZE	DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_E_FG_STATUS		126

#define DA9150_QIF_E_FG_STATUS_SIZE	DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_SYNC			127

#define DA9150_QIF_SYNC_SIZE		DA9150_QIF_SHORT_SIZE

#define DA9150_QIF_MAX_CODES		128

/* QIF Sync Timeout */

#define DA9150_QIF_SYNC_TIMEOUT		1000

#define DA9150_QIF_SYNC_RETRIES		10

/* QIF E_FG_STATUS */

#define DA9150_FG_IRQ_LOW_SOC_MASK	(1 << 0)

#define DA9150_FG_IRQ_HIGH_SOC_MASK	(1 << 1)

#define DA9150_FG_IRQ_SOC_MASK	\
	(DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK)

/* Private data */

struct da9150_fg {
	
struct da9150 *da9150;
	
struct device *dev;

	
struct mutex io_lock;

	
struct power_supply *battery;
	
struct delayed_work work;
	
u32 interval;

	
int warn_soc;
	
int crit_soc;
	
int soc;
};

/* Battery Properties */

static u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size) { u8 buf[size]; u8 read_addr; u32 res = 0; int i; /* Set QIF code (READ mode) */ read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ; da9150_read_qif(fg->da9150, read_addr, size, buf); for (i = 0; i < size; ++i) res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE)); return res; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson89100.00%1100.00%
Total89100.00%1100.00%


static void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size, u32 val) { u8 buf[size]; u8 write_addr; int i; /* Set QIF code (WRITE mode) */ write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE; for (i = 0; i < size; ++i) { buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) & DA9150_QIF_BYTE_MASK; } da9150_write_qif(fg->da9150, write_addr, size, buf); }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson88100.00%1100.00%
Total88100.00%1100.00%

/* Trigger QIF Sync to update QIF readable data */
static void da9150_fg_read_sync_start(struct da9150_fg *fg) { int i = 0; u32 res = 0; mutex_lock(&fg->io_lock); /* Check if QIF sync already requested, and write to sync if not */ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, DA9150_QIF_SYNC_SIZE); if (res > 0) da9150_fg_write_attr(fg, DA9150_QIF_SYNC, DA9150_QIF_SYNC_SIZE, 0); /* Wait for sync to complete */ res = 0; while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { usleep_range(DA9150_QIF_SYNC_TIMEOUT, DA9150_QIF_SYNC_TIMEOUT * 2); res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, DA9150_QIF_SYNC_SIZE); } /* Check if sync completed */ if (res == 0) dev_err(fg->dev, "Failed to perform QIF read sync!\n"); }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson116100.00%1100.00%
Total116100.00%1100.00%

/* * Should always be called after QIF sync read has been performed, and all * attributes required have been accessed. */
static inline void da9150_fg_read_sync_end(struct da9150_fg *fg) { mutex_unlock(&fg->io_lock); }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson20100.00%1100.00%
Total20100.00%1100.00%

/* Sync read of single QIF attribute */
static u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size) { u32 val; da9150_fg_read_sync_start(fg); val = da9150_fg_read_attr(fg, code, size); da9150_fg_read_sync_end(fg); return val; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson44100.00%1100.00%
Total44100.00%1100.00%

/* Wait for QIF Sync, write QIF data and wait for ack */
static void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size, u32 val) { int i = 0; u32 res = 0, sync_val; mutex_lock(&fg->io_lock); /* Check if QIF sync already requested */ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, DA9150_QIF_SYNC_SIZE); /* Wait for an existing sync to complete */ while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { usleep_range(DA9150_QIF_SYNC_TIMEOUT, DA9150_QIF_SYNC_TIMEOUT * 2); res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, DA9150_QIF_SYNC_SIZE); } if (res == 0) { dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n"); mutex_unlock(&fg->io_lock); return; } /* Write value for QIF code */ da9150_fg_write_attr(fg, code, size, val); /* Wait for write acknowledgment */ i = 0; sync_val = res; while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) { usleep_range(DA9150_QIF_SYNC_TIMEOUT, DA9150_QIF_SYNC_TIMEOUT * 2); res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, DA9150_QIF_SYNC_SIZE); } mutex_unlock(&fg->io_lock); /* Check write was actually successful */ if (res != (sync_val + 1)) dev_err(fg->dev, "Error performing QIF sync write for code %d\n", code); }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson204100.00%1100.00%
Total204100.00%1100.00%

/* Power Supply attributes */
static int da9150_fg_capacity(struct da9150_fg *fg, union power_supply_propval *val) { val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, DA9150_QIF_SOC_PCT_SIZE); if (val->intval > 100) val->intval = 100; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson46100.00%1100.00%
Total46100.00%1100.00%


static int da9150_fg_current_avg(struct da9150_fg *fg, union power_supply_propval *val) { u32 iavg, sd_gain, shunt_val; u64 div, res; da9150_fg_read_sync_start(fg); iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG, DA9150_QIF_IAVG_SIZE); shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL, DA9150_QIF_SHUNT_VAL_SIZE); sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN, DA9150_QIF_SD_GAIN_SIZE); da9150_fg_read_sync_end(fg); div = (u64) (sd_gain * shunt_val * 65536ULL); do_div(div, 1000000); res = (u64) (iavg * 1000000ULL); do_div(res, div); val->intval = (int) res; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson121100.00%1100.00%
Total121100.00%1100.00%


static int da9150_fg_voltage_avg(struct da9150_fg *fg, union power_supply_propval *val) { u64 res; val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG, DA9150_QIF_UAVG_SIZE); res = (u64) (val->intval * 186ULL); do_div(res, 10000); val->intval = (int) res; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson64100.00%1100.00%
Total64100.00%1100.00%


static int da9150_fg_charge_full(struct da9150_fg *fg, union power_supply_propval *val) { val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH, DA9150_QIF_FCC_MAH_SIZE); val->intval = val->intval * 1000; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson42100.00%1100.00%
Total42100.00%1100.00%

/* * Temperature reading from device is only valid if battery/system provides * valid NTC to associated pin of DA9150 chip. */
static int da9150_fg_temp(struct da9150_fg *fg, union power_supply_propval *val) { val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG, DA9150_QIF_NTCAVG_SIZE); val->intval = (val->intval * 10) / 1048576; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson46100.00%1100.00%
Total46100.00%1100.00%

static enum power_supply_property da9150_fg_props[] = { POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_VOLTAGE_AVG, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_TEMP, };
static int da9150_fg_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct da9150_fg *fg = dev_get_drvdata(psy->dev.parent); int ret; switch (psp) { case POWER_SUPPLY_PROP_CAPACITY: ret = da9150_fg_capacity(fg, val); break; case POWER_SUPPLY_PROP_CURRENT_AVG: ret = da9150_fg_current_avg(fg, val); break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: ret = da9150_fg_voltage_avg(fg, val); break; case POWER_SUPPLY_PROP_CHARGE_FULL: ret = da9150_fg_charge_full(fg, val); break; case POWER_SUPPLY_PROP_TEMP: ret = da9150_fg_temp(fg, val); break; default: ret = -EINVAL; break; } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson118100.00%1100.00%
Total118100.00%1100.00%

/* Repeated SOC check */
static bool da9150_fg_soc_changed(struct da9150_fg *fg) { union power_supply_propval val; da9150_fg_capacity(fg, &val); if (val.intval != fg->soc) { fg->soc = val.intval; return true; } return false; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson49100.00%1100.00%
Total49100.00%1100.00%


static void da9150_fg_work(struct work_struct *work) { struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work); /* Report if SOC has changed */ if (da9150_fg_soc_changed(fg)) power_supply_changed(fg->battery); schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval)); }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson58100.00%1100.00%
Total58100.00%1100.00%

/* SOC level event configuration */
static void da9150_fg_soc_event_config(struct da9150_fg *fg) { int soc; soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, DA9150_QIF_SOC_PCT_SIZE); if (soc > fg->warn_soc) { /* If SOC > warn level, set discharge warn level event */ da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, DA9150_QIF_DISCHARGE_LIMIT_SIZE, fg->warn_soc + 1); } else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) { /* * If SOC <= warn level, set discharge crit level event, * and set charge warn level event. */ da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, DA9150_QIF_DISCHARGE_LIMIT_SIZE, fg->crit_soc + 1); da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, DA9150_QIF_CHARGE_LIMIT_SIZE, fg->warn_soc); } else if (soc <= fg->crit_soc) { /* If SOC <= crit level, set charge crit level event */ da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, DA9150_QIF_CHARGE_LIMIT_SIZE, fg->crit_soc); } }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson126100.00%1100.00%
Total126100.00%1100.00%


static irqreturn_t da9150_fg_irq(int irq, void *data) { struct da9150_fg *fg = data; u32 e_fg_status; /* Read FG IRQ status info */ e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS, DA9150_QIF_E_FG_STATUS_SIZE); /* Handle warning/critical threhold events */ if (e_fg_status & DA9150_FG_IRQ_SOC_MASK) da9150_fg_soc_event_config(fg); /* Clear any FG IRQs */ da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS, DA9150_QIF_E_FG_STATUS_SIZE, e_fg_status); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson62100.00%1100.00%
Total62100.00%1100.00%


static struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev) { struct device_node *fg_node = dev->of_node; struct da9150_fg_pdata *pdata; pdata = devm_kzalloc(dev, sizeof(struct da9150_fg_pdata), GFP_KERNEL); if (!pdata) return NULL; of_property_read_u32(fg_node, "dlg,update-interval", &pdata->update_interval); of_property_read_u8(fg_node, "dlg,warn-soc-level", &pdata->warn_soc_lvl); of_property_read_u8(fg_node, "dlg,crit-soc-level", &pdata->crit_soc_lvl); return pdata; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson89100.00%1100.00%
Total89100.00%1100.00%

static const struct power_supply_desc fg_desc = { .name = "da9150-fg", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = da9150_fg_props, .num_properties = ARRAY_SIZE(da9150_fg_props), .get_property = da9150_fg_get_prop, };
static int da9150_fg_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct da9150 *da9150 = dev_get_drvdata(dev->parent); struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev); struct da9150_fg *fg; int ver, irq, ret = 0; fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL); if (fg == NULL) return -ENOMEM; platform_set_drvdata(pdev, fg); fg->da9150 = da9150; fg->dev = dev; mutex_init(&fg->io_lock); /* Enable QIF */ da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK, DA9150_FG_QIF_EN_MASK); fg->battery = devm_power_supply_register(dev, &fg_desc, NULL); if (IS_ERR(fg->battery)) { ret = PTR_ERR(fg->battery); return ret; } ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER, DA9150_QIF_FW_MAIN_VER_SIZE); dev_info(dev, "Version: 0x%x\n", ver); /* Handle DT data if provided */ if (dev->of_node) { fg_pdata = da9150_fg_dt_pdata(dev); dev->platform_data = fg_pdata; } /* Handle any pdata provided */ if (fg_pdata) { fg->interval = fg_pdata->update_interval; if (fg_pdata->warn_soc_lvl > 100) dev_warn(dev, "Invalid SOC warning level provided, Ignoring"); else fg->warn_soc = fg_pdata->warn_soc_lvl; if ((fg_pdata->crit_soc_lvl > 100) || (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl)) dev_warn(dev, "Invalid SOC critical level provided, Ignoring"); else fg->crit_soc = fg_pdata->crit_soc_lvl; } /* Configure initial SOC level events */ da9150_fg_soc_event_config(fg); /* * If an interval period has been provided then setup repeating * work for reporting data updates. */ if (fg->interval) { INIT_DELAYED_WORK(&fg->work, da9150_fg_work); schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval)); } /* Register IRQ */ irq = platform_get_irq_byname(pdev, "FG"); if (irq < 0) { dev_err(dev, "Failed to get IRQ FG: %d\n", irq); ret = irq; goto irq_fail; } ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, IRQF_ONESHOT, "FG", fg); if (ret) { dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); goto irq_fail; } return 0; irq_fail: if (fg->interval) cancel_delayed_work(&fg->work); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson410100.00%1100.00%
Total410100.00%1100.00%


static int da9150_fg_remove(struct platform_device *pdev) { struct da9150_fg *fg = platform_get_drvdata(pdev); if (fg->interval) cancel_delayed_work(&fg->work); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson38100.00%1100.00%
Total38100.00%1100.00%


static int da9150_fg_resume(struct platform_device *pdev) { struct da9150_fg *fg = platform_get_drvdata(pdev); /* * Trigger SOC check to happen now so as to indicate any value change * since last check before suspend. */ if (fg->interval) flush_delayed_work(&fg->work); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson39100.00%1100.00%
Total39100.00%1100.00%

static struct platform_driver da9150_fg_driver = { .driver = { .name = "da9150-fuel-gauge", }, .probe = da9150_fg_probe, .remove = da9150_fg_remove, .resume = da9150_fg_resume, }; module_platform_driver(da9150_fg_driver); MODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150"); MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Adam Thomson2218100.00%1100.00%
Total2218100.00%1100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.