BigW Consortium Gitlab

Commit 274de470 by David Clark

Merge branch 'master' of ssh://github.com/mangOH/mangOH

parents 4a5fb9f2 8ceb84bc
......@@ -35,3 +35,9 @@
[submodule "apps/Heartbeat"]
path = apps/Heartbeat
url = https://github.com/mangOH/Heartbeat
[submodule "apps/BatteryService"]
path = apps/BatteryService
url = https://github.com/mangOH/BatteryService
[submodule "samples/BatteryClient"]
path = samples/BatteryClient
url = https://github.com/mangOH/BatteryClient
Subproject commit cba3d131ca6fe030598fb8e2c1684c84ba8177ec
Subproject commit a33b09530162e22e0ac95ae84548d2808708cbf4
Subproject commit 18a0b13c4a5dc0096eefe029ed0c56974ec5e8e1
sources:
{
bq24190-charger.c
}
menuconfig POWER_SUPPLY
bool "Power supply class support"
help
Say Y here to enable power supply class support. This allows
power supply (batteries, AC, USB) monitoring by userspace
via sysfs and uevent (if available) and/or APM kernel interface
(if selected below).
if POWER_SUPPLY
config CHARGER_BQ24190
tristate "TI BQ24190 battery charger driver"
depends on I2C && GPIOLIB
help
Say Y to enable support for the TI BQ24190 battery charger.
endif # POWER_SUPPLY
Driver files were copied from yocto/kernel/drivers/power in revision TBC
/*
* Driver for the TI bq24190 battery charger.
*
* Author: Mark A. Greer <mgreer@animalcreek.com>
*
* 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/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include "bq24190-platform-data.h"
#define BQ24190_MANUFACTURER "Texas Instruments"
#define BQ24190_REG_ISC 0x00 /* Input Source Control */
#define BQ24190_REG_ISC_EN_HIZ_MASK BIT(7)
#define BQ24190_REG_ISC_EN_HIZ_SHIFT 7
#define BQ24190_REG_ISC_VINDPM_MASK (BIT(6) | BIT(5) | BIT(4) | \
BIT(3))
#define BQ24190_REG_ISC_VINDPM_SHIFT 3
#define BQ24190_REG_ISC_IINLIM_MASK (BIT(2) | BIT(1) | BIT(0))
#define BQ24190_REG_ISC_IINLIM_SHIFT 0
#define BQ24190_REG_POC 0x01 /* Power-On Configuration */
#define BQ24190_REG_POC_RESET_MASK BIT(7)
#define BQ24190_REG_POC_RESET_SHIFT 7
#define BQ24190_REG_POC_WDT_RESET_MASK BIT(6)
#define BQ24190_REG_POC_WDT_RESET_SHIFT 6
#define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4
#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1))
#define BQ24190_REG_POC_SYS_MIN_SHIFT 1
#define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0)
#define BQ24190_REG_POC_BOOST_LIM_SHIFT 0
#define BQ24190_REG_CCC 0x02 /* Charge Current Control */
#define BQ24190_REG_CCC_ICHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4) | BIT(3) | BIT(2))
#define BQ24190_REG_CCC_ICHG_SHIFT 2
#define BQ24190_REG_CCC_FORCE_20PCT_MASK BIT(0)
#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT 0
#define BQ24190_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */
#define BQ24190_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4))
#define BQ24190_REG_PCTCC_IPRECHG_SHIFT 4
#define BQ24190_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \
BIT(0))
#define BQ24190_REG_PCTCC_ITERM_SHIFT 0
#define BQ24190_REG_CVC 0x04 /* Charge Voltage Control */
#define BQ24190_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4) | BIT(3) | BIT(2))
#define BQ24190_REG_CVC_VREG_SHIFT 2
#define BQ24190_REG_CVC_BATLOWV_MASK BIT(1)
#define BQ24190_REG_CVC_BATLOWV_SHIFT 1
#define BQ24190_REG_CVC_VRECHG_MASK BIT(0)
#define BQ24190_REG_CVC_VRECHG_SHIFT 0
#define BQ24190_REG_CTTC 0x05 /* Charge Term/Timer Control */
#define BQ24190_REG_CTTC_EN_TERM_MASK BIT(7)
#define BQ24190_REG_CTTC_EN_TERM_SHIFT 7
#define BQ24190_REG_CTTC_TERM_STAT_MASK BIT(6)
#define BQ24190_REG_CTTC_TERM_STAT_SHIFT 6
#define BQ24190_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_CTTC_WATCHDOG_SHIFT 4
#define BQ24190_REG_CTTC_EN_TIMER_MASK BIT(3)
#define BQ24190_REG_CTTC_EN_TIMER_SHIFT 3
#define BQ24190_REG_CTTC_CHG_TIMER_MASK (BIT(2) | BIT(1))
#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT 1
#define BQ24190_REG_CTTC_JEITA_ISET_MASK BIT(0)
#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT 0
#define BQ24190_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */
#define BQ24190_REG_ICTRC_BAT_COMP_MASK (BIT(7) | BIT(6) | BIT(5))
#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT 5
#define BQ24190_REG_ICTRC_VCLAMP_MASK (BIT(4) | BIT(3) | BIT(2))
#define BQ24190_REG_ICTRC_VCLAMP_SHIFT 2
#define BQ24190_REG_ICTRC_TREG_MASK (BIT(1) | BIT(0))
#define BQ24190_REG_ICTRC_TREG_SHIFT 0
#define BQ24190_REG_MOC 0x07 /* Misc. Operation Control */
#define BQ24190_REG_MOC_DPDM_EN_MASK BIT(7)
#define BQ24190_REG_MOC_DPDM_EN_SHIFT 7
#define BQ24190_REG_MOC_TMR2X_EN_MASK BIT(6)
#define BQ24190_REG_MOC_TMR2X_EN_SHIFT 6
#define BQ24190_REG_MOC_BATFET_DISABLE_MASK BIT(5)
#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT 5
#define BQ24190_REG_MOC_JEITA_VSET_MASK BIT(4)
#define BQ24190_REG_MOC_JEITA_VSET_SHIFT 4
#define BQ24190_REG_MOC_INT_MASK_MASK (BIT(1) | BIT(0))
#define BQ24190_REG_MOC_INT_MASK_SHIFT 0
#define BQ24190_REG_SS 0x08 /* System Status */
#define BQ24190_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6))
#define BQ24190_REG_SS_VBUS_STAT_SHIFT 6
#define BQ24190_REG_SS_CHRG_STAT_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_SS_CHRG_STAT_SHIFT 4
#define BQ24190_REG_SS_DPM_STAT_MASK BIT(3)
#define BQ24190_REG_SS_DPM_STAT_SHIFT 3
#define BQ24190_REG_SS_PG_STAT_MASK BIT(2)
#define BQ24190_REG_SS_PG_STAT_SHIFT 2
#define BQ24190_REG_SS_THERM_STAT_MASK BIT(1)
#define BQ24190_REG_SS_THERM_STAT_SHIFT 1
#define BQ24190_REG_SS_VSYS_STAT_MASK BIT(0)
#define BQ24190_REG_SS_VSYS_STAT_SHIFT 0
#define BQ24190_REG_F 0x09 /* Fault */
#define BQ24190_REG_F_WATCHDOG_FAULT_MASK BIT(7)
#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT 7
#define BQ24190_REG_F_BOOST_FAULT_MASK BIT(6)
#define BQ24190_REG_F_BOOST_FAULT_SHIFT 6
#define BQ24190_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_F_CHRG_FAULT_SHIFT 4
#define BQ24190_REG_F_BAT_FAULT_MASK BIT(3)
#define BQ24190_REG_F_BAT_FAULT_SHIFT 3
#define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0))
#define BQ24190_REG_F_NTC_FAULT_SHIFT 0
#define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */
#define BQ24190_REG_VPRS_PN_MASK (BIT(7) | BIT(6) | BIT(5))
#define BQ24190_REG_VPRS_PN_SHIFT 5
#define BQ24190_REG_VPRS_PN_24190 0x1
#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193 */
#define BQ24190_REG_VPRS_PN_24192I 0x3
#define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2)
#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2
#define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0))
#define BQ24190_REG_VPRS_DEV_REG_SHIFT 0
/*
* The FAULT register is latched by the bq24190 (except for NTC_FAULT)
* so the first read after a fault returns the latched value and subsequent
* reads return the current value. In order to return the fault status
* to the user, have the interrupt handler save the reg's value and retrieve
* it in the appropriate health/status routine. Each routine has its own
* flag indicating whether it should use the value stored by the last run
* of the interrupt handler or do an actual reg read. That way each routine
* can report back whatever fault may have occured.
*/
struct bq24190_dev_info {
struct i2c_client *client;
struct device *dev;
struct power_supply charger;
struct power_supply battery;
char model_name[I2C_NAME_SIZE];
kernel_ulong_t model;
unsigned int gpio_int;
unsigned int irq;
struct mutex f_reg_lock;
bool first_time;
bool charger_health_valid;
bool battery_health_valid;
bool battery_status_valid;
u8 f_reg;
u8 ss_reg;
u8 watchdog;
};
/*
* The tables below provide a 2-way mapping for the value that goes in
* the register field and the real-world value that it represents.
* The index of the array is the value that goes in the register; the
* number at that index in the array is the real-world value that it
* represents.
*/
/* REG02[7:2] (ICHG) in uAh */
static const int bq24190_ccc_ichg_values[] = {
512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000,
1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000,
1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000,
2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000,
2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000,
3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000,
3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000,
4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000
};
/* REG04[7:2] (VREG) in uV */
static const int bq24190_cvc_vreg_values[] = {
3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000,
3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000,
3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000,
3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000,
4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000,
4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000,
4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000,
4400000
};
/* REG06[1:0] (TREG) in tenths of degrees Celcius */
static const int bq24190_ictrc_treg_values[] = {
600, 800, 1000, 1200
};
/*
* Return the index in 'tbl' of greatest value that is less than or equal to
* 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that
* the values in 'tbl' are sorted from smallest to largest and 'tbl_size'
* is less than 2^8.
*/
static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v)
{
int i;
for (i = 1; i < tbl_size; i++)
if (v < tbl[i])
break;
return i - 1;
}
/* Basic driver I/O routines */
static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data)
{
int ret;
ret = i2c_smbus_read_byte_data(bdi->client, reg);
if (ret < 0)
return ret;
*data = ret;
return 0;
}
static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(bdi->client, reg, data);
}
static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg,
u8 mask, u8 shift, u8 *data)
{
u8 v;
int ret;
ret = bq24190_read(bdi, reg, &v);
if (ret < 0)
return ret;
v &= mask;
v >>= shift;
*data = v;
return 0;
}
static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg,
u8 mask, u8 shift, u8 data)
{
u8 v;
int ret;
ret = bq24190_read(bdi, reg, &v);
if (ret < 0)
return ret;
v &= ~mask;
v |= ((data << shift) & mask);
return bq24190_write(bdi, reg, v);
}
static int bq24190_get_field_val(struct bq24190_dev_info *bdi,
u8 reg, u8 mask, u8 shift,
const int tbl[], int tbl_size,
int *val)
{
u8 v;
int ret;
ret = bq24190_read_mask(bdi, reg, mask, shift, &v);
if (ret < 0)
return ret;
v = (v >= tbl_size) ? (tbl_size - 1) : v;
*val = tbl[v];
return 0;
}
static int bq24190_set_field_val(struct bq24190_dev_info *bdi,
u8 reg, u8 mask, u8 shift,
const int tbl[], int tbl_size,
int val)
{
u8 idx;
idx = bq24190_find_idx(tbl, tbl_size, val);
return bq24190_write_mask(bdi, reg, mask, shift, idx);
}
#ifdef CONFIG_SYSFS
/*
* There are a numerous options that are configurable on the bq24190
* that go well beyond what the power_supply properties provide access to.
* Provide sysfs access to them so they can be examined and possibly modified
* on the fly. They will be provided for the charger power_supply object only
* and will be prefixed by 'f_' to make them easier to recognize.
*/
#define BQ24190_SYSFS_FIELD(_name, r, f, m, store) \
{ \
.attr = __ATTR(f_##_name, m, bq24190_sysfs_show, store), \
.reg = BQ24190_REG_##r, \
.mask = BQ24190_REG_##r##_##f##_MASK, \
.shift = BQ24190_REG_##r##_##f##_SHIFT, \
}
#define BQ24190_SYSFS_FIELD_RW(_name, r, f) \
BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \
bq24190_sysfs_store)
#define BQ24190_SYSFS_FIELD_RO(_name, r, f) \
BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL)
static ssize_t bq24190_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t bq24190_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
struct bq24190_sysfs_field_info {
struct device_attribute attr;
u8 reg;
u8 mask;
u8 shift;
};
/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */
#undef SS
static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = {
/* sysfs name reg field in reg */
BQ24190_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ),
BQ24190_SYSFS_FIELD_RW(vindpm, ISC, VINDPM),
BQ24190_SYSFS_FIELD_RW(iinlim, ISC, IINLIM),
BQ24190_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG),
BQ24190_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN),
BQ24190_SYSFS_FIELD_RW(boost_lim, POC, BOOST_LIM),
BQ24190_SYSFS_FIELD_RW(ichg, CCC, ICHG),
BQ24190_SYSFS_FIELD_RW(force_20_pct, CCC, FORCE_20PCT),
BQ24190_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG),
BQ24190_SYSFS_FIELD_RW(iterm, PCTCC, ITERM),
BQ24190_SYSFS_FIELD_RW(vreg, CVC, VREG),
BQ24190_SYSFS_FIELD_RW(batlowv, CVC, BATLOWV),
BQ24190_SYSFS_FIELD_RW(vrechg, CVC, VRECHG),
BQ24190_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM),
BQ24190_SYSFS_FIELD_RW(term_stat, CTTC, TERM_STAT),
BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG),
BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER),
BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER),
BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET),
BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP),
BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP),
BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG),
BQ24190_SYSFS_FIELD_RW(dpdm_en, MOC, DPDM_EN),
BQ24190_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN),
BQ24190_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE),
BQ24190_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET),
BQ24190_SYSFS_FIELD_RO(int_mask, MOC, INT_MASK),
BQ24190_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT),
BQ24190_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT),
BQ24190_SYSFS_FIELD_RO(dpm_stat, SS, DPM_STAT),
BQ24190_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT),
BQ24190_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT),
BQ24190_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT),
BQ24190_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT),
BQ24190_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT),
BQ24190_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT),
BQ24190_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT),
BQ24190_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT),
BQ24190_SYSFS_FIELD_RO(pn, VPRS, PN),
BQ24190_SYSFS_FIELD_RO(ts_profile, VPRS, TS_PROFILE),
BQ24190_SYSFS_FIELD_RO(dev_reg, VPRS, DEV_REG),
};
static struct attribute *
bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1];
static const struct attribute_group bq24190_sysfs_attr_group = {
.attrs = bq24190_sysfs_attrs,
};
static void bq24190_sysfs_init_attrs(void)
{
int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
for (i = 0; i < limit; i++)
bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr;
bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */
}
static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup(
const char *name)
{
int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
for (i = 0; i < limit; i++)
if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name))
break;
if (i >= limit)
return NULL;
return &bq24190_sysfs_field_tbl[i];
}
static ssize_t bq24190_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
struct bq24190_sysfs_field_info *info;
int ret;
u8 v;
info = bq24190_sysfs_field_lookup(attr->attr.name);
if (!info)
return -EINVAL;
ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
if (ret)
return ret;
return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
}
static ssize_t bq24190_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
struct bq24190_sysfs_field_info *info;
int ret;
u8 v;
info = bq24190_sysfs_field_lookup(attr->attr.name);
if (!info)
return -EINVAL;
ret = kstrtou8(buf, 0, &v);
if (ret < 0)
return ret;
ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v);
if (ret)
return ret;
return count;
}
static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
{
bq24190_sysfs_init_attrs();
return sysfs_create_group(&bdi->charger.dev->kobj,
&bq24190_sysfs_attr_group);
}
static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi)
{
sysfs_remove_group(&bdi->charger.dev->kobj, &bq24190_sysfs_attr_group);
}
#else
static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
{
return 0;
}
static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {}
#endif
/*
* According to the "Host Mode and default Mode" section of the
* manual, a write to any register causes the bq24190 to switch
* from default mode to host mode. It will switch back to default
* mode after a WDT timeout unless the WDT is turned off as well.
* So, by simply turning off the WDT, we accomplish both with the
* same write.
*/
static int bq24190_set_mode_host(struct bq24190_dev_info *bdi)
{
int ret;
u8 v;
ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v);
if (ret < 0)
return ret;
bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >>
BQ24190_REG_CTTC_WATCHDOG_SHIFT);
v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK;
return bq24190_write(bdi, BQ24190_REG_CTTC, v);
}
static int bq24190_register_reset(struct bq24190_dev_info *bdi)
{
int ret, limit = 100;
u8 v;
/* Reset the registers */
ret = bq24190_write_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_RESET_MASK,
BQ24190_REG_POC_RESET_SHIFT,
0x1);
if (ret < 0)
return ret;
/* Reset bit will be cleared by hardware so poll until it is */
do {
ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_RESET_MASK,
BQ24190_REG_POC_RESET_SHIFT,
&v);
if (ret < 0)
return ret;
if (!v)
break;
udelay(10);
} while (--limit);
if (!limit)
return -EIO;
return 0;
}
/* Charger power supply property routines */
static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int type, ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_CHG_CONFIG_MASK,
BQ24190_REG_POC_CHG_CONFIG_SHIFT,
&v);
if (ret < 0)
return ret;
/* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */
if (!v) {
type = POWER_SUPPLY_CHARGE_TYPE_NONE;
} else {
ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
&v);
if (ret < 0)
return ret;
type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE :
POWER_SUPPLY_CHARGE_TYPE_FAST;
}
val->intval = type;
return 0;
}
static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
u8 chg_config, force_20pct, en_term;
int ret;
/*
* According to the "Termination when REG02[0] = 1" section of
* the bq24190 manual, the trickle charge could be less than the
* termination current so it recommends turning off the termination
* function.
*
* Note: AFAICT from the datasheet, the user will have to manually
* turn off the charging when in 20% mode. If its not turned off,
* there could be battery damage. So, use this mode at your own risk.
*/
switch (val->intval) {
case POWER_SUPPLY_CHARGE_TYPE_NONE:
chg_config = 0x0;
break;
case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
chg_config = 0x1;
force_20pct = 0x1;
en_term = 0x0;
break;
case POWER_SUPPLY_CHARGE_TYPE_FAST:
chg_config = 0x1;
force_20pct = 0x0;
en_term = 0x1;
break;
default:
return -EINVAL;
}
if (chg_config) { /* Enabling the charger */
ret = bq24190_write_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
force_20pct);
if (ret < 0)
return ret;
ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC,
BQ24190_REG_CTTC_EN_TERM_MASK,
BQ24190_REG_CTTC_EN_TERM_SHIFT,
en_term);
if (ret < 0)
return ret;
}
return bq24190_write_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_CHG_CONFIG_MASK,
BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config);
}
static int bq24190_charger_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->charger_health_valid) {
v = bdi->f_reg;
bdi->charger_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
/*
* This could be over-current or over-voltage but there's
* no way to tell which. Return 'OVERVOLTAGE' since there
* isn't an 'OVERCURRENT' value defined that we can return
* even if it was over-current.
*/
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else {
v &= BQ24190_REG_F_CHRG_FAULT_MASK;
v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
switch (v) {
case 0x0: /* Normal */
health = POWER_SUPPLY_HEALTH_GOOD;
break;
case 0x1: /* Input Fault (VBUS OVP or VBAT<VBUS<3.8V) */
/*
* This could be over-voltage or under-voltage
* and there's no way to tell which. Instead
* of looking foolish and returning 'OVERVOLTAGE'
* when its really under-voltage, just return
* 'UNSPEC_FAILURE'.
*/
health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
case 0x2: /* Thermal Shutdown */
health = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
case 0x3: /* Charge Safety Timer Expiration */
health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
break;
default:
health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
val->intval = health;
return 0;
}
static int bq24190_charger_get_online(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_SS,
BQ24190_REG_SS_PG_STAT_MASK,
BQ24190_REG_SS_PG_STAT_SHIFT, &v);
if (ret < 0)
return ret;
val->intval = v;
return 0;
}
static int bq24190_charger_get_current(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int curr, ret;
ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
bq24190_ccc_ichg_values,
ARRAY_SIZE(bq24190_ccc_ichg_values), &curr);
if (ret < 0)
return ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
if (ret < 0)
return ret;
/* If FORCE_20PCT is enabled, then current is 20% of ICHG value */
if (v)
curr /= 5;
val->intval = curr;
return 0;
}
static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1;
val->intval = bq24190_ccc_ichg_values[idx];
return 0;
}
static int bq24190_charger_set_current(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
u8 v;
int ret, curr = val->intval;
ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
if (ret < 0)
return ret;
/* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */
if (v)
curr *= 5;
return bq24190_set_field_val(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
bq24190_ccc_ichg_values,
ARRAY_SIZE(bq24190_ccc_ichg_values), curr);
}
static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int voltage, ret;
ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC,
BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
bq24190_cvc_vreg_values,
ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage);
if (ret < 0)
return ret;
val->intval = voltage;
return 0;
}
static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1;
val->intval = bq24190_cvc_vreg_values[idx];
return 0;
}
static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
return bq24190_set_field_val(bdi, BQ24190_REG_CVC,
BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
bq24190_cvc_vreg_values,
ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval);
}
static int bq24190_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq24190_charger_get_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = bq24190_charger_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = bq24190_charger_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq24190_charger_get_current(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
ret = bq24190_charger_get_current_max(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq24190_charger_get_voltage(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
ret = bq24190_charger_get_voltage_max(bdi, val);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bdi->model_name;
ret = 0;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ24190_MANUFACTURER;
ret = 0;
break;
default:
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq24190_charger_set_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq24190_charger_set_current(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq24190_charger_set_voltage(bdi, val);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property bq24190_charger_properties[] = {
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static char *bq24190_charger_supplied_to[] = {
"main-battery",
};
static void bq24190_charger_init(struct power_supply *charger)
{
charger->name = "bq24190-charger";
charger->type = POWER_SUPPLY_TYPE_USB;
charger->properties = bq24190_charger_properties;
charger->num_properties = ARRAY_SIZE(bq24190_charger_properties);
charger->supplied_to = bq24190_charger_supplied_to;
charger->num_supplies = ARRAY_SIZE(bq24190_charger_supplied_to);
charger->get_property = bq24190_charger_get_property;
charger->set_property = bq24190_charger_set_property;
charger->property_is_writeable = bq24190_charger_property_is_writeable;
}
/* Battery power supply property routines */
static int bq24190_battery_get_status(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 ss_reg, chrg_fault;
int status, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_status_valid) {
chrg_fault = bdi->f_reg;
bdi->battery_status_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault);
if (ret < 0)
return ret;
}
chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK;
chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
if (ret < 0)
return ret;
/*
* The battery must be discharging when any of these are true:
* - there is no good power source;
* - there is a charge fault.
* Could also be discharging when in "supplement mode" but
* there is no way to tell when its in that mode.
*/
if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) {
status = POWER_SUPPLY_STATUS_DISCHARGING;
} else {
ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK;
ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT;
switch (ss_reg) {
case 0x0: /* Not Charging */
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case 0x1: /* Pre-charge */
case 0x2: /* Fast Charging */
status = POWER_SUPPLY_STATUS_CHARGING;
break;
case 0x3: /* Charge Termination Done */
status = POWER_SUPPLY_STATUS_FULL;
break;
default:
ret = -EIO;
}
}
if (!ret)
val->intval = status;
return ret;
}
static int bq24190_battery_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_health_valid) {
v = bdi->f_reg;
bdi->battery_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else {
v &= BQ24190_REG_F_NTC_FAULT_MASK;
v >>= BQ24190_REG_F_NTC_FAULT_SHIFT;
switch (v) {
case 0x0: /* Normal */
health = POWER_SUPPLY_HEALTH_GOOD;
break;
case 0x1: /* TS1 Cold */
case 0x3: /* TS2 Cold */
case 0x5: /* Both Cold */
health = POWER_SUPPLY_HEALTH_COLD;
break;
case 0x2: /* TS1 Hot */
case 0x4: /* TS2 Hot */
case 0x6: /* Both Hot */
health = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
default:
health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
val->intval = health;
return 0;
}
static int bq24190_battery_get_online(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 batfet_disable;
int ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_MOC,
BQ24190_REG_MOC_BATFET_DISABLE_MASK,
BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable);
if (ret < 0)
return ret;
val->intval = !batfet_disable;
return 0;
}
static int bq24190_battery_set_online(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
return bq24190_write_mask(bdi, BQ24190_REG_MOC,
BQ24190_REG_MOC_BATFET_DISABLE_MASK,
BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval);
}
static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int temp, ret;
ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC,
BQ24190_REG_ICTRC_TREG_MASK,
BQ24190_REG_ICTRC_TREG_SHIFT,
bq24190_ictrc_treg_values,
ARRAY_SIZE(bq24190_ictrc_treg_values), &temp);
if (ret < 0)
return ret;
val->intval = temp;
return 0;
}
static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC,
BQ24190_REG_ICTRC_TREG_MASK,
BQ24190_REG_ICTRC_TREG_SHIFT,
bq24190_ictrc_treg_values,
ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval);
}
static int bq24190_battery_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, battery);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
ret = bq24190_battery_get_status(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = bq24190_battery_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = bq24190_battery_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
/* Could be Li-on or Li-polymer but no way to tell which */
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
ret = 0;
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = bq24190_battery_get_temp_alert_max(bdi, val);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
default:
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, battery);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_put_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = bq24190_battery_set_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = bq24190_battery_set_temp_alert_max(bdi, val);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property bq24190_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_SCOPE,
};
static void bq24190_battery_init(struct power_supply *battery)
{
battery->name = "bq24190-battery";
battery->type = POWER_SUPPLY_TYPE_BATTERY;
battery->properties = bq24190_battery_properties;
battery->num_properties = ARRAY_SIZE(bq24190_battery_properties);
battery->get_property = bq24190_battery_get_property;
battery->set_property = bq24190_battery_set_property;
battery->property_is_writeable = bq24190_battery_property_is_writeable;
}
static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
{
struct bq24190_dev_info *bdi = data;
bool alert_userspace = false;
u8 ss_reg, f_reg;
int ret;
pm_runtime_get_sync(bdi->dev);
ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
if (ret < 0) {
dev_err(bdi->dev, "Can't read SS reg: %d\n", ret);
goto out;
}
if (ss_reg != bdi->ss_reg) {
/*
* The device is in host mode so when PG_STAT goes from 1->0
* (i.e., power removed) HIZ needs to be disabled.
*/
if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) &&
!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) {
ret = bq24190_write_mask(bdi, BQ24190_REG_ISC,
BQ24190_REG_ISC_EN_HIZ_MASK,
BQ24190_REG_ISC_EN_HIZ_SHIFT,
0);
if (ret < 0)
dev_err(bdi->dev, "Can't access ISC reg: %d\n",
ret);
}
bdi->ss_reg = ss_reg;
alert_userspace = true;
}
mutex_lock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
if (ret < 0) {
mutex_unlock(&bdi->f_reg_lock);
dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
goto out;
}
if (f_reg != bdi->f_reg) {
bdi->f_reg = f_reg;
bdi->charger_health_valid = true;
bdi->battery_health_valid = true;
bdi->battery_status_valid = true;
alert_userspace = true;
}
mutex_unlock(&bdi->f_reg_lock);
/*
* Sometimes bq24190 gives a steady trickle of interrupts even
* though the watchdog timer is turned off and neither the STATUS
* nor FAULT registers have changed. Weed out these sprurious
* interrupts so userspace isn't alerted for no reason.
* In addition, the chip always generates an interrupt after
* register reset so we should ignore that one (the very first
* interrupt received).
*/
if (alert_userspace && !bdi->first_time) {
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
bdi->first_time = false;
}
out:
pm_runtime_put_sync(bdi->dev);
dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg);
return IRQ_HANDLED;
}
static int bq24190_hw_init(struct bq24190_dev_info *bdi)
{
u8 v;
int ret;
pm_runtime_get_sync(bdi->dev);
/* First check that the device really is what its supposed to be */
ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS,
BQ24190_REG_VPRS_PN_MASK,
BQ24190_REG_VPRS_PN_SHIFT,
&v);
if (ret < 0)
goto out;
if (v != bdi->model) {
ret = -ENODEV;
goto out;
}
ret = bq24190_register_reset(bdi);
if (ret < 0)
goto out;
ret = bq24190_set_mode_host(bdi);
out:
pm_runtime_put_sync(bdi->dev);
return ret;
}
#ifdef CONFIG_OF
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
{
bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
if (bdi->irq <= 0)
return -1;
return 0;
}
#else
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
{
return -1;
}
#endif
static int bq24190_setup_pdata(struct bq24190_dev_info *bdi,
struct bq24190_platform_data *pdata)
{
int ret;
if (!gpio_is_valid(pdata->gpio_int))
return -1;
ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev));
if (ret < 0)
return -1;
ret = gpio_direction_input(pdata->gpio_int);
if (ret < 0)
goto out;
bdi->irq = gpio_to_irq(pdata->gpio_int);
if (!bdi->irq)
goto out;
bdi->gpio_int = pdata->gpio_int;
return 0;
out:
gpio_free(pdata->gpio_int);
return -1;
}
static int bq24190_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct device *dev = &client->dev;
struct bq24190_platform_data *pdata = client->dev.platform_data;
struct bq24190_dev_info *bdi;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL);
if (!bdi) {
dev_err(dev, "Can't alloc bdi struct\n");
return -ENOMEM;
}
bdi->client = client;
bdi->dev = dev;
bdi->model = id->driver_data;
strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
mutex_init(&bdi->f_reg_lock);
bdi->first_time = true;
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
i2c_set_clientdata(client, bdi);
if (dev->of_node) {
ret = bq24190_setup_dt(bdi);
} else {
ret = bq24190_setup_pdata(bdi, pdata);
}
if (ret) {
dev_err(&client->dev, "Can't get irq info\n");
return -EINVAL;
}
ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
bq24190_irq_handler_thread,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bq24190-charger", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
goto out1;
}
pm_runtime_enable(dev);
pm_runtime_resume(dev);
ret = bq24190_hw_init(bdi);
if (ret < 0) {
dev_err(dev, "Hardware init failed\n");
goto out2;
}
bq24190_charger_init(&bdi->charger);
ret = power_supply_register(dev, &bdi->charger);
if (ret) {
dev_err(dev, "Can't register charger\n");
goto out2;
}
bq24190_battery_init(&bdi->battery);
ret = power_supply_register(dev, &bdi->battery);
if (ret) {
dev_err(dev, "Can't register battery\n");
goto out3;
}
ret = bq24190_sysfs_create_group(bdi);
if (ret) {
dev_err(dev, "Can't create sysfs entries\n");
goto out4;
}
return 0;
out4:
power_supply_unregister(&bdi->battery);
out3:
power_supply_unregister(&bdi->charger);
out2:
pm_runtime_disable(dev);
out1:
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return ret;
}
static int bq24190_remove(struct i2c_client *client)
{
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
bq24190_sysfs_remove_group(bdi);
power_supply_unregister(&bdi->battery);
power_supply_unregister(&bdi->charger);
pm_runtime_disable(bdi->dev);
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int bq24190_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
return 0;
}
static int bq24190_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
/* Things may have changed while suspended so alert upper layer */
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume);
/*
* Only support the bq24190 right now. The bq24192, bq24192i, and bq24193
* are similar but not identical so the driver needs to be extended to
* support them.
*/
static const struct i2c_device_id bq24190_i2c_ids[] = {
{ "bq24190", BQ24190_REG_VPRS_PN_24190 },
{ },
};
#ifdef CONFIG_OF
static const struct of_device_id bq24190_of_match[] = {
{ .compatible = "ti,bq24190", },
{ },
};
MODULE_DEVICE_TABLE(of, bq24190_of_match);
#else
static const struct of_device_id bq24190_of_match[] = {
{ },
};
#endif
static struct i2c_driver bq24190_driver = {
.probe = bq24190_probe,
.remove = bq24190_remove,
.id_table = bq24190_i2c_ids,
.driver = {
.name = "bq24190-charger",
.owner = THIS_MODULE,
.pm = &bq24190_pm_ops,
.of_match_table = of_match_ptr(bq24190_of_match),
},
};
module_i2c_driver(bq24190_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>");
MODULE_ALIAS("i2c:bq24190-charger");
MODULE_DESCRIPTION("TI BQ24190 Charger Driver");
/*
* Platform data for the TI bq24190 battery charger driver.
*
* 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.
*/
#ifndef BQ24190_PLATFORM_DATA_H_
#define BQ24190_PLATFORM_DATA_H_
struct bq24190_platform_data {
int gpio_int; /* GPIO pin that's connected to INT# */
};
#endif
sources:
{
iot-slot-core.c
iot-slot-eeprom.c
}
params:
{
}
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/i2c.h>
#include "iot-slot-eeprom.h"
#include "iot-slot.h"
#ifndef CONFIG_IOT_SLOT_NUM_SUPPORTED
#define CONFIG_IOT_SLOT_NUM_SUPPORTED 3
#endif
#ifndef CONFIG_IOT_SLOT_MAX_INTERFACES_PER_CARD
#define CONFIG_IOT_SLOT_MAX_INTERFACES_PER_CARD 10
#endif /* CONFIG_IOT_SLOT_MAX_INTERFACES_PER_CARD */
enum iot_slot_interface {
IOT_SLOT_INTERFACE_GPIO,
IOT_SLOT_INTERFACE_I2C,
IOT_SLOT_INTERFACE_SPI,
IOT_SLOT_INTERFACE_USB,
IOT_SLOT_INTERFACE_SDIO,
IOT_SLOT_INTERFACE_ADC,
IOT_SLOT_INTERFACE_PCM,
IOT_SLOT_INTERFACE_CLK,
IOT_SLOT_INTERFACE_UART,
IOT_SLOT_INTERFACE_PLATFORM,
};
struct iot_slot_interface_data {
enum iot_slot_interface type;
union {
struct {
struct i2c_client* client;
} i2c;
struct {
struct spi_device* device;
} spi;
} data;
};
struct iot_slot {
struct platform_device *pdev;
bool card_present;
struct i2c_adapter *i2c_adapter;
unsigned int num_interfaces;
struct iot_slot_interface_data interfaces[
CONFIG_IOT_SLOT_MAX_INTERFACES_PER_CARD];
};
static int iot_slot_add_gpio(struct iot_slot *slot,
struct list_head *gpio_item);
static int iot_slot_add_i2c(struct iot_slot *slot, struct list_head *i2c_item);
static int iot_slot_add_spi(struct iot_slot *slot, struct list_head *spi_item);
static int iot_slot_add_sdio(struct iot_slot *slot,
struct list_head *sdio_item);
static int iot_slot_add_adc(struct iot_slot *slot, struct list_head *adc_item);
static int iot_slot_add_pcm(struct iot_slot *slot, struct list_head *pcm_item);
static int iot_slot_add_uart(struct iot_slot *slot,
struct list_head *uart_item);
static void iot_slot_release_resources(struct iot_slot *slot);
static struct iot_slot *slots[CONFIG_IOT_SLOT_NUM_SUPPORTED];
static struct mutex management_mutex;
static int iot_slot_enumerate(struct iot_slot *slot)
{
int ret = 0;
struct list_head *item;
struct device* device;
struct iot_slot_platform_data *pdata;
int slot_index;
struct i2c_client *eeprom;
device = &slot->pdev->dev;
pdata = dev_get_platdata(device);
slot_index = slot->pdev->id;
slot->card_present =
gpio_get_value_cansleep(pdata->card_detect_gpio) == 0;
if (slot->card_present) {
dev_info(device, "Detected IoT card on slot %d\n", slot_index);
} else {
dev_dbg(device, "No IoT card detected on slot %d\n", slot_index);
goto done;
}
/* Set card detect to output high to activate the eeprom */
ret = gpio_direction_output(pdata->card_detect_gpio, 1);
if (ret != 0) {
dev_err(device, "Couldn't set card detect to output\n");
}
eeprom = eeprom_load(slot->i2c_adapter);
if (eeprom == NULL)
{
dev_warn(device,
"Card detected on IoT slot %d, but no eeprom was detected\n",
slot_index);
goto restore_card_detect;
}
/* Take the IoT card out of reset */
gpio_set_value_cansleep(pdata->reset_gpio, 1);
/*
* Give the IoT card 10ms to get ready before we start accessing it.
*/
msleep(10);
list_for_each(item, eeprom_if_list(eeprom)) {
enum EepromInterface interface_type;
if (slot->num_interfaces >=
CONFIG_IOT_SLOT_MAX_INTERFACES_PER_CARD) {
dev_info(
device,
"Card contains more than the maximum supported number of interfaces (%d)\n",
CONFIG_IOT_SLOT_MAX_INTERFACES_PER_CARD);
ret = -ERANGE;
goto enumeration_fail;
}
interface_type = eeprom_if_type(item);
switch (interface_type) {
case EEPROM_IF_GPIO:
dev_info(device,
"Found GPIO interface specification\n");
ret = iot_slot_add_gpio(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_I2C:
dev_info(device,
"Found I2C interface specification\n");
ret = iot_slot_add_i2c(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_SPI:
dev_info(device,
"Found SPI interface specification\n");
ret = iot_slot_add_spi(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_USB:
dev_info(device, "Found USB interface specification\n");
slot->interfaces[slot->num_interfaces].type =
IOT_SLOT_INTERFACE_CLK;
slot->num_interfaces++;
break;
case EEPROM_IF_SDIO:
dev_info(device,
"Found SDIO interface specification\n");
ret = iot_slot_add_sdio(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_ADC:
dev_info(device, "Found ADC interface specification\n");
ret = iot_slot_add_adc(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_PCM:
dev_info(device, "Found PCM interface specification\n");
ret = iot_slot_add_pcm(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_CLK:
dev_info(device, "Found PPS interface specification\n");
slot->interfaces[slot->num_interfaces].type =
IOT_SLOT_INTERFACE_CLK;
slot->num_interfaces++;
break;
case EEPROM_IF_UART:
dev_info(device,
"Found UART interface specification\n");
ret = iot_slot_add_uart(slot, item);
if (ret != 0) {
goto enumeration_fail;
}
break;
case EEPROM_IF_PLAT:
/* TODO: platorm type isn't properly supported yet */
dev_info(device,
"Found platofrm interface specification\n");
slot->interfaces[slot->num_interfaces].type =
IOT_SLOT_INTERFACE_PLATFORM;
slot->num_interfaces++;
break;
default:
break;
}
}
goto unload_eeprom;
enumeration_fail:
iot_slot_release_resources(slot);
unload_eeprom:
/* Free EEPROM and restore GPIO direction */
eeprom_unload(eeprom);
restore_card_detect:
gpio_direction_input(pdata->card_detect_gpio);
done:
return ret;
}
static int iot_slot_add_gpio(struct iot_slot *slot,
struct list_head *gpio_item)
{
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
int i;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
int slot_index = pdev->id;
int ret = 0;
char gpio_label[32];
for (i = 0; i < 4; i++) {
uint8_t cfg = eeprom_if_gpio_cfg(gpio_item, i);
ret = snprintf(gpio_label, ARRAY_SIZE(gpio_label),
"IoT slot %d gpio %d", slot_index, i);
BUG_ON(ret >= ARRAY_SIZE(gpio_label));
ret = devm_gpio_request_one(device,
pdata->gpio[i],
GPIOF_IN, gpio_label);
if (ret != 0) {
int j;
dev_err(device,
"Couldn't acquire gpio %d on IoT slot %d\n", i,
slot_index);
for (j = 0; j < i; j++) {
devm_gpio_free(device, pdata->gpio[j]);
return -EACCES;
}
}
switch (cfg)
{
case EEPROM_GPIO_CFG_OUTPUT_LOW:
gpio_direction_output(pdata->gpio[i], 0);
break;
case EEPROM_GPIO_CFG_OUTPUT_HIGH:
gpio_direction_output(pdata->gpio[i], 1);
break;
case EEPROM_GPIO_CFG_INPUT_PULL_UP:
gpio_direction_input(pdata->gpio[i]);
gpio_pull_up(pdata->gpio[i]);
break;
case EEPROM_GPIO_CFG_INPUT_PULL_DOWN:
gpio_direction_input(pdata->gpio[i]);
gpio_pull_down(pdata->gpio[i]);
break;
case EEPROM_GPIO_CFG_INPUT_FLOATING:
gpio_direction_input(pdata->gpio[i]);
break;
default:
/* reserved, ignore */
break;
}
}
slot->interfaces[slot->num_interfaces].type = IOT_SLOT_INTERFACE_GPIO;
slot->num_interfaces++;
return 0;
}
static int iot_slot_add_i2c(struct iot_slot *slot, struct list_head *i2c_item)
{
struct platform_device *pdev = slot->pdev;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct i2c_board_info board = {};
int irq_gpio;
/*
* NOTE: There's no need to request the i2c_adapter because that's
* already handled earlier when the eeprom is read.
*/
strncpy(board.type, eeprom_if_i2c_modalias(i2c_item),
sizeof(board.type));
irq_gpio = eeprom_if_i2c_irq_gpio(i2c_item);
if (irq_gpio != IRQ_GPIO_UNUSED)
{
board.irq = gpio_to_irq(pdata->gpio[irq_gpio]);
}
board.addr = eeprom_if_i2c_address(i2c_item);
slot->interfaces[slot->num_interfaces].type = IOT_SLOT_INTERFACE_I2C;
slot->interfaces[slot->num_interfaces].data.i2c.client =
i2c_new_device(slot->i2c_adapter, &board);
if (slot->interfaces[slot->num_interfaces].data.i2c.client == NULL) {
return -EINVAL;
}
slot->num_interfaces++;
return 0;
}
static int iot_slot_add_spi(struct iot_slot *slot, struct list_head *spi_item)
{
int ret = 0;
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct spi_master *spi_master;
int chip_select;
int irq_gpio;
struct spi_board_info board = {
.max_speed_hz = 2*1000*1000,
.mode = SPI_MODE_0,
.platform_data = NULL,
};
struct spi_device *spi_device;
ret = pdata->request_spi(&spi_master, &chip_select);
if (ret != 0) {
dev_err(device, "Couldn't get SPI master\n");
return -ENODEV;
}
board.chip_select = chip_select;
/* Assign IRQ number */
irq_gpio = eeprom_if_spi_irq_gpio(spi_item);
if (irq_gpio != IRQ_GPIO_UNUSED)
{
board.irq = gpio_to_irq(pdata->gpio[irq_gpio]);
}
strncpy(board.modalias, eeprom_if_spi_modalias(spi_item),
sizeof(board.modalias));
spi_device = spi_new_device(spi_master, &board);
if (spi_device == NULL) {
dev_err(device, "Couldn't add new SPI device\n");
pdata->release_spi();
return -ENODEV;
}
slot->interfaces[slot->num_interfaces].type = IOT_SLOT_INTERFACE_SPI;
slot->interfaces[slot->num_interfaces].data.spi.device = spi_device;
slot->num_interfaces++;
return 0;
}
static int iot_slot_add_sdio(struct iot_slot *slot, struct list_head *sdio_item)
{
int ret = 0;
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
if (pdata->request_sdio) {
ret = pdata->request_sdio();
if (ret != 0)
{
dev_err(device, "Couldn't request the SDIO interface\n");
ret = -ENODEV;
goto done;
}
}
slot->interfaces[slot->num_interfaces].type = IOT_SLOT_INTERFACE_SDIO;
slot->num_interfaces++;
done:
return ret;
}
static int iot_slot_add_adc(struct iot_slot *slot, struct list_head *adc_item)
{
int ret = 0;
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
if (pdata->request_adc) {
ret = pdata->request_adc();
if (ret != 0)
{
dev_err(device, "Couldn't request the ADC\n");
ret = -ENODEV;
goto done;
}
}
slot->interfaces[slot->num_interfaces].type = IOT_SLOT_INTERFACE_ADC;
slot->num_interfaces++;
done:
return ret;
}
static int iot_slot_add_pcm(struct iot_slot *slot, struct list_head *pcm_item)
{
int ret = 0;
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
if (pdata->request_pcm) {
ret = pdata->request_pcm();
if (ret != 0)
{
dev_err(device, "Couldn't request the PCM interface\n");
ret = -ENODEV;
goto done;
}
}
slot->interfaces[slot->num_interfaces].type = IOT_SLOT_INTERFACE_PCM;
slot->num_interfaces++;
done:
return ret;
}
static int iot_slot_add_uart(struct iot_slot *slot, struct list_head *uart_item)
{
int ret = 0;
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
struct iot_slot_platform_data *pdata = dev_get_platdata(&pdev->dev);
if (pdata->request_uart) {
/*
* TODO: Should the request_uart be providing some sort of uart handle
* that we can pass to a kernel module?
*/
ret = pdata->request_uart();
if (ret != 0)
{
dev_err(device, "Couldn't request the UART interface\n");
ret = -ENODEV;
goto done;
}
}
slot->interfaces[slot->num_interfaces].type =
IOT_SLOT_INTERFACE_UART;
slot->num_interfaces++;
done:
return ret;
}
static void iot_slot_release_resources(struct iot_slot *slot)
{
struct platform_device *pdev = slot->pdev;
struct device *device = &pdev->dev;
struct iot_slot_platform_data *pdata = dev_get_platdata(device);
int i;
for (i = 0; i < slot->num_interfaces; i++) {
int gpio;
dev_info(device, "On interface %d of type %d\n", i, slot->interfaces[i].type);
switch (slot->interfaces[i].type) {
case IOT_SLOT_INTERFACE_GPIO:
for (gpio = 0; gpio < ARRAY_SIZE(pdata->gpio); gpio++) {
devm_gpio_free(device, pdata->gpio[gpio]);
}
break;
case IOT_SLOT_INTERFACE_I2C:
i2c_unregister_device(
slot->interfaces[i].data.i2c.client);
/*
* NOTE: I2C is released lower down because it is
* acquired to read the eeprom
*/
break;
case IOT_SLOT_INTERFACE_SPI:
spi_unregister_device(
slot->interfaces[i].data.spi.device);
if (pdata->release_spi() != 0)
dev_err(device, "Couldn't release SPI\n");
break;
case IOT_SLOT_INTERFACE_USB:
/* No cleanup required */
break;
case IOT_SLOT_INTERFACE_SDIO:
if (pdata->release_sdio() != 0)
dev_err(device, "Couldn't release SDIO\n");
break;
case IOT_SLOT_INTERFACE_ADC:
if (pdata->release_adc() != 0)
dev_err(device, "Couldn't release ADC\n");
break;
case IOT_SLOT_INTERFACE_PCM:
if (pdata->release_pcm() != 0)
dev_err(device, "Couldn't release PCM\n");
break;
case IOT_SLOT_INTERFACE_CLK:
/* No cleanup required */
break;
case IOT_SLOT_INTERFACE_UART:
if (pdata->release_uart && pdata->release_uart() != 0)
dev_err(device, "Couldn't release UART\n");
break;
case IOT_SLOT_INTERFACE_PLATFORM:
/* TODO: ??? */
break;
default:
dev_err(device,
"Found interface type %d, which is not supported\n",
slot->interfaces[i].type);
break;
}
}
if (slot->i2c_adapter) {
int ret = pdata->release_i2c(&slot->i2c_adapter);
if (ret != 0) {
dev_err(device, "Failed to release i2c adapter");
}
slot->i2c_adapter = NULL;
}
devm_gpio_free(device, pdata->card_detect_gpio);
devm_gpio_free(device, pdata->reset_gpio);
slot->pdev = NULL;
slot->card_present = false;
}
static int iot_slot_request_essential_resources(struct iot_slot *slot)
{
int ret = 0;
struct device* device;
struct iot_slot_platform_data *pdata;
int slot_index;
char gpio_label[32];
device = &slot->pdev->dev;
pdata = dev_get_platdata(device);
slot_index = slot->pdev->id;
ret = snprintf(gpio_label, ARRAY_SIZE(gpio_label), "IoT slot %d reset",
slot_index);
BUG_ON(ret >= ARRAY_SIZE(gpio_label));
ret = devm_gpio_request_one(device, pdata->reset_gpio,
(GPIOF_OUT_INIT_HIGH | GPIOF_ACTIVE_LOW),
gpio_label);
if (ret != 0) {
dev_err(device, "Couldn't acquire reset gpio on IoT slot %d\n",
slot_index);
goto cleanup;
}
ret = snprintf(gpio_label, ARRAY_SIZE(gpio_label),
"IoT slot %d card detect", slot_index);
BUG_ON(ret >= ARRAY_SIZE(gpio_label));
ret = devm_gpio_request_one(device,
pdata->card_detect_gpio,
GPIOF_IN, gpio_label);
if (ret != 0) {
dev_err(device,
"Couldn't acquire card detect gpio on IoT slot %d\n",
slot_index);
goto cleanup;
}
ret = pdata->request_i2c(&slot->i2c_adapter);
if (ret != 0) {
// Couldn't get i2c adapter
goto cleanup;
}
return ret;
cleanup:
iot_slot_release_resources(slot);
return ret;
}
static int iot_slot_probe(struct platform_device *pdev)
{
int ret = 0;
int new_slot_index = pdev->id;
struct device *device = &pdev->dev;
struct iot_slot *slot;
if (new_slot_index < 0 ||
new_slot_index >= CONFIG_IOT_SLOT_NUM_SUPPORTED) {
dev_err(device, "IoT slot has invalid index (%d)\n",
new_slot_index);
ret = -EINVAL;
goto done;
}
mutex_lock(&management_mutex);
if (slots[new_slot_index] != NULL) {
dev_err(device,
"An IoT slot device has already been created for slot %d\n",
new_slot_index);
ret = -EINVAL;
goto unlock;
}
slot = devm_kzalloc(&pdev->dev, sizeof(*slot), GFP_KERNEL);
if (!slot) {
ret = -ENOMEM;
goto unlock;
}
slots[new_slot_index] = slot;
slot->pdev = pdev;
slot->num_interfaces = 0;
platform_set_drvdata(pdev, slot);
ret = iot_slot_request_essential_resources(slot);
if (ret != 0) {
dev_warn(device,
"Couldn't acquire resources required to perform IoT card enumeration\n");
goto cleanup;
}
ret = iot_slot_enumerate(slot);
if (ret != 0)
dev_warn(device,
"IoT card enumeration failed for slot %d\n",
new_slot_index);
cleanup:
if (ret != 0) {
kfree(slot);
slots[new_slot_index] = NULL;
}
unlock:
mutex_unlock(&management_mutex);
done:
return ret;
}
static int iot_slot_remove(struct platform_device *pdev)
{
int ret = 0;
struct iot_slot *slot = platform_get_drvdata(pdev);
const int slot_index = pdev->id;
if (slot_index < 0 || slot_index >= CONFIG_IOT_SLOT_NUM_SUPPORTED) {
dev_err(&pdev->dev, "IoT slot has invalid index (%d)\n",
slot_index);
ret = -EINVAL;
goto done;
}
if (slots[slot_index]->pdev == NULL) {
dev_err(&pdev->dev,
"Tried to remove IoT slot %d, but it does not exist\n",
slot_index);
ret = -EINVAL;
goto done;
}
if (slots[slot_index] != slot || slots[slot_index]->pdev != pdev) {
dev_err(&pdev->dev,
"The platform device which claims to be registered for IoT slot %d does not appear to be the actual device registered\n",
slot_index);
ret = -EINVAL;
goto done;
}
iot_slot_release_resources(slot);
kfree(slot);
slots[slot_index] = NULL;
done:
return ret;
}
static const struct platform_device_id iot_slot_ids[] = {
{"iot-slot", (kernel_ulong_t)0},
{},
};
MODULE_DEVICE_TABLE(platform, iot_slot_ids);
static struct platform_driver iot_slot_driver = {
.probe = iot_slot_probe,
.remove = iot_slot_remove,
.driver = {
.name = "iot-slot",
.owner = THIS_MODULE,
.bus = &platform_bus_type,
},
.id_table = iot_slot_ids,
};
static int __init iot_slot_init(void)
{
mutex_init(&management_mutex);
platform_driver_register(&iot_slot_driver);
return 0;
}
static void __exit iot_slot_exit(void)
{
platform_driver_unregister(&iot_slot_driver);
}
module_init(iot_slot_init);
module_exit(iot_slot_exit);
MODULE_ALIAS("platform:iot-slot");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sierra Wireless");
MODULE_DESCRIPTION("IoT slot management for IoT cards");
MODULE_VERSION("0.2");
/* Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef IOT_SLOT_EEPROM_1V0_H
#define IOT_SLOT_EEPROM_1V0_H
typedef struct eeprom_if_reserved_ {
char reserved[63];
} __attribute__((packed)) eeprom_if_reserved;
typedef struct eeprom_if_gpio_1v0_ {
uint8_t cfg[4];
char reserved[59];
} __attribute__((packed)) eeprom_if_gpio_1v0;
typedef struct eeprom_if_i2c_1v0_ {
uint8_t address;
uint8_t irq_gpio;
char modalias[32];
char reserved[29];
} __attribute__((packed)) eeprom_if_i2c_1v0;
typedef struct eeprom_if_spi_1v0_ {
uint8_t irq_gpio;
char modalias[32];
char reserved[30];
} __attribute__((packed)) eeprom_if_spi_1v0;
typedef eeprom_if_reserved eeprom_if_usb_1v0;
typedef eeprom_if_reserved eeprom_if_sdio_1v0;
typedef eeprom_if_reserved eeprom_if_adc_1v0;
typedef eeprom_if_reserved eeprom_if_pcm_1v0;
typedef eeprom_if_reserved eeprom_if_clk_1v0;
typedef eeprom_if_reserved eeprom_if_uart_1v0;
typedef struct eeprom_if_plat_1v0_ {
uint8_t irq_gpio;
char modalias[32];
char reserved[30];
} __attribute__((packed)) eeprom_if_plat_1v0;
typedef struct eeprom_if_1v0_ {
uint8_t type;
union {
eeprom_if_gpio_1v0 gpio;
eeprom_if_i2c_1v0 i2c;
eeprom_if_spi_1v0 spi;
eeprom_if_sdio_1v0 sdio;
eeprom_if_usb_1v0 usb;
eeprom_if_adc_1v0 adc;
eeprom_if_pcm_1v0 pcm;
eeprom_if_clk_1v0 clk;
eeprom_if_uart_1v0 uart;
eeprom_if_plat_1v0 plat;
} ifc;
} __attribute__((packed)) eeprom_if_1v0;
#define EEPROM_1V0_INTERFACE_OFFSET 192
#endif /* IOT_SLOT_EEPROM_1V0_H */
/* Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
/*
* EEPROMs on IoT cards contain information about the device(s) on the
* cards and busses it uses. For a detailed description of EEPROM
* format, please refer to the IoT card specification on MangOH web
* site www.mangoh.io.
*
* Each IoT card EEPROM contains a master header that (among other
* information) contains the EEPROM magic 0xAA55 that identifies a
* valid programmed EEPROM, and the version number. Parsing of EEPROM
* contents depends on the EEPROM version number, so this code makes
* maximum attempts to support backwards-compatibility of format.
*
* IoT cards use at24 compatible EEPROMs. IoT card configuration
* starts by loading EEPROM contents into memory and creating an
* EEPROM map to easily parse sections. At the top of the EEPROM there
* is a master header section that contains the board manufacturer,
* board name, serial number, etc. This section is followed by one or
* more card interface sections that describes busses and devices
* used on the IoT card. This information should be sufficient to
* identify and load the driver(s) for device(s) on the card.
*
* EEPROM map looks as follows: +----struct eeprom_map----+
* +--------------------------+<--------+-------buffer |
* | | +--+-------interfaces; |
* | Master Header | | +-------------------------+
* | | |
* +--------------------------+<---+ | +--struct eeprom_if_map---+
* | Interface Description 1 | +-+--+-----contents |
* +--------------------------+<-+ +--+---->list |
* | Interface Description 2 | | | +-------------------------+
* |--------------------------+ | |
* | ... | | | +--struct eeprom_if_map---+
* +--------------------------+ +---+--+-----contents |
* | Interface Description N | +--+---->list |
* +--------------------------+ | +-------------------------+
* . ...
* The master eeprom map and eeprom interface maps are used for easily
* locating the corresponding buffers, to reference each other, as
* well as to back-reference the eeprom device struct(s).
*/
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/gpio/driver.h>
#include <linux/platform_data/at24.h>
#include "iot-slot-eeprom.h"
#include "iot-slot-eeprom-1v0.h"
#define HOST_UINT8(buffer, offset) (*(uint8_t*)(&(buffer)[(offset)]))
#define HOST_UINT16(buffer, offset) ntohs(*(uint16_t*)(&(buffer)[(offset)]))
#define HOST_UINT32(buffer, offset) ntohl(*(uint32_t*)(&(buffer)[(offset)]))
#define EEPROM_VERSION(major, minor) (((major)&0xff) << 8 | ((minor)&0xff))
#define EEPROM_VERSION_MAJOR(version) (((version) & 0xff00) >> 8)
#define EEPROM_VERSION_MINOR(version) ((version) & 0xff)
#define IOT_EEPROM_SIZE 4096
/*
* Map in which EEPROM contents are read. This is global so make sure
* buffer is invalidated beforehand and EEPROMs are read out one-by-one.
*/
static struct eeprom_map {
uint8_t buffer[IOT_EEPROM_SIZE];
struct list_head interfaces;
} this_eeprom;
struct eeprom_if_map {
struct i2c_client *eeprom; /* back pointer to eeprom */
uint8_t *contents; /* pointer to interface description */
struct list_head list; /* interface list */
};
#define to_eeprom_if_map(item) container_of((item), struct eeprom_if_map, list)
static void at24_eeprom_setup(struct memory_accessor *mem_acc, void *context)
{
struct eeprom_map *map = (struct eeprom_map *)context;
/* Invalidate buffer before reading */
if (!map) {
pr_err("%s: Invalid buffer %p.\n", __func__, map);
return;
}
INIT_LIST_HEAD(&map->interfaces);
if (mem_acc->read(mem_acc, map->buffer, 0, sizeof(map->buffer))
!= sizeof(map->buffer)) {
/* Invalidate buffer again in case of failed/partial read */
pr_err("%s: Error reading from EEPROM.\n", __func__);
memset(map->buffer, 0xff, IOT_EEPROM_SIZE);
return;
}
}
static struct at24_platform_data at24_eeprom_data = {
.byte_len = IOT_EEPROM_SIZE,
.page_size = 32,
.flags = AT24_FLAG_ADDR16,
.setup = at24_eeprom_setup,
.context = &this_eeprom,
};
static struct i2c_board_info at24_eeprom_info = {
I2C_BOARD_INFO("at24", 0x52),
.platform_data = &at24_eeprom_data,
};
static inline uint8_t *to_eeprom_buffer(struct i2c_client *eeprom)
{
struct at24_platform_data *pdata = dev_get_platdata(&eeprom->dev);
uint8_t *buffer = ((struct eeprom_map *)pdata->context)->buffer;
if (0xAA != buffer[0] || 0x55 != buffer[1]) {
dev_err(&eeprom->dev, "Invalid header: %02x%02x.\n",
buffer[0], buffer[1]);
BUG();
return NULL;
}
return buffer;
}
static inline struct list_head *to_eeprom_if_list(struct i2c_client *eeprom)
{
struct at24_platform_data *pdata = dev_get_platdata(&eeprom->dev);
struct eeprom_map *map = (struct eeprom_map *)pdata->context;
return &map->interfaces;
}
#define VERSION_OFFSET 2
static uint16_t eeprom_version(struct i2c_client *eeprom)
{
uint8_t *buffer = to_eeprom_buffer(eeprom);
return HOST_UINT16(buffer, VERSION_OFFSET);
}
static void *eeprom_if_first(struct i2c_client *eeprom)
{
switch (eeprom_version(eeprom)) {
case EEPROM_VERSION(1, 0): {
uint8_t *buffer = to_eeprom_buffer(eeprom);
eeprom_if_1v0 *ifc =
(eeprom_if_1v0*)&buffer[EEPROM_1V0_INTERFACE_OFFSET];
return (ifc->type == EEPROM_IF_LAST ? NULL : ifc);
}
default:
BUG();
return NULL;
}
}
static void *eeprom_if_next(struct i2c_client *eeprom, void *prev)
{
switch (eeprom_version(eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *ifc = (eeprom_if_1v0*)prev;
return (ifc->type == EEPROM_IF_LAST ? NULL : ++ifc);
}
default:
BUG();
return NULL;
}
}
static void eeprom_free_interfaces(struct i2c_client *eeprom)
{
while (!list_empty(to_eeprom_if_list(eeprom))) {
struct eeprom_if_map *m;
m = list_first_entry(to_eeprom_if_list(eeprom),
struct eeprom_if_map, list);
list_del(&m->list);
kfree(m);
}
}
static int eeprom_load_interfaces(struct i2c_client *eeprom)
{
int count = 0;
void *ifc = eeprom_if_first(eeprom);
while (ifc) {
struct eeprom_if_map *m = kzalloc(sizeof(*m), GFP_KERNEL);
if (!m) {
dev_info(&eeprom->dev, "Out of memory.");
eeprom_free_interfaces(eeprom);
return -1;
}
m->eeprom = eeprom;
m->contents = ifc;
list_add_tail(&m->list, to_eeprom_if_list(eeprom));
count++;
ifc = eeprom_if_next(eeprom, ifc);
}
dev_info(&eeprom->dev, "%d interface(s) detected\n", count);
return count;
}
/* Public functions */
struct i2c_client *eeprom_load(struct i2c_adapter *i2c_adapter)
{
struct i2c_client *eeprom;
if (!i2c_adapter)
return NULL;
/* This automatically runs the setup() function */
eeprom = i2c_new_device(i2c_adapter, &at24_eeprom_info);
/* If no eeprom is detected, return NULL */
if (eeprom == NULL)
return NULL;
/* Validate EEPROM header */
if (!to_eeprom_buffer(eeprom)) {
dev_err(&eeprom->dev, "Header not found.\n");
i2c_unregister_device(eeprom);
return NULL;
}
return (eeprom_load_interfaces(eeprom) > 0 ? eeprom : NULL);
}
void eeprom_unload(struct i2c_client *eeprom)
{
uint8_t *buffer = to_eeprom_buffer(eeprom);
/* Free interface list and invalidate buffer */
eeprom_free_interfaces(eeprom);
memset(buffer, 0xff, IOT_EEPROM_SIZE);
i2c_unregister_device(eeprom);
}
int eeprom_num_slots(struct i2c_client *eeprom)
{
return 1; /* for now */
}
struct list_head *eeprom_if_list(struct i2c_client *eeprom)
{
return to_eeprom_if_list(eeprom);
}
enum EepromInterface eeprom_if_type(struct list_head *item)
{
enum EepromInterface result;
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0):
{
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
result = eif->type;
break;
}
default:
BUG();
result = EEPROM_IF_LAST;
break;
}
return result;
}
uint8_t eeprom_if_gpio_cfg(struct list_head *item, unsigned int pin)
{
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
BUG_ON(eif->type != EEPROM_IF_GPIO);
BUG_ON(pin >= ARRAY_SIZE(eif->ifc.gpio.cfg));
return eif->ifc.gpio.cfg[pin];
}
default:
BUG();
return 0xff;
}
}
char *eeprom_if_spi_modalias(struct list_head *item)
{
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
BUG_ON(eif->type != EEPROM_IF_SPI);
return (eif->ifc.spi.modalias);
}
default:
/* unsupported eeprom version */
BUG();
return NULL;
}
}
int eeprom_if_spi_irq_gpio(struct list_head *item)
{
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
BUG_ON(eif->type != EEPROM_IF_SPI);
return eif->ifc.spi.irq_gpio;
}
default:
/* unsupported eeprom version */
BUG();
return -1;
}
}
char *eeprom_if_i2c_modalias(struct list_head *item)
{
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
BUG_ON(eif->type != EEPROM_IF_I2C);
return (eif->ifc.i2c.modalias);
}
default:
/* unsupported eeprom version */
BUG();
return NULL;
}
}
int eeprom_if_i2c_irq_gpio(struct list_head *item)
{
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
BUG_ON(eif->type != EEPROM_IF_I2C);
return eif->ifc.i2c.irq_gpio;
}
default:
/* unsupported eeprom version */
BUG();
return -1;
}
}
uint8_t eeprom_if_i2c_address(struct list_head *item)
{
struct eeprom_if_map *m = to_eeprom_if_map(item);
switch (eeprom_version(m->eeprom)) {
case EEPROM_VERSION(1, 0): {
eeprom_if_1v0 *eif = (eeprom_if_1v0*)m->contents;
BUG_ON(eif->type != EEPROM_IF_I2C);
return (eif->ifc.i2c.address);
}
default:
/* unsupported eeprom version */
BUG();
return -1;
}
}
/* Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef IOT_SLOT_EEPROM_H
#define IOT_SLOT_EEPROM_H
#include <linux/i2c.h>
#define IRQ_GPIO_UNUSED (0xFF)
#define EEPROM_GPIO_CFG_INPUT_PULL_UP (0x1)
#define EEPROM_GPIO_CFG_INPUT_PULL_DOWN (0x2)
#define EEPROM_GPIO_CFG_INPUT_FLOATING (0x3)
#define EEPROM_GPIO_CFG_OUTPUT_LOW (0x4)
#define EEPROM_GPIO_CFG_OUTPUT_HIGH (0x5)
enum EepromInterface
{
EEPROM_IF_GPIO,
EEPROM_IF_I2C,
EEPROM_IF_SPI,
EEPROM_IF_USB,
EEPROM_IF_SDIO,
EEPROM_IF_ADC,
EEPROM_IF_PCM,
EEPROM_IF_CLK,
EEPROM_IF_UART,
EEPROM_IF_PLAT,
/* add more interface types here */
EEPROM_IF_LAST_SUPPORTED,
EEPROM_IF_LAST = 0xFF,
};
struct i2c_client *eeprom_load(struct i2c_adapter *i2c_adapter);
void eeprom_unload(struct i2c_client *eeprom);
struct list_head *eeprom_if_list(struct i2c_client *eeprom);
int eeprom_num_slots(struct i2c_client *eeprom);
enum EepromInterface eeprom_if_type(struct list_head *item);
uint8_t eeprom_if_gpio_cfg(struct list_head *item, unsigned int pin);
char *eeprom_if_spi_modalias(struct list_head *item);
int eeprom_if_spi_irq_gpio(struct list_head *item);
char *eeprom_if_i2c_modalias(struct list_head *item);
int eeprom_if_i2c_irq_gpio(struct list_head *item);
uint8_t eeprom_if_i2c_address(struct list_head *item);
#endif /* IOT_SLOT_EEPROM_H */
#ifndef IOT_SLOT_H
#define IOT_SLOT_H
struct spi_master;
struct i2c_adapter;
/*
* TODO:
* Need to decide whether an absent function pointer means that no action is
* needed to acquire the interface or that the interface is not supported on the
* IoT slot. I think not supported makes more sense unless we separate out
* muxing from getting. Eg. have a require_x, get_x, release_x functions.
*/
struct iot_slot_platform_data {
int gpio[4];
int reset_gpio;
int card_detect_gpio;
int (*request_i2c)(struct i2c_adapter **adapter);
int (*release_i2c)(struct i2c_adapter **adapter);
int (*request_spi)(struct spi_master **spi_master, int *cs);
int (*release_spi)(void);
/* TODO: what output param(s) for uart? */
int (*request_uart)(void);
int (*release_uart)(void);
/* TODO: how are adc's managed in the kernel? */
int (*request_adc)(void);
int (*release_adc)(void);
/* TODO: output params? */
int (*request_sdio)(void);
int (*release_sdio)(void);
/* TODO: output params? */
int (*request_pcm)(void);
int (*release_pcm)(void);
};
#endif /* IOT_SLOT_H */
sources:
{
ltc2941-battery-gauge.c
}
menuconfig POWER_SUPPLY
bool "Power supply class support"
help
Say Y here to enable power supply class support. This allows
power supply (batteries, AC, USB) monitoring by userspace
via sysfs and uevent (if available) and/or APM kernel interface
(if selected below).
if POWER_SUPPLY
config BATTERY_GAUGE_LTC2941
tristate "LTC2941/LTC2943 Battery Gauge Driver"
depends on I2C
help
Say Y here to include support for LTC2941 and LTC2943 Battery
Gauge IC. The driver reports the charge count continuously, and
measures the voltage and temperature every 10 seconds.
endif # POWER_SUPPLY
Driver files were copied from git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git in revision 17825ff6ec6db23892e61a1496abf7d737afe9e7
/*
* I2C client/driver for the Linear Technology LTC2941, LTC2942, LTC2943
* and LTC2944 Battery Gas Gauge IC
*
* Copyright (C) 2014 Topic Embedded Systems
*
* Author: Auryn Verwegen
* Author: Mike Looijmans
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/swab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include "ltc294x-platform-data.h"
#define I16_MSB(x) ((x >> 8) & 0xFF)
#define I16_LSB(x) (x & 0xFF)
#define LTC294X_WORK_DELAY 10 /* Update delay in seconds */
#define LTC294X_MAX_VALUE 0xFFFF
#define LTC294X_MID_SUPPLY 0x7FFF
#define LTC2941_MAX_PRESCALER_EXP 7
#define LTC2943_MAX_PRESCALER_EXP 6
enum ltc294x_reg {
LTC294X_REG_STATUS = 0x00,
LTC294X_REG_CONTROL = 0x01,
LTC294X_REG_ACC_CHARGE_MSB = 0x02,
LTC294X_REG_ACC_CHARGE_LSB = 0x03,
LTC294X_REG_VOLTAGE_MSB = 0x08,
LTC294X_REG_VOLTAGE_LSB = 0x09,
LTC2942_REG_TEMPERATURE_MSB = 0x0C,
LTC2942_REG_TEMPERATURE_LSB = 0x0D,
LTC2943_REG_CURRENT_MSB = 0x0E,
LTC2943_REG_CURRENT_LSB = 0x0F,
LTC2943_REG_TEMPERATURE_MSB = 0x14,
LTC2943_REG_TEMPERATURE_LSB = 0x15,
};
/*enum ltc294x_id {
LTC2941_ID,
LTC2942_ID,
LTC2943_ID,
LTC2944_ID,
};
*/
#define LTC2941_REG_STATUS_CHIP_ID BIT(7)
#define LTC2942_REG_CONTROL_MODE_SCAN (BIT(7) | BIT(6))
#define LTC2943_REG_CONTROL_MODE_SCAN BIT(7)
#define LTC294X_REG_CONTROL_PRESCALER_MASK (BIT(5) | BIT(4) | BIT(3))
#define LTC294X_REG_CONTROL_SHUTDOWN_MASK (BIT(0))
#define LTC294X_REG_CONTROL_PRESCALER_SET(x) \
((x << 3) & LTC294X_REG_CONTROL_PRESCALER_MASK)
#define LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED 0
struct ltc294x_info {
struct i2c_client *client; /* I2C Client pointer */
struct power_supply supply; /* Supply pointer */
struct delayed_work work; /* Work scheduler */
enum ltc294x_id id; /* Chip type */
int charge; /* Last charge register content */
int r_sense; /* mOhm */
int Qlsb; /* nAh */
};
static inline int convert_bin_to_uAh(
const struct ltc294x_info *info, int Q)
{
return ((Q * (info->Qlsb / 10))) / 100;
}
static inline int convert_uAh_to_bin(
const struct ltc294x_info *info, int uAh)
{
int Q;
Q = (uAh * 100) / (info->Qlsb/10);
return (Q < LTC294X_MAX_VALUE) ? Q : LTC294X_MAX_VALUE;
}
static int ltc294x_read_regs(struct i2c_client *client,
enum ltc294x_reg reg, u8 *buf, int num_regs)
{
int ret;
struct i2c_msg msgs[2] = { };
u8 reg_start = reg;
msgs[0].addr = client->addr;
msgs[0].len = 1;
msgs[0].buf = &reg_start;
msgs[1].addr = client->addr;
msgs[1].len = num_regs;
msgs[1].buf = buf;
msgs[1].flags = I2C_M_RD;
ret = i2c_transfer(client->adapter, &msgs[0], 2);
if (ret < 0) {
dev_err(&client->dev, "ltc2941 read_reg failed!\n");
return ret;
}
dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n",
__func__, reg, num_regs, *buf);
return 0;
}
static int ltc294x_write_regs(struct i2c_client *client,
enum ltc294x_reg reg, const u8 *buf, int num_regs)
{
int ret;
u8 reg_start = reg;
ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf);
if (ret < 0) {
dev_err(&client->dev, "ltc2941 write_reg failed!\n");
return ret;
}
dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n",
__func__, reg, num_regs, *buf);
return 0;
}
static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp)
{
int ret;
u8 value;
u8 control;
/* Read status and control registers */
ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1);
if (ret < 0) {
dev_err(&info->client->dev,
"Could not read registers from device\n");
goto error_exit;
}
control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) |
LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED;
/* Put device into "monitor" mode */
switch (info->id) {
case LTC2942_ID: /* 2942 measures every 2 sec */
control |= LTC2942_REG_CONTROL_MODE_SCAN;
break;
case LTC2943_ID:
case LTC2944_ID: /* 2943 and 2944 measure every 10 sec */
control |= LTC2943_REG_CONTROL_MODE_SCAN;
break;
default:
break;
}
if (value != control) {
ret = ltc294x_write_regs(info->client,
LTC294X_REG_CONTROL, &control, 1);
if (ret < 0) {
dev_err(&info->client->dev,
"Could not write register\n");
goto error_exit;
}
}
return 0;
error_exit:
return ret;
}
static int ltc294x_read_charge_register(const struct ltc294x_info *info)
{
int ret;
u8 datar[2];
ret = ltc294x_read_regs(info->client,
LTC294X_REG_ACC_CHARGE_MSB, &datar[0], 2);
if (ret < 0)
return ret;
return (datar[0] << 8) + datar[1];
}
static int ltc294x_get_charge_now(const struct ltc294x_info *info, int *val)
{
int value = ltc294x_read_charge_register(info);
if (value < 0)
return value;
/* When r_sense < 0, this counts up when the battery discharges */
if (info->Qlsb < 0)
value -= 0xFFFF;
*val = convert_bin_to_uAh(info, value);
return 0;
}
static int ltc294x_set_charge_now(const struct ltc294x_info *info, int val)
{
int ret;
u8 dataw[2];
u8 ctrl_reg;
s32 value;
value = convert_uAh_to_bin(info, val);
/* Direction depends on how sense+/- were connected */
if (info->Qlsb < 0)
value += 0xFFFF;
if ((value < 0) || (value > 0xFFFF)) /* input validation */
return -EINVAL;
/* Read control register */
ret = ltc294x_read_regs(info->client,
LTC294X_REG_CONTROL, &ctrl_reg, 1);
if (ret < 0)
return ret;
/* Disable analog section */
ctrl_reg |= LTC294X_REG_CONTROL_SHUTDOWN_MASK;
ret = ltc294x_write_regs(info->client,
LTC294X_REG_CONTROL, &ctrl_reg, 1);
if (ret < 0)
return ret;
/* Set new charge value */
dataw[0] = I16_MSB(value);
dataw[1] = I16_LSB(value);
ret = ltc294x_write_regs(info->client,
LTC294X_REG_ACC_CHARGE_MSB, &dataw[0], 2);
if (ret < 0)
goto error_exit;
/* Enable analog section */
error_exit:
ctrl_reg &= ~LTC294X_REG_CONTROL_SHUTDOWN_MASK;
ret = ltc294x_write_regs(info->client,
LTC294X_REG_CONTROL, &ctrl_reg, 1);
return ret < 0 ? ret : 0;
}
static int ltc294x_get_charge_counter(
const struct ltc294x_info *info, int *val)
{
int value = ltc294x_read_charge_register(info);
if (value < 0)
return value;
value -= LTC294X_MID_SUPPLY;
*val = convert_bin_to_uAh(info, value);
return 0;
}
static int ltc294x_get_voltage(const struct ltc294x_info *info, int *val)
{
int ret;
u8 datar[2];
u32 value;
ret = ltc294x_read_regs(info->client,
LTC294X_REG_VOLTAGE_MSB, &datar[0], 2);
value = (datar[0] << 8) | datar[1];
switch (info->id) {
case LTC2943_ID:
value *= 23600 * 2;
value /= 0xFFFF;
value *= 1000 / 2;
break;
case LTC2944_ID:
value *= 70800 / 5*4;
value /= 0xFFFF;
value *= 1000 * 5/4;
break;
default:
value *= 6000 * 10;
value /= 0xFFFF;
value *= 1000 / 10;
break;
}
*val = value;
return ret;
}
static int ltc294x_get_current(const struct ltc294x_info *info, int *val)
{
int ret;
u8 datar[2];
s32 value;
ret = ltc294x_read_regs(info->client,
LTC2943_REG_CURRENT_MSB, &datar[0], 2);
value = (datar[0] << 8) | datar[1];
value -= 0x7FFF;
if (info->id == LTC2944_ID)
value *= 64000;
else
value *= 60000;
/* Value is in range -32k..+32k, r_sense is usually 10..50 mOhm,
* the formula below keeps everything in s32 range while preserving
* enough digits */
*val = 1000 * (value / (info->r_sense * 0x7FFF)); /* in uA */
return ret;
}
static int ltc294x_get_temperature(const struct ltc294x_info *info, int *val)
{
enum ltc294x_reg reg;
int ret;
u8 datar[2];
u32 value;
if (info->id == LTC2942_ID) {
reg = LTC2942_REG_TEMPERATURE_MSB;
value = 60000; /* Full-scale is 600 Kelvin */
} else {
reg = LTC2943_REG_TEMPERATURE_MSB;
value = 51000; /* Full-scale is 510 Kelvin */
}
ret = ltc294x_read_regs(info->client, reg, &datar[0], 2);
value *= (datar[0] << 8) | datar[1];
/* Convert to centidegrees */
*val = value / 0xFFFF - 27215;
return ret;
}
static int ltc294x_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct ltc294x_info *info =
container_of(psy, struct ltc294x_info, supply);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGE_NOW:
return ltc294x_get_charge_now(info, &val->intval);
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
return ltc294x_get_charge_counter(info, &val->intval);
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
return ltc294x_get_voltage(info, &val->intval);
case POWER_SUPPLY_PROP_CURRENT_NOW:
return ltc294x_get_current(info, &val->intval);
case POWER_SUPPLY_PROP_TEMP:
return ltc294x_get_temperature(info, &val->intval);
default:
return -EINVAL;
}
}
static int ltc294x_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct ltc294x_info *info =
container_of(psy, struct ltc294x_info, supply);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_NOW:
return ltc294x_set_charge_now(info, val->intval);
default:
return -EPERM;
}
}
static int ltc294x_property_is_writeable(
struct power_supply *psy, enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_NOW:
return 1;
default:
return 0;
}
}
static void ltc294x_update(struct ltc294x_info *info)
{
int charge = ltc294x_read_charge_register(info);
if (charge != info->charge) {
info->charge = charge;
power_supply_changed(&info->supply);
}
}
static void ltc294x_work(struct work_struct *work)
{
struct ltc294x_info *info;
info = container_of(work, struct ltc294x_info, work.work);
ltc294x_update(info);
schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
}
static enum power_supply_property ltc294x_properties[] = {
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static int ltc294x_i2c_remove(struct i2c_client *client)
{
struct ltc294x_info *info = i2c_get_clientdata(client);
cancel_delayed_work(&info->work);
power_supply_unregister(&info->supply);
return 0;
}
static int ltc294x_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct ltc294x_platform_data *platform_data;
struct ltc294x_info *info;
int ret;
u32 prescaler_exp;
u8 status;
info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
if (info == NULL)
return -ENOMEM;
i2c_set_clientdata(client, info);
platform_data = client->dev.platform_data;
info->id = platform_data->chip_id;
info->supply.name = platform_data->name;
/* r_sense can be negative, when sense+ is connected to the battery
* instead of the sense-. This results in reversed measurements. */
info->r_sense = platform_data->r_sense;
prescaler_exp = platform_data->prescaler_exp;
if (info->id == LTC2943_ID) {
if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP)
prescaler_exp = LTC2943_MAX_PRESCALER_EXP;
info->Qlsb = ((340 * 50000) / info->r_sense) /
(4096 / (1 << (2*prescaler_exp)));
} else {
if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP)
prescaler_exp = LTC2941_MAX_PRESCALER_EXP;
info->Qlsb = ((85 * 50000) / info->r_sense) /
(128 / (1 << prescaler_exp));
}
/* Read status register to check for LTC2942 */
if (info->id == LTC2941_ID || info->id == LTC2942_ID) {
ret = ltc294x_read_regs(client, LTC294X_REG_STATUS, &status, 1);
if (ret < 0) {
dev_err(&client->dev,
"Could not read status register\n");
return ret;
}
if (status & LTC2941_REG_STATUS_CHIP_ID)
info->id = LTC2941_ID;
else
info->id = LTC2942_ID;
}
info->client = client;
info->supply.type = POWER_SUPPLY_TYPE_BATTERY;
info->supply.properties = ltc294x_properties;
switch (info->id) {
case LTC2944_ID:
case LTC2943_ID:
info->supply.num_properties =
ARRAY_SIZE(ltc294x_properties);
break;
case LTC2942_ID:
info->supply.num_properties =
ARRAY_SIZE(ltc294x_properties);
break;
case LTC2941_ID:
default:
info->supply.num_properties =
ARRAY_SIZE(ltc294x_properties) - 3;
break;
}
info->supply.get_property = ltc294x_get_property;
info->supply.set_property = ltc294x_set_property;
info->supply.property_is_writeable = ltc294x_property_is_writeable;
info->supply.external_power_changed = NULL;
INIT_DELAYED_WORK(&info->work, ltc294x_work);
ret = ltc294x_reset(info, prescaler_exp);
if (ret < 0) {
dev_err(&client->dev, "Communication with chip failed\n");
return ret;
}
ret = power_supply_register(&client->dev, &info->supply);
if (ret < 0) {
dev_err(&client->dev, "failed to register ltc2941\n");
return ret;
} else {
schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int ltc294x_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ltc294x_info *info = i2c_get_clientdata(client);
cancel_delayed_work(&info->work);
return 0;
}
static int ltc294x_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ltc294x_info *info = i2c_get_clientdata(client);
schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
return 0;
}
static SIMPLE_DEV_PM_OPS(ltc294x_pm_ops, ltc294x_suspend, ltc294x_resume);
#define LTC294X_PM_OPS (&ltc294x_pm_ops)
#else
#define LTC294X_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP */
static const struct i2c_device_id ltc294x_i2c_id[] = {
{ "ltc2941", LTC2941_ID, },
{ "ltc2942", LTC2942_ID, },
{ "ltc2943", LTC2943_ID, },
{ "ltc2944", LTC2944_ID, },
{ },
};
MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id);
static const struct of_device_id ltc294x_i2c_of_match[] = {
{
.compatible = "lltc,ltc2941",
.data = (void *)LTC2941_ID,
},
{
.compatible = "lltc,ltc2942",
.data = (void *)LTC2942_ID,
},
{
.compatible = "lltc,ltc2943",
.data = (void *)LTC2943_ID,
},
{
.compatible = "lltc,ltc2944",
.data = (void *)LTC2944_ID,
},
{ },
};
MODULE_DEVICE_TABLE(of, ltc294x_i2c_of_match);
static struct i2c_driver ltc294x_driver = {
.driver = {
.name = "LTC2941",
.of_match_table = ltc294x_i2c_of_match,
.pm = LTC294X_PM_OPS,
},
.probe = ltc294x_i2c_probe,
.remove = ltc294x_i2c_remove,
.id_table = ltc294x_i2c_id,
};
module_i2c_driver(ltc294x_driver);
MODULE_AUTHOR("Auryn Verwegen, Topic Embedded Systems");
MODULE_AUTHOR("Mike Looijmans, Topic Embedded Products");
MODULE_DESCRIPTION("LTC2941/LTC2942/LTC2943/LTC2944 Battery Gas Gauge IC driver");
MODULE_LICENSE("GPL");
#ifndef LTC294X_PLATFORM_DATA_H
#define LTC294X_PLATFORM_DATA_H
// TODO: duplicated between .c and .h - that's bad
enum ltc294x_id {
LTC2941_ID,
LTC2942_ID,
LTC2943_ID,
LTC2944_ID,
};
struct ltc294x_platform_data
{
enum ltc294x_id chip_id;
int r_sense;
u32 prescaler_exp;
const char *name;
};
#endif /* LTC294X_PLATFORM_DATA_H */
......@@ -2,6 +2,7 @@ cflags:
{
// Needed for lsm6ds3 platform data type definition
-I${MANGOH_ROOT}/linux_kernel_modules/lsm6ds3
-I${MANGOH_ROOT}/linux_kernel_modules/ltc294x
}
sources:
......
......@@ -2,11 +2,15 @@ cflags:
{
// Needed for lsm6ds3 platform data type definition
-I${MANGOH_ROOT}/linux_kernel_modules/lsm6ds3
-I${MANGOH_ROOT}/linux_kernel_modules/ltc294x
-I${MANGOH_ROOT}/linux_kernel_modules/bq24296
-I${MANGOH_ROOT}/linux_kernel_modules/iot_slot
}
sources:
{
mangoh_red.c
mangoh_red_mux.c
}
params:
......
......@@ -2,11 +2,15 @@ cflags:
{
// Needed for lsm6ds3 platform data type definition
-I${MANGOH_ROOT}/linux_kernel_modules/lsm6ds3
-I${MANGOH_ROOT}/linux_kernel_modules/ltc294x
-I${MANGOH_ROOT}/linux_kernel_modules/bq24296
-I${MANGOH_ROOT}/linux_kernel_modules/iot_slot
}
sources:
{
mangoh_red.c
mangoh_red_mux.c
}
params:
......
......@@ -2,14 +2,18 @@ cflags:
{
// Needed for lsm6ds3 platform data type definition
-I${MANGOH_ROOT}/linux_kernel_modules/lsm6ds3
-I${MANGOH_ROOT}/linux_kernel_modules/ltc294x
-I${MANGOH_ROOT}/linux_kernel_modules/bq24296
-I${MANGOH_ROOT}/linux_kernel_modules/iot_slot
}
sources:
{
mangoh_red.c
mangoh_red_mux.c
}
params:
{
revision = "dv4"
revision = "dv5"
}
......@@ -3,9 +3,16 @@
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/i2c/pca954x.h>
#include <linux/i2c/sx150x.h>
#include <linux/delay.h>
#include <linux/gpio/driver.h>
#include "lsm6ds3_platform_data.h"
#include "ltc294x-platform-data.h"
#include "bq24190-platform-data.h"
#include "mangoh_red_mux.h"
#include "iot-slot.h"
/*
*-----------------------------------------------------------------------------
......@@ -19,6 +26,14 @@
#define MANGOH_RED_I2C_SW_PORT_GPIO_EXPANDER (2)
#define MANGOH_RED_I2C_SW_PORT_EXP (3) /* expansion header */
/* TODO: There should be a better way to convert from WP GPIO numbers to real GPIO numbers */
#define WPX5_GPIO42 (80)
#define WPX5_GPIO13 (34)
#define WPX5_GPIO7 (79)
#define WPX5_GPIO8 (29)
#define WPX5_GPIO2 (59)
#define WPX5_GPIO33 (78)
/*
*-----------------------------------------------------------------------------
* Types
......@@ -27,7 +42,7 @@
enum mangoh_red_board_rev {
MANGOH_RED_BOARD_REV_DV2,
MANGOH_RED_BOARD_REV_DV3,
MANGOH_RED_BOARD_REV_DV4,
MANGOH_RED_BOARD_REV_DV5,
};
/*
......@@ -38,6 +53,13 @@ enum mangoh_red_board_rev {
static void mangoh_red_release(struct device* dev);
static int mangoh_red_probe(struct platform_device* pdev);
static int mangoh_red_remove(struct platform_device* pdev);
static void mangoh_red_iot_slot_release(struct device *dev);
static int mangoh_red_iot_slot_request_i2c(struct i2c_adapter **adapter);
static int mangoh_red_iot_slot_release_i2c(struct i2c_adapter **adapter);
static int mangoh_red_iot_slot_request_sdio(void);
static int mangoh_red_iot_slot_release_sdio(void);
static int mangoh_red_iot_slot_request_pcm(void);
static int mangoh_red_iot_slot_release_pcm(void);
/*
*-----------------------------------------------------------------------------
......@@ -47,9 +69,9 @@ static int mangoh_red_remove(struct platform_device* pdev);
static char *revision_dv2 = "dv2";
static char *revision_dv3 = "dv3";
static char *revision_dv4 = "dv4";
static char *revision_dv5 = "dv5";
static char *revision = "dv4";
static char *revision = "dv5";
module_param(revision, charp, S_IRUGO);
MODULE_PARM_DESC(revision, "mangOH Red board revision");
......@@ -71,7 +93,15 @@ static struct mangoh_red_driver_data {
struct i2c_client* i2c_switch;
struct i2c_client* accelerometer;
struct i2c_client* pressure;
} mangoh_red_driver_data;
struct i2c_client* battery_gauge;
struct i2c_client* battery_charger;
struct i2c_client* gpio_expander;
bool mux_initialized;
bool iot_slot_registered;
} mangoh_red_driver_data = {
.mux_initialized = false,
.iot_slot_registered = false,
};
static struct platform_device mangoh_red_device = {
.name = "mangoh red",
......@@ -97,6 +127,24 @@ static const struct i2c_board_info mangoh_red_pca954x_device_info = {
.platform_data = &mangoh_red_pca954x_pdata,
};
static struct sx150x_platform_data mangoh_red_gpio_expander_platform_data = {
.gpio_base = -1,
.oscio_is_gpo = false,
.io_pullup_ena = 0,
.io_pulldn_ena = 0,
.io_open_drain_ena = 0,
.io_polarity = 0,
.irq_summary = -1,
.irq_base = -1,
.reset_during_probe = true,
};
static const struct i2c_board_info mangoh_red_gpio_expander_devinfo = {
I2C_BOARD_INFO("sx1509q", 0x3e),
.platform_data = &mangoh_red_gpio_expander_platform_data,
.irq = 0,
};
static struct i2c_board_info mangoh_red_bmi160_devinfo = {
I2C_BOARD_INFO("bmi160", 0x68),
};
......@@ -113,6 +161,44 @@ static struct i2c_board_info mangoh_red_pressure_devinfo = {
I2C_BOARD_INFO("bmp280", 0x76),
};
static struct ltc294x_platform_data mangoh_red_battery_gauge_platform_data = {
.chip_id = LTC2942_ID,
.r_sense = 18,
.prescaler_exp = 32,
.name = "LTC2942",
};
static struct i2c_board_info mangoh_red_battery_gauge_devinfo = {
I2C_BOARD_INFO("ltc2942", 0x64),
.platform_data = &mangoh_red_battery_gauge_platform_data,
};
static struct bq24190_platform_data mangoh_red_battery_charger_platform_data = {
.gpio_int = 49,
};
static struct i2c_board_info mangoh_red_battery_charger_devinfo = {
I2C_BOARD_INFO("bq24190", 0x6B),
.platform_data = &mangoh_red_battery_charger_platform_data,
};
static struct iot_slot_platform_data mangoh_red_iot_slot_pdata = {
.gpio = {WPX5_GPIO42, WPX5_GPIO13, WPX5_GPIO7, WPX5_GPIO8},
.reset_gpio = WPX5_GPIO2,
.card_detect_gpio = WPX5_GPIO33,
.request_i2c = mangoh_red_iot_slot_request_i2c,
.release_i2c = mangoh_red_iot_slot_release_i2c,
.request_sdio = mangoh_red_iot_slot_request_sdio,
.release_sdio = mangoh_red_iot_slot_release_sdio,
.request_pcm = mangoh_red_iot_slot_request_pcm,
.release_pcm = mangoh_red_iot_slot_release_pcm,
};
static struct platform_device mangoh_red_iot_slot = {
.name = "iot-slot",
.id = 0, /* Means IoT slot 0 */
.dev = {
.platform_data = &mangoh_red_iot_slot_pdata,
.release = mangoh_red_iot_slot_release,
},
};
static void mangoh_red_release(struct device* dev)
......@@ -122,7 +208,18 @@ static void mangoh_red_release(struct device* dev)
static int mangoh_red_probe(struct platform_device* pdev)
{
dev_info(&pdev->dev, "In the probe\n");
int ret = 0;
int sdio_mux_gpio;
int pcm_mux_gpio;
struct gpio_chip *gpio_expander;
struct i2c_board_info *accelerometer_board_info;
struct i2c_adapter *other_adapter = NULL;
struct i2c_adapter *main_adapter = i2c_get_adapter(0);
if (!main_adapter) {
dev_err(&pdev->dev, "Failed to get I2C adapter 0.\n");
ret = -ENODEV;
goto done;
}
/*
* This is a workaround of questionable validity for USB issues first
......@@ -130,28 +227,64 @@ static int mangoh_red_probe(struct platform_device* pdev)
*/
msleep(5000);
/* TODO: seems pointless */
platform_set_drvdata(pdev, &mangoh_red_driver_data);
/* Get the main i2c adapter */
struct i2c_adapter* adapter = i2c_get_adapter(0);
if (!adapter) {
dev_err(&pdev->dev, "Failed to get I2C adapter 0.\n");
return -ENODEV;
}
/* Map the I2C switch */
dev_dbg(&pdev->dev, "mapping i2c switch\n");
mangoh_red_driver_data.i2c_switch =
i2c_new_device(adapter, &mangoh_red_pca954x_device_info);
i2c_new_device(main_adapter, &mangoh_red_pca954x_device_info);
if (!mangoh_red_driver_data.i2c_switch) {
dev_err(
&pdev->dev,
"Failed to register %s\n",
mangoh_red_pca954x_device_info.type);
return -ENODEV;
ret = -ENODEV;
goto cleanup;
}
/* Map the GPIO expander */
dev_dbg(&pdev->dev, "mapping gpio expander\n");
other_adapter = i2c_get_adapter(MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_GPIO_EXPANDER);
if (!other_adapter) {
dev_err(&pdev->dev, "No I2C bus %d.\n",
MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_GPIO_EXPANDER);
ret = -ENODEV;
goto cleanup;
}
mangoh_red_driver_data.gpio_expander =
i2c_new_device(other_adapter, &mangoh_red_gpio_expander_devinfo);
i2c_put_adapter(other_adapter);
if (!mangoh_red_driver_data.gpio_expander) {
dev_err(
&pdev->dev,
"Failed to register %s\n",
mangoh_red_gpio_expander_devinfo.type);
ret = -ENODEV;
goto cleanup;
}
gpio_expander = i2c_get_clientdata(
mangoh_red_driver_data.gpio_expander);
sdio_mux_gpio = gpio_expander->base + 9;
pcm_mux_gpio = gpio_expander->base + 13;
ret = mangoh_red_mux_init(pdev, sdio_mux_gpio, pcm_mux_gpio);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to initialize mangOH Red mux\n");
goto cleanup;
}
mangoh_red_driver_data.mux_initialized = true;
/* Map the IoT slot */
ret = platform_device_register(&mangoh_red_iot_slot);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register IoT slot device\n");
goto cleanup;
}
mangoh_red_driver_data.iot_slot_registered = true;
/* Map the accelerometer */
dev_dbg(&pdev->dev, "mapping accelerometer\n");
/*
......@@ -159,16 +292,11 @@ static int mangoh_red_probe(struct platform_device* pdev)
* and INT2 pins respectively. It does not appear that the bmi160 driver
* makes use of these interrupt pins.
*/
adapter = i2c_get_adapter(0);
if (!adapter) {
dev_err(&pdev->dev, "No I2C bus %d.\n", 0);
return -ENODEV;
}
struct i2c_board_info *accelerometer_board_info =
accelerometer_board_info =
mangoh_red_pdata.board_rev == MANGOH_RED_BOARD_REV_DV2 ?
&mangoh_red_lsm6ds3_devinfo : &mangoh_red_bmi160_devinfo;
mangoh_red_driver_data.accelerometer =
i2c_new_device(adapter, accelerometer_board_info);
i2c_new_device(main_adapter, accelerometer_board_info);
if (!mangoh_red_driver_data.accelerometer) {
dev_err(&pdev->dev, "Accelerometer is missing\n");
return -ENODEV;
......@@ -176,60 +304,143 @@ static int mangoh_red_probe(struct platform_device* pdev)
/* Map the I2C BMP280 pressure sensor */
dev_dbg(&pdev->dev, "mapping bmp280 pressure sensor\n");
adapter = i2c_get_adapter(0);
if (!adapter) {
dev_err(&pdev->dev, "No I2C bus %d.\n", 0);
return -ENODEV;
}
mangoh_red_driver_data.pressure =
i2c_new_device(adapter, &mangoh_red_pressure_devinfo);
i2c_new_device(main_adapter, &mangoh_red_pressure_devinfo);
if (!mangoh_red_driver_data.pressure) {
dev_err(&pdev->dev, "Pressure sensor is missing\n");
return -ENODEV;
}
/* Map the I2C BQ24296 driver: for now use the BQ24190 driver code */
dev_dbg(&pdev->dev, "mapping bq24296 driver\n");
other_adapter = i2c_get_adapter(MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_USB_HUB);
if(!other_adapter) {
dev_err(&pdev->dev, "No I2C bus %d.\n",
MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_USB_HUB);
ret = -ENODEV;
goto cleanup;
}
mangoh_red_driver_data.battery_charger = i2c_new_device(
other_adapter, &mangoh_red_battery_charger_devinfo);
i2c_put_adapter(other_adapter);
if (!mangoh_red_driver_data.battery_charger) {
dev_err(&pdev->dev, "battery charger is missing\n");
ret = -ENODEV;
goto cleanup;
}
if (mangoh_red_pdata.board_rev != MANGOH_RED_BOARD_REV_DV3) {
/*
* TODO:
* SX1509 GPIO expanders: 0x3E
* There is a driver in the WP85 kernel, but the gpiolib
* infrastructure of the WP85 kernel does not allow the
* expander GPIOs to be used in sysfs due to a hardcoded
* translation table.
* Battery Gauge: 0x64
* chip is LTC2942 which is made by linear technologies (now
* Analog Devices). There is a kernel driver in the
* linux-power-supply repository in the for-next branch.
*/
/* Map the I2C ltc2942 battery gauge */
dev_dbg(&pdev->dev, "mapping ltc2942 battery gauge\n");
other_adapter = i2c_get_adapter(
MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_USB_HUB);
if (!other_adapter) {
dev_err(&pdev->dev, "No I2C bus %d.\n",
MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_USB_HUB);
ret = -ENODEV;
goto cleanup;
}
mangoh_red_driver_data.battery_gauge = i2c_new_device(
other_adapter, &mangoh_red_battery_gauge_devinfo);
i2c_put_adapter(other_adapter);
if (!mangoh_red_driver_data.battery_gauge) {
dev_err(&pdev->dev, "battery gauge is missing\n");
ret = -ENODEV;
goto cleanup;
}
}
/*
* TODO:
* 3503 USB Hub: 0x08
* Looks like there is a driver in the wp85 kernel source at drivers/usb/misc/usb3503.c
* I'm not really sure what benefit is achieved through using this driver.
* Buck & Battery Charger: 0x6B
* chip is BQ24296RGER
* Looks like there is a driver in the wp85 kernel source at
* drivers/usb/misc/usb3503.c. I'm not really sure what benefit is
* achieved through using this driver.
*/
return 0;
cleanup:
i2c_put_adapter(main_adapter);
if (ret != 0)
mangoh_red_remove(pdev);
done:
return ret;
}
static void try_unregister_i2c_device(struct i2c_client *client)
{
if (client != NULL) {
i2c_unregister_device(client);
i2c_put_adapter(client->adapter);
}
}
static int mangoh_red_remove(struct platform_device* pdev)
{
dev_info(&pdev->dev, "In the remove\n");
struct mangoh_red_driver_data *dd = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "Removing mangoh red platform device\n");
if (mangoh_red_pdata.board_rev != MANGOH_RED_BOARD_REV_DV3)
try_unregister_i2c_device(dd->battery_gauge);
try_unregister_i2c_device(dd->battery_charger);
try_unregister_i2c_device(dd->pressure);
try_unregister_i2c_device(dd->accelerometer);
if (dd->iot_slot_registered)
platform_device_unregister(&mangoh_red_iot_slot);
if (dd->mux_initialized)
mangoh_red_mux_deinit();
try_unregister_i2c_device(dd->gpio_expander);
try_unregister_i2c_device(dd->i2c_switch);
i2c_unregister_device(mangoh_red_driver_data.pressure);
i2c_put_adapter(mangoh_red_driver_data.pressure->adapter);
return 0;
}
/* Release function is needed to avoid warning when device is deleted */
static void mangoh_red_iot_slot_release(struct device *dev) { /* do nothing */ }
i2c_unregister_device(mangoh_red_driver_data.accelerometer);
i2c_put_adapter(mangoh_red_driver_data.accelerometer->adapter);
static int mangoh_red_iot_slot_request_i2c(struct i2c_adapter **adapter)
{
*adapter = i2c_get_adapter(MANGOH_RED_I2C_SW_BASE_ADAPTER_ID +
MANGOH_RED_I2C_SW_PORT_IOT0);
return *adapter != NULL ? 0 : -EINVAL;
}
i2c_unregister_device(mangoh_red_driver_data.i2c_switch);
i2c_put_adapter(mangoh_red_driver_data.i2c_switch->adapter);
static int mangoh_red_iot_slot_release_i2c(struct i2c_adapter **adapter)
{
i2c_put_adapter(*adapter);
*adapter = NULL;
return 0;
}
static int mangoh_red_iot_slot_request_sdio(void)
{
return mangoh_red_mux_sdio_select(SDIO_SELECTION_IOT_SLOT);
}
static int mangoh_red_iot_slot_release_sdio(void)
{
return mangoh_red_mux_sdio_release(SDIO_SELECTION_IOT_SLOT);
}
static int mangoh_red_iot_slot_request_pcm(void)
{
return mangoh_red_mux_pcm_select(PCM_SELECTION_IOT_SLOT);
}
static int mangoh_red_iot_slot_release_pcm(void)
{
return mangoh_red_mux_pcm_release(PCM_SELECTION_IOT_SLOT);
}
static int __init mangoh_red_init(void)
{
platform_driver_register(&mangoh_red_driver);
......@@ -239,8 +450,8 @@ static int __init mangoh_red_init(void)
mangoh_red_pdata.board_rev = MANGOH_RED_BOARD_REV_DV2;
} else if (strcmp(revision, revision_dv3) == 0) {
mangoh_red_pdata.board_rev = MANGOH_RED_BOARD_REV_DV3;
} else if (strcmp(revision, revision_dv4) == 0) {
mangoh_red_pdata.board_rev = MANGOH_RED_BOARD_REV_DV4;
} else if (strcmp(revision, revision_dv5) == 0) {
mangoh_red_pdata.board_rev = MANGOH_RED_BOARD_REV_DV5;
} else {
pr_err(
"%s: Unsupported mangOH Red board revision (%s)\n",
......
#include <linux/kernel.h>
#include <linux/gpio.h>
#include "mangoh_red_mux.h"
static struct platform_device *board_device;
static int sdio_mux_gpio;
static struct mutex sdio_mutex;
static enum sdio_selection sdio_current_selection;
static unsigned int sdio_num_requested;
static int pcm_mux_gpio;
static struct mutex pcm_mutex;
static enum pcm_selection pcm_current_selection;
static unsigned int pcm_num_requested;
int mangoh_red_mux_init(struct platform_device *pdev, int sdio_gpio,
int pcm_gpio)
{
int ret;
struct device *device = &pdev->dev;
board_device = pdev;
sdio_mux_gpio = sdio_gpio;
pcm_mux_gpio = pcm_gpio;
dev_info(device,
"Initializing mangOH mux with sdio_gpio=%d, pcm_gpio=%d\n",
sdio_mux_gpio, pcm_mux_gpio);
ret = devm_gpio_request_one(device, sdio_mux_gpio,
GPIOF_OUT_INIT_HIGH,
"sdio mux");
if (ret != 0) {
dev_err(device, "Couldn't acquire GPIO for SDIO mux\n");
return ret;
}
ret = devm_gpio_request_one(device, pcm_mux_gpio,
GPIOF_OUT_INIT_HIGH,
"pcm mux");
if (ret != 0) {
dev_err(device, "Couldn't acquire GPIO for PCM mux\n");
/* Release the SDIO mux gpio that was acquired previously */
devm_gpio_free(device, sdio_mux_gpio);
return ret;
}
mutex_init(&sdio_mutex);
mutex_init(&pcm_mutex);
/* Initialize based on hardware defaults */
sdio_current_selection = SDIO_SELECTION_SD_CARD_SLOT;
pcm_current_selection = PCM_SELECTION_ONBOARD;
sdio_num_requested = 0;
pcm_num_requested = 0;
return 0;
}
void mangoh_red_mux_deinit(void)
{
struct device *device = &board_device->dev;
devm_gpio_free(device, pcm_mux_gpio);
devm_gpio_free(device, sdio_mux_gpio);
}
int mangoh_red_mux_sdio_select(enum sdio_selection selection)
{
int ret = 0;
dev_info(&board_device->dev, "SDIO mux: selecting %s\n",
(selection == SDIO_SELECTION_SD_CARD_SLOT ?
"SD card slot" : "IoT slot"));
mutex_lock(&sdio_mutex);
if (selection == sdio_current_selection)
sdio_num_requested++;
else {
if (sdio_num_requested == 0) {
ret = gpio_direction_output(
sdio_mux_gpio,
selection == SDIO_SELECTION_SD_CARD_SLOT ?
1 : 0);
if (ret != 0) {
dev_err(&board_device->dev,
"Couldn't set sdio mux\n");
goto unlock;
}
sdio_current_selection = selection;
sdio_num_requested++;
} else {
ret = -EBUSY;
}
}
unlock:
mutex_unlock(&sdio_mutex);
return ret;
}
int mangoh_red_mux_sdio_release(enum sdio_selection selection)
{
int ret = 0;
mutex_lock(&sdio_mutex);
if (selection != sdio_current_selection) {
dev_err(&board_device->dev,
"Trying to release SDIO mux, but the current selection differs from what the client specified\n");
ret = -EACCES;
goto unlock;
}
if (sdio_num_requested != 0) {
sdio_num_requested--;
} else {
dev_err(&board_device->dev,
"Couldn't release SDIO since it wasn't requested\n");
ret = -ENOLCK;
}
unlock:
mutex_unlock(&sdio_mutex);
return ret;
}
int mangoh_red_mux_pcm_select(enum pcm_selection selection)
{
int ret = 0;
dev_info(&board_device->dev, "PCM mux: selecting %s\n",
(selection == PCM_SELECTION_ONBOARD ? "onboard" : "IoT slot"));
mutex_lock(&pcm_mutex);
if (selection == pcm_current_selection)
pcm_num_requested++;
else {
if (pcm_num_requested == 0) {
ret = gpio_direction_output(
pcm_mux_gpio,
selection == PCM_SELECTION_ONBOARD ?
1 : 0);
if (ret != 0) {
dev_err(&board_device->dev,
"Couldn't set pcm mux\n");
goto unlock;
}
pcm_current_selection = selection;
pcm_num_requested++;
} else {
ret = -EBUSY;
}
}
unlock:
mutex_unlock(&pcm_mutex);
return ret;
}
int mangoh_red_mux_pcm_release(enum pcm_selection selection)
{
int ret = 0;
mutex_lock(&pcm_mutex);
if (selection != pcm_current_selection) {
dev_err(
&board_device->dev,
"Trying to release PCM mux, but the current selection differs from what the client specified\n");
ret = -EACCES;
goto unlock;
}
if (pcm_num_requested != 0) {
pcm_num_requested--;
} else {
dev_err(&board_device->dev,
"Couldn't release PCM since it wasn't requested\n");
ret = -ENOLCK;
}
unlock:
mutex_unlock(&pcm_mutex);
return ret;
}
#ifndef MANGOH_RED_MUX_H
#define MANGOH_RED_MUX_H
#include <linux/platform_device.h>
enum sdio_selection {
SDIO_SELECTION_SD_CARD_SLOT,
SDIO_SELECTION_IOT_SLOT,
};
enum pcm_selection {
PCM_SELECTION_IOT_SLOT,
PCM_SELECTION_ONBOARD,
};
int mangoh_red_mux_init(struct platform_device *pdev, int sdio_gpio, int pcm_gpio);
void mangoh_red_mux_deinit(void);
int mangoh_red_mux_sdio_select(enum sdio_selection selection);
int mangoh_red_mux_sdio_release(enum sdio_selection selection);
int mangoh_red_mux_pcm_select(enum pcm_selection selection);
int mangoh_red_mux_pcm_release(enum pcm_selection selection);
#endif /* MANGOH_RED_MUX_H */
......@@ -14,9 +14,11 @@ apps:
$MANGOH_ROOT/apps/DataRouter/dataRouter
$MANGOH_ROOT/apps/DataRouter/drTool/drTool
$MANGOH_ROOT/apps/SocialService/socialService
$MANGOH_ROOT/apps/RedSensorToCloud/redSensorToCloud
// Disabled until Release 15 is released due to bug in power supply kernel support
// $MANGOH_ROOT/apps/BatteryService/batteryService
// The heartbeat app is disabled on mangOH Red because the logging messages
// from the low power microcontroller make it very difficult to use the
// console port.
......@@ -37,13 +39,14 @@ interfaceSearch:
$MANGOH_ROOT/apps/DataRouter
$MANGOH_ROOT/apps/MuxControl
$MANGOH_ROOT/apps/SocialService/interfaces
$MANGOH_ROOT/apps/BatteryService
}
kernelModules:
{
// $MANGOH_ROOT/linux_kernel_modules/mangoh/9-mangoh_red_dv2
// $MANGOH_ROOT/linux_kernel_modules/mangoh/9-mangoh_red_dv3
$MANGOH_ROOT/linux_kernel_modules/mangoh/9-mangoh_red_dv4
$MANGOH_ROOT/linux_kernel_modules/mangoh/9-mangoh_red_dv3
// $MANGOH_ROOT/linux_kernel_modules/mangoh/9-mangoh_red_dv5
// cp2130 required for MT7697 WiFi/BT
$MANGOH_ROOT/linux_kernel_modules/cp2130/0-cp2130
......@@ -65,6 +68,9 @@ kernelModules:
$MANGOH_ROOT/linux_kernel_modules/bmi160/3-bmi160
$MANGOH_ROOT/linux_kernel_modules/bmi160/4-bmi160-i2c
// Don't enable IoT slot driver until failure case testing is complete
// $MANGOH_ROOT/linux_kernel_modules/iot_slot/0-iot_slot
$MANGOH_ROOT/linux_kernel_modules/mt7697q/1-mt7697q
$MANGOH_ROOT/linux_kernel_modules/mt7697serial/1-mt7697serial
$MANGOH_ROOT/linux_kernel_modules/mt7697wifi/2-mt7697wifi_core
......@@ -72,4 +78,10 @@ kernelModules:
// spisvc creates a spidev device which will appear as /dev/spidev0.0 once the spidev module is
// loaded.
$LEGATO_ROOT/drivers/spisvc/spisvc
// Only on mangOH Red DV4
$MANGOH_ROOT/linux_kernel_modules/ltc294x/0-ltc294x.mdef
// Required for BQ24296
$MANGOH_ROOT/linux_kernel_modules/bq24296/0-bq24296.mdef
}
Subproject commit 9fa6a660e8bce919e336345069102739674f21c4
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment