power supply and reset changes for the v4.3 series
* new reset driver for ZTE SoCs * add support for sama5d3 reset handling * overhaul of twl4030 charger driver * misc. fixes and cleanups -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCgAGBQJV2xolAAoJENju1/PIO/qaoWwP/1vQ5xJxqyV0zyv2GIMRPbDi 0hPrr8gC6K+++50weRFKVeFU/UGzpSB/QMryrUWmXP9INa9XnzRSh+qQxZao5dLi xm4DJ08bsszjsL94e8JwQuumreEerQnjEPB2FAcYr+Ep1pG40RlQ7H5yLuwN217i ufi2L7szaNBlmXPQ3lOfsVHGX3SLm2T3wIXpBKzG6hMr+GWbJP4sexs6W+iFU2cm 802vWAkhyTHydlTFpjmP8JHtqq5CfgLA9wmPjlXCtEHNxOe/auSKN3dBXJhfUiFi ftiwXlShRb6NtBn+tQnOpG4ITaBiH5WKKfAZz3b9+4sxYbFwF/aptGumxOdu4Uvp B18M9Uw66DDVcDioi/I0L91D4OYJafTTzDxpBImHRlqzdKzWiUZ2V/+LVUOizGLj VH9EyUaA3u/EJ6pmWK8IEY/6OtgyjND1ZskRpWlI5u1CL16HiHpgmhASy0xajAjo fIESYDXdzEEatQbM6S5xbmOIm1syS7nSSSfSdmFQj6A6zQAHDnPBwZ7hiwUDjsf0 4Y60bB3WxJGjne3kCpgWJ5xTdHYV+4bmVU5f82UYiWXLSawMmULtAii09KSd4Hie cYCztBQWvpLRM6FpjfrblsY+d8Wme8148/Y1X4IUmneNxoKpr5sMGBIMEl8ox8SP ZfJQvxNBOMmk5aSy3N6t =ENTy -----END PGP SIGNATURE----- Merge tag 'for-v4.3' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply Pull power supply and reset changes from Sebastian Reichel: - new reset driver for ZTE SoCs - add support for sama5d3 reset handling - overhaul of twl4030 charger driver - misc fixes and cleanups * tag 'for-v4.3' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (35 commits) bq2415x_charger: Allow to load and use driver even if notify device is not registered yet twl4030_charger: fix compile error when TWL4030_MADC not available. power: bq24190_charger: Fix charge type sysfs property power: Allow compile test of GPIO consumers if !GPIOLIB power: Export I2C module alias information in missing drivers twl4030_charger: Increase current carefully while watching voltage. twl4030_charger: add ac/mode to match usb/mode twl4030_charger: add software controlled linear charging mode. twl4030_charger: enable manual enable/disable of usb charging. twl4030_charger: allow max_current to be managed via sysfs. twl4030_charger: distinguish between USB current and 'AC' current twl4030_charger: allow fine control of charger current. twl4030_charger: split uA calculation into a function. twl4030_charger: trust phy to determine when USB power is available. twl4030_charger: correctly handle -EPROBE_DEFER from devm_usb_get_phy_by_node twl4030_charger: convert to module_platform_driver instead of ..._probe. twl4030_charger: use runtime_pm to keep usb phy active while charging. rx51-battery: Set name to rx51-battery MAINTAINERS: AVS is not maintained via power supply tree power: olpc_battery: clean up eeprom read function ...
This commit is contained in:
commit
c8192ba416
22 changed files with 863 additions and 357 deletions
45
Documentation/ABI/testing/sysfs-class-power-twl4030
Normal file
45
Documentation/ABI/testing/sysfs-class-power-twl4030
Normal file
|
@ -0,0 +1,45 @@
|
|||
What: /sys/class/power_supply/twl4030_ac/max_current
|
||||
/sys/class/power_supply/twl4030_usb/max_current
|
||||
Description:
|
||||
Read/Write limit on current which may
|
||||
be drawn from the ac (Accessory Charger) or
|
||||
USB port.
|
||||
|
||||
Value is in micro-Amps.
|
||||
|
||||
Value is set automatically to an appropriate
|
||||
value when a cable is plugged or unplugged.
|
||||
|
||||
Value can the set by writing to the attribute.
|
||||
The change will only persist until the next
|
||||
plug event. These event are reported via udev.
|
||||
|
||||
|
||||
What: /sys/class/power_supply/twl4030_usb/mode
|
||||
Description:
|
||||
Changing mode for USB port.
|
||||
Writing to this can disable charging.
|
||||
|
||||
Possible values are:
|
||||
"auto" - draw power as appropriate for detected
|
||||
power source and battery status.
|
||||
"off" - do not draw any power.
|
||||
"continuous"
|
||||
- activate mode described as "linear" in
|
||||
TWL data sheets. This uses whatever
|
||||
current is available and doesn't switch off
|
||||
when voltage drops.
|
||||
|
||||
This is useful for unstable power sources
|
||||
such as bicycle dynamo, but care should
|
||||
be taken that battery is not over-charged.
|
||||
|
||||
What: /sys/class/power_supply/twl4030_ac/mode
|
||||
Description:
|
||||
Changing mode for 'ac' port.
|
||||
Writing to this can disable charging.
|
||||
|
||||
Possible values are:
|
||||
"auto" - draw power as appropriate for detected
|
||||
power source and battery status.
|
||||
"off" - do not draw any power.
|
|
@ -87,7 +87,7 @@ One interrupt per TC channel in a TC block:
|
|||
|
||||
RSTC Reset Controller required properties:
|
||||
- compatible: Should be "atmel,<chip>-rstc".
|
||||
<chip> can be "at91sam9260" or "at91sam9g45"
|
||||
<chip> can be "at91sam9260" or "at91sam9g45" or "sama5d3"
|
||||
- reg: Should contain registers location and length
|
||||
|
||||
Example:
|
||||
|
|
|
@ -8093,6 +8093,7 @@ T: git git://git.infradead.org/battery-2.6.git
|
|||
S: Maintained
|
||||
F: include/linux/power_supply.h
|
||||
F: drivers/power/
|
||||
X: drivers/power/avs/
|
||||
|
||||
PNP SUPPORT
|
||||
M: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
|
||||
|
|
|
@ -788,9 +788,8 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
|
|||
static struct regulator_consumer_supply usb1v8 = {
|
||||
.supply = "usb1v8",
|
||||
};
|
||||
static struct regulator_consumer_supply usb3v1[] = {
|
||||
{ .supply = "usb3v1" },
|
||||
{ .supply = "bci3v1" },
|
||||
static struct regulator_consumer_supply usb3v1 = {
|
||||
.supply = "usb3v1",
|
||||
};
|
||||
|
||||
/* First add the regulators so that they can be used by transceiver */
|
||||
|
@ -818,7 +817,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
|
|||
return PTR_ERR(child);
|
||||
|
||||
child = add_regulator_linked(TWL4030_REG_VUSB3V1,
|
||||
&usb_fixed, usb3v1, 2,
|
||||
&usb_fixed, &usb3v1, 1,
|
||||
features);
|
||||
if (IS_ERR(child))
|
||||
return PTR_ERR(child);
|
||||
|
@ -838,7 +837,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
|
|||
if (IS_ENABLED(CONFIG_REGULATOR_TWL4030) && child) {
|
||||
usb1v5.dev_name = dev_name(child);
|
||||
usb1v8.dev_name = dev_name(child);
|
||||
usb3v1[0].dev_name = dev_name(child);
|
||||
usb3v1.dev_name = dev_name(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ config CHARGER_LP8788
|
|||
|
||||
config CHARGER_GPIO
|
||||
tristate "GPIO charger"
|
||||
depends on GPIOLIB
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
help
|
||||
Say Y to include support for chargers which report their online status
|
||||
through a GPIO pin.
|
||||
|
@ -391,26 +391,30 @@ config CHARGER_BQ2415X
|
|||
|
||||
config CHARGER_BQ24190
|
||||
tristate "TI BQ24190 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
depends on I2C
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
help
|
||||
Say Y to enable support for the TI BQ24190 battery charger.
|
||||
|
||||
config CHARGER_BQ24257
|
||||
tristate "TI BQ24257 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
depends on I2C
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
depends on REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for the TI BQ24257 battery charger.
|
||||
|
||||
config CHARGER_BQ24735
|
||||
tristate "TI BQ24735 battery charger support"
|
||||
depends on I2C && GPIOLIB
|
||||
depends on I2C
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
help
|
||||
Say Y to enable support for the TI BQ24735 battery charger.
|
||||
|
||||
config CHARGER_BQ25890
|
||||
tristate "TI BQ25890 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
depends on I2C
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for the TI BQ25890 battery charger.
|
||||
|
@ -462,7 +466,8 @@ config BATTERY_RT5033
|
|||
|
||||
config CHARGER_RT9455
|
||||
tristate "Richtek RT9455 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
depends on I2C
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for Richtek RT9455 battery charger.
|
||||
|
|
|
@ -170,7 +170,7 @@ struct bq2415x_device {
|
|||
struct power_supply *charger;
|
||||
struct power_supply_desc charger_desc;
|
||||
struct delayed_work work;
|
||||
struct power_supply *notify_psy;
|
||||
struct device_node *notify_node;
|
||||
struct notifier_block nb;
|
||||
enum bq2415x_mode reported_mode;/* mode reported by hook function */
|
||||
enum bq2415x_mode mode; /* currently configured mode */
|
||||
|
@ -792,31 +792,9 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
|
|||
|
||||
}
|
||||
|
||||
static int bq2415x_notifier_call(struct notifier_block *nb,
|
||||
unsigned long val, void *v)
|
||||
static bool bq2415x_update_reported_mode(struct bq2415x_device *bq, int mA)
|
||||
{
|
||||
struct bq2415x_device *bq =
|
||||
container_of(nb, struct bq2415x_device, nb);
|
||||
struct power_supply *psy = v;
|
||||
enum bq2415x_mode mode;
|
||||
union power_supply_propval prop;
|
||||
int ret;
|
||||
int mA;
|
||||
|
||||
if (val != PSY_EVENT_PROP_CHANGED)
|
||||
return NOTIFY_OK;
|
||||
|
||||
if (psy != bq->notify_psy)
|
||||
return NOTIFY_OK;
|
||||
|
||||
dev_dbg(bq->dev, "notifier call was called\n");
|
||||
|
||||
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
&prop);
|
||||
if (ret != 0)
|
||||
return NOTIFY_OK;
|
||||
|
||||
mA = prop.intval;
|
||||
|
||||
if (mA == 0)
|
||||
mode = BQ2415X_MODE_OFF;
|
||||
|
@ -828,9 +806,43 @@ static int bq2415x_notifier_call(struct notifier_block *nb,
|
|||
mode = BQ2415X_MODE_DEDICATED_CHARGER;
|
||||
|
||||
if (bq->reported_mode == mode)
|
||||
return NOTIFY_OK;
|
||||
return false;
|
||||
|
||||
bq->reported_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int bq2415x_notifier_call(struct notifier_block *nb,
|
||||
unsigned long val, void *v)
|
||||
{
|
||||
struct bq2415x_device *bq =
|
||||
container_of(nb, struct bq2415x_device, nb);
|
||||
struct power_supply *psy = v;
|
||||
union power_supply_propval prop;
|
||||
int ret;
|
||||
|
||||
if (val != PSY_EVENT_PROP_CHANGED)
|
||||
return NOTIFY_OK;
|
||||
|
||||
/* Ignore event if it was not send by notify_node/notify_device */
|
||||
if (bq->notify_node) {
|
||||
if (!psy->dev.parent ||
|
||||
psy->dev.parent->of_node != bq->notify_node)
|
||||
return NOTIFY_OK;
|
||||
} else if (bq->init_data.notify_device) {
|
||||
if (strcmp(psy->desc->name, bq->init_data.notify_device) != 0)
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
dev_dbg(bq->dev, "notifier call was called\n");
|
||||
|
||||
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
&prop);
|
||||
if (ret != 0)
|
||||
return NOTIFY_OK;
|
||||
|
||||
if (!bq2415x_update_reported_mode(bq, prop.intval))
|
||||
return NOTIFY_OK;
|
||||
|
||||
/* if automode is not enabled do not tell about reported_mode */
|
||||
if (bq->automode < 1)
|
||||
|
@ -1536,6 +1548,8 @@ static int bq2415x_probe(struct i2c_client *client,
|
|||
struct device_node *np = client->dev.of_node;
|
||||
struct bq2415x_platform_data *pdata = client->dev.platform_data;
|
||||
const struct acpi_device_id *acpi_id = NULL;
|
||||
struct power_supply *notify_psy = NULL;
|
||||
union power_supply_propval prop;
|
||||
|
||||
if (!np && !pdata && !ACPI_HANDLE(&client->dev)) {
|
||||
dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n");
|
||||
|
@ -1569,25 +1583,6 @@ static int bq2415x_probe(struct i2c_client *client,
|
|||
goto error_2;
|
||||
}
|
||||
|
||||
if (np) {
|
||||
bq->notify_psy = power_supply_get_by_phandle(np,
|
||||
"ti,usb-charger-detection");
|
||||
|
||||
if (IS_ERR(bq->notify_psy)) {
|
||||
dev_info(&client->dev,
|
||||
"no 'ti,usb-charger-detection' property (err=%ld)\n",
|
||||
PTR_ERR(bq->notify_psy));
|
||||
bq->notify_psy = NULL;
|
||||
} else if (!bq->notify_psy) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto error_2;
|
||||
}
|
||||
} else if (pdata && pdata->notify_device) {
|
||||
bq->notify_psy = power_supply_get_by_name(pdata->notify_device);
|
||||
} else {
|
||||
bq->notify_psy = NULL;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, bq);
|
||||
|
||||
bq->id = num;
|
||||
|
@ -1607,32 +1602,35 @@ static int bq2415x_probe(struct i2c_client *client,
|
|||
"ti,current-limit",
|
||||
&bq->init_data.current_limit);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,weak-battery-voltage",
|
||||
&bq->init_data.weak_battery_voltage);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,battery-regulation-voltage",
|
||||
&bq->init_data.battery_regulation_voltage);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,charge-current",
|
||||
&bq->init_data.charge_current);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,termination-current",
|
||||
&bq->init_data.termination_current);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,resistor-sense",
|
||||
&bq->init_data.resistor_sense);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
if (np)
|
||||
bq->notify_node = of_parse_phandle(np,
|
||||
"ti,usb-charger-detection", 0);
|
||||
} else {
|
||||
memcpy(&bq->init_data, pdata, sizeof(bq->init_data));
|
||||
}
|
||||
|
@ -1642,56 +1640,72 @@ static int bq2415x_probe(struct i2c_client *client,
|
|||
ret = bq2415x_power_supply_init(bq);
|
||||
if (ret) {
|
||||
dev_err(bq->dev, "failed to register power supply: %d\n", ret);
|
||||
goto error_3;
|
||||
goto error_2;
|
||||
}
|
||||
|
||||
ret = bq2415x_sysfs_init(bq);
|
||||
if (ret) {
|
||||
dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
|
||||
goto error_4;
|
||||
goto error_3;
|
||||
}
|
||||
|
||||
ret = bq2415x_set_defaults(bq);
|
||||
if (ret) {
|
||||
dev_err(bq->dev, "failed to set default values: %d\n", ret);
|
||||
goto error_5;
|
||||
goto error_4;
|
||||
}
|
||||
|
||||
if (bq->notify_psy) {
|
||||
if (bq->notify_node || bq->init_data.notify_device) {
|
||||
bq->nb.notifier_call = bq2415x_notifier_call;
|
||||
ret = power_supply_reg_notifier(&bq->nb);
|
||||
if (ret) {
|
||||
dev_err(bq->dev, "failed to reg notifier: %d\n", ret);
|
||||
goto error_6;
|
||||
goto error_4;
|
||||
}
|
||||
|
||||
/* Query for initial reported_mode and set it */
|
||||
bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED,
|
||||
bq->notify_psy);
|
||||
bq2415x_set_mode(bq, bq->reported_mode);
|
||||
|
||||
bq->automode = 1;
|
||||
dev_info(bq->dev, "automode enabled\n");
|
||||
dev_info(bq->dev, "automode supported, waiting for events\n");
|
||||
} else {
|
||||
bq->automode = -1;
|
||||
dev_info(bq->dev, "automode not supported\n");
|
||||
}
|
||||
|
||||
/* Query for initial reported_mode and set it */
|
||||
if (bq->nb.notifier_call) {
|
||||
if (np) {
|
||||
notify_psy = power_supply_get_by_phandle(np,
|
||||
"ti,usb-charger-detection");
|
||||
if (IS_ERR(notify_psy))
|
||||
notify_psy = NULL;
|
||||
} else if (bq->init_data.notify_device) {
|
||||
notify_psy = power_supply_get_by_name(
|
||||
bq->init_data.notify_device);
|
||||
}
|
||||
}
|
||||
if (notify_psy) {
|
||||
ret = power_supply_get_property(notify_psy,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
|
||||
power_supply_put(notify_psy);
|
||||
|
||||
if (ret == 0) {
|
||||
bq2415x_update_reported_mode(bq, prop.intval);
|
||||
bq2415x_set_mode(bq, bq->reported_mode);
|
||||
}
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work);
|
||||
bq2415x_set_autotimer(bq, 1);
|
||||
|
||||
dev_info(bq->dev, "driver registered\n");
|
||||
return 0;
|
||||
|
||||
error_6:
|
||||
error_5:
|
||||
bq2415x_sysfs_exit(bq);
|
||||
error_4:
|
||||
bq2415x_power_supply_exit(bq);
|
||||
bq2415x_sysfs_exit(bq);
|
||||
error_3:
|
||||
if (bq->notify_psy)
|
||||
power_supply_put(bq->notify_psy);
|
||||
bq2415x_power_supply_exit(bq);
|
||||
error_2:
|
||||
if (bq->notify_node)
|
||||
of_node_put(bq->notify_node);
|
||||
kfree(name);
|
||||
error_1:
|
||||
mutex_lock(&bq2415x_id_mutex);
|
||||
|
@ -1707,10 +1721,11 @@ static int bq2415x_remove(struct i2c_client *client)
|
|||
{
|
||||
struct bq2415x_device *bq = i2c_get_clientdata(client);
|
||||
|
||||
if (bq->notify_psy) {
|
||||
if (bq->nb.notifier_call)
|
||||
power_supply_unreg_notifier(&bq->nb);
|
||||
power_supply_put(bq->notify_psy);
|
||||
}
|
||||
|
||||
if (bq->notify_node)
|
||||
of_node_put(bq->notify_node);
|
||||
|
||||
bq2415x_sysfs_exit(bq);
|
||||
bq2415x_power_supply_exit(bq);
|
||||
|
|
|
@ -902,7 +902,7 @@ static int bq24190_charger_property_is_writeable(struct power_supply *psy,
|
|||
}
|
||||
|
||||
static enum power_supply_property bq24190_charger_properties[] = {
|
||||
POWER_SUPPLY_PROP_TYPE,
|
||||
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
||||
|
@ -1515,6 +1515,7 @@ static const struct i2c_device_id bq24190_i2c_ids[] = {
|
|||
{ "bq24190", BQ24190_REG_VPRS_PN_24190 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id bq24190_of_match[] = {
|
||||
|
@ -1534,7 +1535,6 @@ static struct i2c_driver bq24190_driver = {
|
|||
.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),
|
||||
},
|
||||
|
|
|
@ -267,7 +267,8 @@ static int bq24735_charger_probe(struct i2c_client *client,
|
|||
|
||||
name = (char *)charger->pdata->name;
|
||||
if (!name) {
|
||||
name = kasprintf(GFP_KERNEL, "bq24735@%s",
|
||||
name = devm_kasprintf(&client->dev, GFP_KERNEL,
|
||||
"bq24735@%s",
|
||||
dev_name(&client->dev));
|
||||
if (!name) {
|
||||
dev_err(&client->dev, "Failed to alloc device name\n");
|
||||
|
@ -296,23 +297,21 @@ static int bq24735_charger_probe(struct i2c_client *client,
|
|||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read manufacturer id : %d\n",
|
||||
ret);
|
||||
goto err_free_name;
|
||||
return ret;
|
||||
} else if (ret != 0x0040) {
|
||||
dev_err(&client->dev,
|
||||
"manufacturer id mismatch. 0x0040 != 0x%04x\n", ret);
|
||||
ret = -ENODEV;
|
||||
goto err_free_name;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = bq24735_read_word(client, BQ24735_DEVICE_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read device id : %d\n", ret);
|
||||
goto err_free_name;
|
||||
return ret;
|
||||
} else if (ret != 0x000B) {
|
||||
dev_err(&client->dev,
|
||||
"device id mismatch. 0x000b != 0x%04x\n", ret);
|
||||
ret = -ENODEV;
|
||||
goto err_free_name;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(charger->pdata->status_gpio)) {
|
||||
|
@ -331,7 +330,7 @@ static int bq24735_charger_probe(struct i2c_client *client,
|
|||
ret = bq24735_config_charger(charger);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "failed in configuring charger");
|
||||
goto err_free_name;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check for AC adapter presence */
|
||||
|
@ -339,17 +338,17 @@ static int bq24735_charger_probe(struct i2c_client *client,
|
|||
ret = bq24735_enable_charging(charger);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to enable charging\n");
|
||||
goto err_free_name;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
charger->charger = power_supply_register(&client->dev, supply_desc,
|
||||
charger->charger = devm_power_supply_register(&client->dev, supply_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(charger->charger)) {
|
||||
ret = PTR_ERR(charger->charger);
|
||||
dev_err(&client->dev, "Failed to register power supply: %d\n",
|
||||
ret);
|
||||
goto err_free_name;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (client->irq) {
|
||||
|
@ -364,32 +363,9 @@ static int bq24735_charger_probe(struct i2c_client *client,
|
|||
dev_err(&client->dev,
|
||||
"Unable to register IRQ %d err %d\n",
|
||||
client->irq, ret);
|
||||
goto err_unregister_supply;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_unregister_supply:
|
||||
power_supply_unregister(charger->charger);
|
||||
err_free_name:
|
||||
if (name != charger->pdata->name)
|
||||
kfree(name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bq24735_charger_remove(struct i2c_client *client)
|
||||
{
|
||||
struct bq24735 *charger = i2c_get_clientdata(client);
|
||||
|
||||
if (charger->client->irq)
|
||||
devm_free_irq(&charger->client->dev, charger->client->irq,
|
||||
&charger->charger);
|
||||
|
||||
power_supply_unregister(charger->charger);
|
||||
|
||||
if (charger->charger_desc.name != charger->pdata->name)
|
||||
kfree(charger->charger_desc.name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -409,11 +385,9 @@ MODULE_DEVICE_TABLE(of, bq24735_match_ids);
|
|||
static struct i2c_driver bq24735_charger_driver = {
|
||||
.driver = {
|
||||
.name = "bq24735-charger",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = bq24735_match_ids,
|
||||
},
|
||||
.probe = bq24735_charger_probe,
|
||||
.remove = bq24735_charger_remove,
|
||||
.id_table = bq24735_charger_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
|
||||
#define DRIVER_VERSION "1.2.0"
|
||||
|
||||
#define BQ27XXX_MANUFACTURER "Texas Instruments"
|
||||
|
||||
#define BQ27x00_REG_TEMP 0x06
|
||||
#define BQ27x00_REG_VOLT 0x08
|
||||
#define BQ27x00_REG_AI 0x14
|
||||
|
@ -142,6 +144,7 @@ static enum power_supply_property bq27x00_battery_props[] = {
|
|||
POWER_SUPPLY_PROP_ENERGY_NOW,
|
||||
POWER_SUPPLY_PROP_POWER_AVG,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static enum power_supply_property bq27425_battery_props[] = {
|
||||
|
@ -156,6 +159,7 @@ static enum power_supply_property bq27425_battery_props[] = {
|
|||
POWER_SUPPLY_PROP_CHARGE_FULL,
|
||||
POWER_SUPPLY_PROP_CHARGE_NOW,
|
||||
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static enum power_supply_property bq27742_battery_props[] = {
|
||||
|
@ -174,6 +178,7 @@ static enum power_supply_property bq27742_battery_props[] = {
|
|||
POWER_SUPPLY_PROP_CYCLE_COUNT,
|
||||
POWER_SUPPLY_PROP_POWER_AVG,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static enum power_supply_property bq27510_battery_props[] = {
|
||||
|
@ -192,12 +197,13 @@ static enum power_supply_property bq27510_battery_props[] = {
|
|||
POWER_SUPPLY_PROP_CYCLE_COUNT,
|
||||
POWER_SUPPLY_PROP_POWER_AVG,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static unsigned int poll_interval = 360;
|
||||
module_param(poll_interval, uint, 0644);
|
||||
MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \
|
||||
"0 disables polling");
|
||||
MODULE_PARM_DESC(poll_interval,
|
||||
"battery poll interval in seconds - 0 disables polling");
|
||||
|
||||
/*
|
||||
* Common code for BQ27x00 devices
|
||||
|
@ -313,8 +319,9 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di)
|
|||
ilmd = bq27x00_read(di, BQ27510_REG_DCAP, false);
|
||||
else
|
||||
ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false);
|
||||
} else
|
||||
} else {
|
||||
ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true);
|
||||
}
|
||||
|
||||
if (ilmd < 0) {
|
||||
dev_dbg(di->dev, "error reading initial last measured discharge\n");
|
||||
|
@ -445,7 +452,7 @@ static int bq27x00_battery_read_health(struct bq27x00_device_info *di)
|
|||
return tval;
|
||||
}
|
||||
|
||||
if ((di->chip == BQ27500)) {
|
||||
if (di->chip == BQ27500) {
|
||||
if (tval & BQ27500_FLAG_SOCF)
|
||||
tval = POWER_SUPPLY_HEALTH_DEAD;
|
||||
else if (tval & BQ27500_FLAG_OTC)
|
||||
|
@ -749,6 +756,9 @@ static int bq27x00_battery_get_property(struct power_supply *psy,
|
|||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
ret = bq27x00_simple_value(di->cache.health, val);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = BQ27XXX_MANUFACTURER;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -827,7 +837,6 @@ static void bq27x00_powersupply_unregister(struct bq27x00_device_info *di)
|
|||
mutex_destroy(&di->lock);
|
||||
}
|
||||
|
||||
|
||||
/* i2c specific code */
|
||||
#ifdef CONFIG_BATTERY_BQ27X00_I2C
|
||||
|
||||
|
@ -888,14 +897,12 @@ static int bq27x00_battery_probe(struct i2c_client *client,
|
|||
|
||||
name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num);
|
||||
if (!name) {
|
||||
dev_err(&client->dev, "failed to allocate device name\n");
|
||||
retval = -ENOMEM;
|
||||
goto batt_failed;
|
||||
}
|
||||
|
||||
di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL);
|
||||
if (!di) {
|
||||
dev_err(&client->dev, "failed to allocate device info data\n");
|
||||
retval = -ENOMEM;
|
||||
goto batt_failed;
|
||||
}
|
||||
|
@ -956,8 +963,9 @@ static struct i2c_driver bq27x00_battery_driver = {
|
|||
static inline int bq27x00_battery_i2c_init(void)
|
||||
{
|
||||
int ret = i2c_add_driver(&bq27x00_battery_driver);
|
||||
|
||||
if (ret)
|
||||
printk(KERN_ERR "Unable to register BQ27x00 i2c driver\n");
|
||||
pr_err("Unable to register BQ27x00 i2c driver\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1028,10 +1036,8 @@ static int bq27000_battery_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
|
||||
if (!di) {
|
||||
dev_err(&pdev->dev, "failed to allocate device info data\n");
|
||||
if (!di)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, di);
|
||||
|
||||
|
@ -1064,8 +1070,9 @@ static struct platform_driver bq27000_battery_driver = {
|
|||
static inline int bq27x00_battery_platform_init(void)
|
||||
{
|
||||
int ret = platform_driver_register(&bq27000_battery_driver);
|
||||
|
||||
if (ret)
|
||||
printk(KERN_ERR "Unable to register BQ27000 platform driver\n");
|
||||
pr_err("Unable to register BQ27000 platform driver\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -637,10 +637,6 @@ static ssize_t ds2780_read_param_eeprom_bin(struct file *filp,
|
|||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
|
||||
|
||||
count = min_t(loff_t, count,
|
||||
DS2780_EEPROM_BLOCK1_END -
|
||||
DS2780_EEPROM_BLOCK1_START + 1 - off);
|
||||
|
||||
return ds2780_read_block(dev_info, buf,
|
||||
DS2780_EEPROM_BLOCK1_START + off, count);
|
||||
}
|
||||
|
@ -655,10 +651,6 @@ static ssize_t ds2780_write_param_eeprom_bin(struct file *filp,
|
|||
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
|
||||
int ret;
|
||||
|
||||
count = min_t(loff_t, count,
|
||||
DS2780_EEPROM_BLOCK1_END -
|
||||
DS2780_EEPROM_BLOCK1_START + 1 - off);
|
||||
|
||||
ret = ds2780_write(dev_info, buf,
|
||||
DS2780_EEPROM_BLOCK1_START + off, count);
|
||||
if (ret < 0)
|
||||
|
@ -676,7 +668,7 @@ static struct bin_attribute ds2780_param_eeprom_bin_attr = {
|
|||
.name = "param_eeprom",
|
||||
.mode = S_IRUGO | S_IWUSR,
|
||||
},
|
||||
.size = DS2780_EEPROM_BLOCK1_END - DS2780_EEPROM_BLOCK1_START + 1,
|
||||
.size = DS2780_PARAM_EEPROM_SIZE,
|
||||
.read = ds2780_read_param_eeprom_bin,
|
||||
.write = ds2780_write_param_eeprom_bin,
|
||||
};
|
||||
|
@ -690,10 +682,6 @@ static ssize_t ds2780_read_user_eeprom_bin(struct file *filp,
|
|||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
|
||||
|
||||
count = min_t(loff_t, count,
|
||||
DS2780_EEPROM_BLOCK0_END -
|
||||
DS2780_EEPROM_BLOCK0_START + 1 - off);
|
||||
|
||||
return ds2780_read_block(dev_info, buf,
|
||||
DS2780_EEPROM_BLOCK0_START + off, count);
|
||||
}
|
||||
|
@ -708,10 +696,6 @@ static ssize_t ds2780_write_user_eeprom_bin(struct file *filp,
|
|||
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
|
||||
int ret;
|
||||
|
||||
count = min_t(loff_t, count,
|
||||
DS2780_EEPROM_BLOCK0_END -
|
||||
DS2780_EEPROM_BLOCK0_START + 1 - off);
|
||||
|
||||
ret = ds2780_write(dev_info, buf,
|
||||
DS2780_EEPROM_BLOCK0_START + off, count);
|
||||
if (ret < 0)
|
||||
|
@ -729,7 +713,7 @@ static struct bin_attribute ds2780_user_eeprom_bin_attr = {
|
|||
.name = "user_eeprom",
|
||||
.mode = S_IRUGO | S_IWUSR,
|
||||
},
|
||||
.size = DS2780_EEPROM_BLOCK0_END - DS2780_EEPROM_BLOCK0_START + 1,
|
||||
.size = DS2780_USER_EEPROM_SIZE,
|
||||
.read = ds2780_read_user_eeprom_bin,
|
||||
.write = ds2780_write_user_eeprom_bin,
|
||||
};
|
||||
|
|
|
@ -639,8 +639,6 @@ static ssize_t ds2781_read_param_eeprom_bin(struct file *filp,
|
|||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
|
||||
|
||||
count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off);
|
||||
|
||||
return ds2781_read_block(dev_info, buf,
|
||||
DS2781_EEPROM_BLOCK1_START + off, count);
|
||||
}
|
||||
|
@ -655,8 +653,6 @@ static ssize_t ds2781_write_param_eeprom_bin(struct file *filp,
|
|||
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
|
||||
int ret;
|
||||
|
||||
count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off);
|
||||
|
||||
ret = ds2781_write(dev_info, buf,
|
||||
DS2781_EEPROM_BLOCK1_START + off, count);
|
||||
if (ret < 0)
|
||||
|
@ -688,8 +684,6 @@ static ssize_t ds2781_read_user_eeprom_bin(struct file *filp,
|
|||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
|
||||
|
||||
count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off);
|
||||
|
||||
return ds2781_read_block(dev_info, buf,
|
||||
DS2781_EEPROM_BLOCK0_START + off, count);
|
||||
|
||||
|
@ -705,8 +699,6 @@ static ssize_t ds2781_write_user_eeprom_bin(struct file *filp,
|
|||
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
|
||||
int ret;
|
||||
|
||||
count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off);
|
||||
|
||||
ret = ds2781_write(dev_info, buf,
|
||||
DS2781_EEPROM_BLOCK0_START + off, count);
|
||||
if (ret < 0)
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include <linux/swab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
|
@ -63,15 +62,11 @@ struct ltc294x_info {
|
|||
struct power_supply_desc supply_desc; /* Supply description */
|
||||
struct delayed_work work; /* Work scheduler */
|
||||
int num_regs; /* Number of registers (chip type) */
|
||||
int id; /* Identifier of ltc294x chip */
|
||||
int charge; /* Last charge register content */
|
||||
int r_sense; /* mOhm */
|
||||
int Qlsb; /* nAh */
|
||||
};
|
||||
|
||||
static DEFINE_IDR(ltc294x_id);
|
||||
static DEFINE_MUTEX(ltc294x_lock);
|
||||
|
||||
static inline int convert_bin_to_uAh(
|
||||
const struct ltc294x_info *info, int Q)
|
||||
{
|
||||
|
@ -371,10 +366,6 @@ static int ltc294x_i2c_remove(struct i2c_client *client)
|
|||
|
||||
cancel_delayed_work(&info->work);
|
||||
power_supply_unregister(info->supply);
|
||||
kfree(info->supply_desc.name);
|
||||
mutex_lock(<c294x_lock);
|
||||
idr_remove(<c294x_id, info->id);
|
||||
mutex_unlock(<c294x_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -384,44 +375,28 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
|
|||
struct power_supply_config psy_cfg = {};
|
||||
struct ltc294x_info *info;
|
||||
int ret;
|
||||
int num;
|
||||
u32 prescaler_exp;
|
||||
s32 r_sense;
|
||||
struct device_node *np;
|
||||
|
||||
mutex_lock(<c294x_lock);
|
||||
ret = idr_alloc(<c294x_id, client, 0, 0, GFP_KERNEL);
|
||||
mutex_unlock(<c294x_lock);
|
||||
if (ret < 0)
|
||||
goto fail_id;
|
||||
|
||||
num = ret;
|
||||
|
||||
info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (info == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_info;
|
||||
}
|
||||
if (info == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, info);
|
||||
|
||||
info->num_regs = id->driver_data;
|
||||
info->supply_desc.name = kasprintf(GFP_KERNEL, "%s-%d", client->name,
|
||||
num);
|
||||
if (!info->supply_desc.name) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_name;
|
||||
}
|
||||
|
||||
np = of_node_get(client->dev.of_node);
|
||||
|
||||
info->num_regs = id->driver_data;
|
||||
info->supply_desc.name = np->name;
|
||||
|
||||
/* r_sense can be negative, when sense+ is connected to the battery
|
||||
* instead of the sense-. This results in reversed measurements. */
|
||||
ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
"Could not find lltc,resistor-sense in devicetree\n");
|
||||
goto fail_name;
|
||||
return ret;
|
||||
}
|
||||
info->r_sense = r_sense;
|
||||
|
||||
|
@ -446,7 +421,6 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
|
|||
}
|
||||
|
||||
info->client = client;
|
||||
info->id = num;
|
||||
info->supply_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
info->supply_desc.properties = ltc294x_properties;
|
||||
if (info->num_regs >= LTC294X_REG_TEMPERATURE_LSB)
|
||||
|
@ -473,31 +447,19 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
|
|||
ret = ltc294x_reset(info, prescaler_exp);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Communication with chip failed\n");
|
||||
goto fail_comm;
|
||||
return ret;
|
||||
}
|
||||
|
||||
info->supply = power_supply_register(&client->dev, &info->supply_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(info->supply)) {
|
||||
dev_err(&client->dev, "failed to register ltc2941\n");
|
||||
ret = PTR_ERR(info->supply);
|
||||
goto fail_register;
|
||||
return PTR_ERR(info->supply);
|
||||
} else {
|
||||
schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_register:
|
||||
kfree(info->supply_desc.name);
|
||||
fail_comm:
|
||||
fail_name:
|
||||
fail_info:
|
||||
mutex_lock(<c294x_lock);
|
||||
idr_remove(<c294x_id, num);
|
||||
mutex_unlock(<c294x_lock);
|
||||
fail_id:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
|
|
@ -521,11 +521,6 @@ static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
|
|||
int ret;
|
||||
int i;
|
||||
|
||||
if (off >= EEPROM_SIZE)
|
||||
return 0;
|
||||
if (off + count > EEPROM_SIZE)
|
||||
count = EEPROM_SIZE - off;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
ec_byte = EEPROM_START + off + i;
|
||||
ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1);
|
||||
|
@ -545,7 +540,7 @@ static struct bin_attribute olpc_bat_eeprom = {
|
|||
.name = "eeprom",
|
||||
.mode = S_IRUGO,
|
||||
},
|
||||
.size = 0,
|
||||
.size = EEPROM_SIZE,
|
||||
.read = olpc_bat_eeprom_read,
|
||||
};
|
||||
|
||||
|
|
|
@ -1244,7 +1244,6 @@ static struct i2c_driver pm2xxx_charger_driver = {
|
|||
.remove = pm2xxx_wall_charger_remove,
|
||||
.driver = {
|
||||
.name = "pm2xxx-wall_charger",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = PM2XXX_PM_OPS,
|
||||
},
|
||||
.id_table = pm2xxx_id,
|
||||
|
|
|
@ -166,5 +166,12 @@ config POWER_RESET_RMOBILE
|
|||
help
|
||||
Reboot support for Renesas R-Mobile and SH-Mobile SoCs.
|
||||
|
||||
config POWER_RESET_ZX
|
||||
tristate "ZTE SoCs reset driver"
|
||||
depends on ARCH_ZX || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Reboot support for ZTE SoCs.
|
||||
|
||||
endif
|
||||
|
||||
|
|
|
@ -19,3 +19,4 @@ obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o
|
|||
obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
|
||||
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
|
||||
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
|
||||
obj-$(CONFIG_POWER_RESET_ZX) += zx-reboot.o
|
||||
|
|
|
@ -123,6 +123,15 @@ static int at91sam9g45_restart(struct notifier_block *this, unsigned long mode,
|
|||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int sama5d3_restart(struct notifier_block *this, unsigned long mode,
|
||||
void *cmd)
|
||||
{
|
||||
writel(cpu_to_le32(AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST),
|
||||
at91_rstc_base);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static void __init at91_reset_status(struct platform_device *pdev)
|
||||
{
|
||||
u32 reg = readl(at91_rstc_base + AT91_RSTC_SR);
|
||||
|
@ -155,13 +164,13 @@ static void __init at91_reset_status(struct platform_device *pdev)
|
|||
static const struct of_device_id at91_ramc_of_match[] = {
|
||||
{ .compatible = "atmel,at91sam9260-sdramc", },
|
||||
{ .compatible = "atmel,at91sam9g45-ddramc", },
|
||||
{ .compatible = "atmel,sama5d3-ddramc", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static const struct of_device_id at91_reset_of_match[] = {
|
||||
{ .compatible = "atmel,at91sam9260-rstc", .data = at91sam9260_restart },
|
||||
{ .compatible = "atmel,at91sam9g45-rstc", .data = at91sam9g45_restart },
|
||||
{ .compatible = "atmel,sama5d3-rstc", .data = sama5d3_restart },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
|
@ -181,6 +190,8 @@ static int at91_reset_of_probe(struct platform_device *pdev)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!of_device_is_compatible(pdev->dev.of_node, "atmel,sama5d3-rstc")) {
|
||||
/* we need to shutdown the ddr controller, so get ramc base */
|
||||
for_each_matching_node(np, at91_ramc_of_match) {
|
||||
at91_ramc_base[idx] = of_iomap(np, 0);
|
||||
if (!at91_ramc_base[idx]) {
|
||||
|
@ -189,6 +200,7 @@ static int at91_reset_of_probe(struct platform_device *pdev)
|
|||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
match = of_match_node(at91_reset_of_match, pdev->dev.of_node);
|
||||
at91_restart_nb.notifier_call = match->data;
|
||||
|
|
80
drivers/power/reset/zx-reboot.c
Normal file
80
drivers/power/reset/zx-reboot.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* ZTE zx296702 SoC reset code
|
||||
*
|
||||
* Copyright (c) 2015 Linaro Ltd.
|
||||
*
|
||||
* Author: Jun Nie <jun.nie@linaro.org>
|
||||
*
|
||||
* 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/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reboot.h>
|
||||
|
||||
static void __iomem *base;
|
||||
static void __iomem *pcu_base;
|
||||
|
||||
static int zx_restart_handler(struct notifier_block *this,
|
||||
unsigned long mode, void *cmd)
|
||||
{
|
||||
writel_relaxed(1, base + 0xb0);
|
||||
writel_relaxed(1, pcu_base + 0x34);
|
||||
|
||||
mdelay(50);
|
||||
pr_emerg("Unable to restart system\n");
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block zx_restart_nb = {
|
||||
.notifier_call = zx_restart_handler,
|
||||
.priority = 128,
|
||||
};
|
||||
|
||||
static int zx_reboot_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int err;
|
||||
|
||||
base = of_iomap(np, 0);
|
||||
if (!base) {
|
||||
WARN(1, "failed to map base address");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
np = of_find_compatible_node(NULL, NULL, "zte,zx296702-pcu");
|
||||
pcu_base = of_iomap(np, 0);
|
||||
if (!pcu_base) {
|
||||
iounmap(base);
|
||||
WARN(1, "failed to map pcu_base address");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = register_restart_handler(&zx_restart_nb);
|
||||
if (err)
|
||||
dev_err(&pdev->dev, "Register restart handler failed(err=%d)\n",
|
||||
err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct of_device_id zx_reboot_of_match[] = {
|
||||
{ .compatible = "zte,sysctrl" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver zx_reboot_driver = {
|
||||
.probe = zx_reboot_probe,
|
||||
.driver = {
|
||||
.name = "zx-reboot",
|
||||
.of_match_table = zx_reboot_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(zx_reboot_driver);
|
|
@ -165,7 +165,7 @@ static const struct i2c_device_id rt5033_battery_id[] = {
|
|||
{ "rt5033-battery", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, rt5033_battery_id);
|
||||
MODULE_DEVICE_TABLE(i2c, rt5033_battery_id);
|
||||
|
||||
static struct i2c_driver rt5033_battery_driver = {
|
||||
.driver = {
|
||||
|
|
|
@ -973,7 +973,6 @@ static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info,
|
|||
|
||||
if (irq2 & GET_MASK(F_CHRVPI)) {
|
||||
dev_dbg(dev, "Charger fault occurred\n");
|
||||
alert_userspace = true;
|
||||
/*
|
||||
* CHRVPI bit is set in 2 cases:
|
||||
* 1. when the power source is connected to the charger.
|
||||
|
@ -981,6 +980,9 @@ static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info,
|
|||
* To identify the case, PWR_RDY bit is checked. Because
|
||||
* PWR_RDY bit is set / cleared after CHRVPI interrupt is
|
||||
* triggered, it is used delayed_work to later read PWR_RDY bit.
|
||||
* Also, do not set to true alert_userspace, because there is no
|
||||
* need to notify userspace when CHRVPI interrupt has occurred.
|
||||
* Userspace will be notified after PWR_RDY bit is read.
|
||||
*/
|
||||
queue_delayed_work(system_power_efficient_wq,
|
||||
&info->pwr_rdy_work,
|
||||
|
@ -1178,7 +1180,7 @@ static irqreturn_t rt9455_irq_handler_thread(int irq, void *data)
|
|||
/*
|
||||
* Sometimes, an interrupt occurs while rt9455_probe() function
|
||||
* is executing and power_supply_register() is not yet called.
|
||||
* Do not call power_supply_charged() in this case.
|
||||
* Do not call power_supply_changed() in this case.
|
||||
*/
|
||||
if (info->charger)
|
||||
power_supply_changed(info->charger);
|
||||
|
@ -1478,6 +1480,11 @@ static void rt9455_pwr_rdy_work_callback(struct work_struct *work)
|
|||
RT9455_MAX_CHARGING_TIME * HZ);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Notify userspace that the charger has been either connected to or
|
||||
* disconnected from the power source.
|
||||
*/
|
||||
power_supply_changed(info->charger);
|
||||
}
|
||||
|
||||
static void rt9455_max_charging_time_work_callback(struct work_struct *work)
|
||||
|
@ -1533,6 +1540,11 @@ static void rt9455_batt_presence_work_callback(struct work_struct *work)
|
|||
if (ret)
|
||||
dev_err(dev, "Failed to unmask BATAB interrupt\n");
|
||||
}
|
||||
/*
|
||||
* Notify userspace that the battery is now connected to the
|
||||
* charger.
|
||||
*/
|
||||
power_supply_changed(info->charger);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,7 +215,7 @@ static int rx51_battery_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, di);
|
||||
|
||||
di->dev = &pdev->dev;
|
||||
di->bat_desc.name = dev_name(&pdev->dev);
|
||||
di->bat_desc.name = "rx51-battery";
|
||||
di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
di->bat_desc.properties = rx51_battery_props;
|
||||
di->bat_desc.num_properties = ARRAY_SIZE(rx51_battery_props);
|
||||
|
|
|
@ -22,8 +22,10 @@
|
|||
#include <linux/power_supply.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/i2c/twl4030-madc.h>
|
||||
|
||||
#define TWL4030_BCIMDEN 0x00
|
||||
#define TWL4030_BCIMDKEY 0x01
|
||||
#define TWL4030_BCIMSTATEC 0x02
|
||||
#define TWL4030_BCIICHG 0x08
|
||||
#define TWL4030_BCIVAC 0x0a
|
||||
|
@ -32,11 +34,19 @@
|
|||
#define TWL4030_BCIMFSTS4 0x10
|
||||
#define TWL4030_BCICTL1 0x23
|
||||
#define TWL4030_BB_CFG 0x12
|
||||
#define TWL4030_BCIIREF1 0x27
|
||||
#define TWL4030_BCIIREF2 0x28
|
||||
#define TWL4030_BCIMFKEY 0x11
|
||||
#define TWL4030_BCIMFEN3 0x14
|
||||
#define TWL4030_BCIMFTH8 0x1d
|
||||
#define TWL4030_BCIMFTH9 0x1e
|
||||
#define TWL4030_BCIWDKEY 0x21
|
||||
|
||||
#define TWL4030_BCIMFSTS1 0x01
|
||||
|
||||
#define TWL4030_BCIAUTOWEN BIT(5)
|
||||
#define TWL4030_CONFIG_DONE BIT(4)
|
||||
#define TWL4030_CVENAC BIT(2)
|
||||
#define TWL4030_BCIAUTOUSB BIT(1)
|
||||
#define TWL4030_BCIAUTOAC BIT(0)
|
||||
#define TWL4030_CGAIN BIT(5)
|
||||
|
@ -81,6 +91,21 @@
|
|||
#define TWL4030_MSTATEC_COMPLETE1 0x0b
|
||||
#define TWL4030_MSTATEC_COMPLETE4 0x0e
|
||||
|
||||
#if IS_ENABLED(CONFIG_TWL4030_MADC)
|
||||
/*
|
||||
* If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
|
||||
* then AC is available.
|
||||
*/
|
||||
static inline int ac_available(void)
|
||||
{
|
||||
return twl4030_get_madc_conversion(11) > 4500;
|
||||
}
|
||||
#else
|
||||
static inline int ac_available(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
static bool allow_usb;
|
||||
module_param(allow_usb, bool, 0644);
|
||||
MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current");
|
||||
|
@ -94,12 +119,39 @@ struct twl4030_bci {
|
|||
struct work_struct work;
|
||||
int irq_chg;
|
||||
int irq_bci;
|
||||
struct regulator *usb_reg;
|
||||
int usb_enabled;
|
||||
|
||||
/*
|
||||
* ichg_* and *_cur values in uA. If any are 'large', we set
|
||||
* CGAIN to '1' which doubles the range for half the
|
||||
* precision.
|
||||
*/
|
||||
unsigned int ichg_eoc, ichg_lo, ichg_hi;
|
||||
unsigned int usb_cur, ac_cur;
|
||||
bool ac_is_active;
|
||||
int usb_mode, ac_mode; /* charging mode requested */
|
||||
#define CHARGE_OFF 0
|
||||
#define CHARGE_AUTO 1
|
||||
#define CHARGE_LINEAR 2
|
||||
|
||||
/* When setting the USB current we slowly increase the
|
||||
* requested current until target is reached or the voltage
|
||||
* drops below 4.75V. In the latter case we step back one
|
||||
* step.
|
||||
*/
|
||||
unsigned int usb_cur_target;
|
||||
struct delayed_work current_worker;
|
||||
#define USB_CUR_STEP 20000 /* 20mA at a time */
|
||||
#define USB_MIN_VOLT 4750000 /* 4.75V */
|
||||
#define USB_CUR_DELAY msecs_to_jiffies(100)
|
||||
#define USB_MAX_CURRENT 1700000 /* TWL4030 caps at 1.7A */
|
||||
|
||||
unsigned long event;
|
||||
};
|
||||
|
||||
/* strings for 'usb_mode' values */
|
||||
static char *modes[] = { "off", "auto", "continuous" };
|
||||
|
||||
/*
|
||||
* clear and set bits on an given register on a given module
|
||||
*/
|
||||
|
@ -180,25 +232,231 @@ static int twl4030_is_battery_present(struct twl4030_bci *bci)
|
|||
}
|
||||
|
||||
/*
|
||||
* Check if VBUS power is present
|
||||
* TI provided formulas:
|
||||
* CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
|
||||
* CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
|
||||
* Here we use integer approximation of:
|
||||
* CGAIN == 0: val * 1.6618 - 0.85 * 1000
|
||||
* CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2
|
||||
*/
|
||||
static int twl4030_bci_have_vbus(struct twl4030_bci *bci)
|
||||
/*
|
||||
* convert twl register value for currents into uA
|
||||
*/
|
||||
static int regval2ua(int regval, bool cgain)
|
||||
{
|
||||
if (cgain)
|
||||
return (regval * 16618 - 8500 * 1000) / 5;
|
||||
else
|
||||
return (regval * 16618 - 8500 * 1000) / 10;
|
||||
}
|
||||
|
||||
/*
|
||||
* convert uA currents into twl register value
|
||||
*/
|
||||
static int ua2regval(int ua, bool cgain)
|
||||
{
|
||||
int ret;
|
||||
u8 hwsts;
|
||||
if (cgain)
|
||||
ua /= 2;
|
||||
ret = (ua * 10 + 8500 * 1000) / 16618;
|
||||
/* rounding problems */
|
||||
if (ret < 512)
|
||||
ret = 512;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &hwsts,
|
||||
TWL4030_PM_MASTER_STS_HW_CONDITIONS);
|
||||
if (ret < 0)
|
||||
static int twl4030_charger_update_current(struct twl4030_bci *bci)
|
||||
{
|
||||
int status;
|
||||
int cur;
|
||||
unsigned reg, cur_reg;
|
||||
u8 bcictl1, oldreg, fullreg;
|
||||
bool cgain = false;
|
||||
u8 boot_bci;
|
||||
|
||||
/*
|
||||
* If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
|
||||
* and AC is enabled, set current for 'ac'
|
||||
*/
|
||||
if (ac_available()) {
|
||||
cur = bci->ac_cur;
|
||||
bci->ac_is_active = true;
|
||||
} else {
|
||||
cur = bci->usb_cur;
|
||||
bci->ac_is_active = false;
|
||||
if (cur > bci->usb_cur_target) {
|
||||
cur = bci->usb_cur_target;
|
||||
bci->usb_cur = cur;
|
||||
}
|
||||
if (cur < bci->usb_cur_target)
|
||||
schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
|
||||
}
|
||||
|
||||
/* First, check thresholds and see if cgain is needed */
|
||||
if (bci->ichg_eoc >= 200000)
|
||||
cgain = true;
|
||||
if (bci->ichg_lo >= 400000)
|
||||
cgain = true;
|
||||
if (bci->ichg_hi >= 820000)
|
||||
cgain = true;
|
||||
if (cur > 852000)
|
||||
cgain = true;
|
||||
|
||||
status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
|
||||
if (status < 0)
|
||||
return status;
|
||||
if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci,
|
||||
TWL4030_PM_MASTER_BOOT_BCI) < 0)
|
||||
boot_bci = 0;
|
||||
boot_bci &= 7;
|
||||
|
||||
if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN))
|
||||
/* Need to turn for charging while we change the
|
||||
* CGAIN bit. Leave it off while everything is
|
||||
* updated.
|
||||
*/
|
||||
twl4030_clear_set_boot_bci(boot_bci, 0);
|
||||
|
||||
/*
|
||||
* For ichg_eoc, the hardware only supports reg values matching
|
||||
* 100XXXX000, and requires the XXXX be stored in the high nibble
|
||||
* of TWL4030_BCIMFTH8.
|
||||
*/
|
||||
reg = ua2regval(bci->ichg_eoc, cgain);
|
||||
if (reg > 0x278)
|
||||
reg = 0x278;
|
||||
if (reg < 0x200)
|
||||
reg = 0x200;
|
||||
reg = (reg >> 3) & 0xf;
|
||||
fullreg = reg << 4;
|
||||
|
||||
/*
|
||||
* For ichg_lo, reg value must match 10XXXX0000.
|
||||
* XXXX is stored in low nibble of TWL4030_BCIMFTH8.
|
||||
*/
|
||||
reg = ua2regval(bci->ichg_lo, cgain);
|
||||
if (reg > 0x2F0)
|
||||
reg = 0x2F0;
|
||||
if (reg < 0x200)
|
||||
reg = 0x200;
|
||||
reg = (reg >> 4) & 0xf;
|
||||
fullreg |= reg;
|
||||
|
||||
/* ichg_eoc and ichg_lo live in same register */
|
||||
status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg);
|
||||
if (status < 0)
|
||||
return status;
|
||||
if (oldreg != fullreg) {
|
||||
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4,
|
||||
TWL4030_BCIMFKEY);
|
||||
if (status < 0)
|
||||
return status;
|
||||
twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
|
||||
fullreg, TWL4030_BCIMFTH8);
|
||||
}
|
||||
|
||||
/* ichg_hi threshold must be 1XXXX01100 (I think) */
|
||||
reg = ua2regval(bci->ichg_hi, cgain);
|
||||
if (reg > 0x3E0)
|
||||
reg = 0x3E0;
|
||||
if (reg < 0x200)
|
||||
reg = 0x200;
|
||||
fullreg = (reg >> 5) & 0xF;
|
||||
fullreg <<= 4;
|
||||
status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg);
|
||||
if (status < 0)
|
||||
return status;
|
||||
if ((oldreg & 0xF0) != fullreg) {
|
||||
fullreg |= (oldreg & 0x0F);
|
||||
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
|
||||
TWL4030_BCIMFKEY);
|
||||
if (status < 0)
|
||||
return status;
|
||||
twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
|
||||
fullreg, TWL4030_BCIMFTH9);
|
||||
}
|
||||
|
||||
/*
|
||||
* And finally, set the current. This is stored in
|
||||
* two registers.
|
||||
*/
|
||||
reg = ua2regval(cur, cgain);
|
||||
/* we have only 10 bits */
|
||||
if (reg > 0x3ff)
|
||||
reg = 0x3ff;
|
||||
status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg);
|
||||
if (status < 0)
|
||||
return status;
|
||||
cur_reg = oldreg;
|
||||
status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg);
|
||||
if (status < 0)
|
||||
return status;
|
||||
cur_reg |= oldreg << 8;
|
||||
if (reg != oldreg) {
|
||||
/* disable write protection for one write access for
|
||||
* BCIIREF */
|
||||
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
|
||||
TWL4030_BCIMFKEY);
|
||||
if (status < 0)
|
||||
return status;
|
||||
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
|
||||
(reg & 0x100) ? 3 : 2,
|
||||
TWL4030_BCIIREF2);
|
||||
if (status < 0)
|
||||
return status;
|
||||
/* disable write protection for one write access for
|
||||
* BCIIREF */
|
||||
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
|
||||
TWL4030_BCIMFKEY);
|
||||
if (status < 0)
|
||||
return status;
|
||||
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
|
||||
reg & 0xff,
|
||||
TWL4030_BCIIREF1);
|
||||
}
|
||||
if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) {
|
||||
/* Flip CGAIN and re-enable charging */
|
||||
bcictl1 ^= TWL4030_CGAIN;
|
||||
twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
|
||||
bcictl1, TWL4030_BCICTL1);
|
||||
twl4030_clear_set_boot_bci(0, boot_bci);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts);
|
||||
static int twl4030_charger_get_current(void);
|
||||
|
||||
/* in case we also have STS_USB_ID, VBUS is driven by TWL itself */
|
||||
if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID))
|
||||
return 1;
|
||||
static void twl4030_current_worker(struct work_struct *data)
|
||||
{
|
||||
int v, curr;
|
||||
int res;
|
||||
struct twl4030_bci *bci = container_of(data, struct twl4030_bci,
|
||||
current_worker.work);
|
||||
|
||||
return 0;
|
||||
res = twl4030bci_read_adc_val(TWL4030_BCIVBUS);
|
||||
if (res < 0)
|
||||
v = 0;
|
||||
else
|
||||
/* BCIVBUS uses ADCIN8, 7/1023 V/step */
|
||||
v = res * 6843;
|
||||
curr = twl4030_charger_get_current();
|
||||
|
||||
dev_dbg(bci->dev, "v=%d cur=%d limit=%d target=%d\n", v, curr,
|
||||
bci->usb_cur, bci->usb_cur_target);
|
||||
|
||||
if (v < USB_MIN_VOLT) {
|
||||
/* Back up and stop adjusting. */
|
||||
bci->usb_cur -= USB_CUR_STEP;
|
||||
bci->usb_cur_target = bci->usb_cur;
|
||||
} else if (bci->usb_cur >= bci->usb_cur_target ||
|
||||
bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) {
|
||||
/* Reached target and voltage is OK - stop */
|
||||
return;
|
||||
} else {
|
||||
bci->usb_cur += USB_CUR_STEP;
|
||||
schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
|
||||
}
|
||||
twl4030_charger_update_current(bci);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -208,45 +466,60 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (enable) {
|
||||
/* Check for USB charger connected */
|
||||
if (!twl4030_bci_have_vbus(bci))
|
||||
return -ENODEV;
|
||||
if (bci->usb_mode == CHARGE_OFF)
|
||||
enable = false;
|
||||
if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
|
||||
|
||||
/*
|
||||
* Until we can find out what current the device can provide,
|
||||
* require a module param to enable USB charging.
|
||||
*/
|
||||
if (!allow_usb) {
|
||||
dev_warn(bci->dev, "USB charging is disabled.\n");
|
||||
return -EACCES;
|
||||
}
|
||||
twl4030_charger_update_current(bci);
|
||||
|
||||
/* Need to keep regulator on */
|
||||
/* Need to keep phy powered */
|
||||
if (!bci->usb_enabled) {
|
||||
ret = regulator_enable(bci->usb_reg);
|
||||
if (ret) {
|
||||
dev_err(bci->dev,
|
||||
"Failed to enable regulator\n");
|
||||
return ret;
|
||||
}
|
||||
pm_runtime_get_sync(bci->transceiver->dev);
|
||||
bci->usb_enabled = 1;
|
||||
}
|
||||
|
||||
if (bci->usb_mode == CHARGE_AUTO)
|
||||
/* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
|
||||
ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
|
||||
ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0,
|
||||
TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4);
|
||||
if (bci->usb_mode == CHARGE_LINEAR) {
|
||||
twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0);
|
||||
/* Watch dog key: WOVF acknowledge */
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33,
|
||||
TWL4030_BCIWDKEY);
|
||||
/* 0x24 + EKEY6: off mode */
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
|
||||
TWL4030_BCIMDKEY);
|
||||
/* EKEY2: Linear charge: USB path */
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26,
|
||||
TWL4030_BCIMDKEY);
|
||||
/* WDKEY5: stop watchdog count */
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3,
|
||||
TWL4030_BCIWDKEY);
|
||||
/* enable MFEN3 access */
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c,
|
||||
TWL4030_BCIMFKEY);
|
||||
/* ICHGEOCEN - end-of-charge monitor (current < 80mA)
|
||||
* (charging continues)
|
||||
* ICHGLOWEN - current level monitor (charge continues)
|
||||
* don't monitor over-current or heat save
|
||||
*/
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0,
|
||||
TWL4030_BCIMFEN3);
|
||||
}
|
||||
} else {
|
||||
ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
|
||||
ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
|
||||
TWL4030_BCIMDKEY);
|
||||
if (bci->usb_enabled) {
|
||||
regulator_disable(bci->usb_reg);
|
||||
pm_runtime_mark_last_busy(bci->transceiver->dev);
|
||||
pm_runtime_put_autosuspend(bci->transceiver->dev);
|
||||
bci->usb_enabled = 0;
|
||||
}
|
||||
bci->usb_cur = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -255,10 +528,13 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
|
|||
/*
|
||||
* Enable/Disable AC Charge funtionality.
|
||||
*/
|
||||
static int twl4030_charger_enable_ac(bool enable)
|
||||
static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (bci->ac_mode == CHARGE_OFF)
|
||||
enable = false;
|
||||
|
||||
if (enable)
|
||||
ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC);
|
||||
else
|
||||
|
@ -318,6 +594,9 @@ static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
|
|||
struct twl4030_bci *bci = arg;
|
||||
|
||||
dev_dbg(bci->dev, "CHG_PRES irq\n");
|
||||
/* reset current on each 'plug' event */
|
||||
bci->ac_cur = 500000;
|
||||
twl4030_charger_update_current(bci);
|
||||
power_supply_changed(bci->ac);
|
||||
power_supply_changed(bci->usb);
|
||||
|
||||
|
@ -350,6 +629,7 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
|
|||
power_supply_changed(bci->ac);
|
||||
power_supply_changed(bci->usb);
|
||||
}
|
||||
twl4030_charger_update_current(bci);
|
||||
|
||||
/* various monitoring events, for now we just log them here */
|
||||
if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
|
||||
|
@ -370,6 +650,63 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide "max_current" attribute in sysfs.
|
||||
*/
|
||||
static ssize_t
|
||||
twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
|
||||
int cur = 0;
|
||||
int status = 0;
|
||||
status = kstrtoint(buf, 10, &cur);
|
||||
if (status)
|
||||
return status;
|
||||
if (cur < 0)
|
||||
return -EINVAL;
|
||||
if (dev == &bci->ac->dev)
|
||||
bci->ac_cur = cur;
|
||||
else
|
||||
bci->usb_cur_target = cur;
|
||||
|
||||
twl4030_charger_update_current(bci);
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* sysfs max_current show
|
||||
*/
|
||||
static ssize_t twl4030_bci_max_current_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int status = 0;
|
||||
int cur = -1;
|
||||
u8 bcictl1;
|
||||
struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (dev == &bci->ac->dev) {
|
||||
if (!bci->ac_is_active)
|
||||
cur = bci->ac_cur;
|
||||
} else {
|
||||
if (bci->ac_is_active)
|
||||
cur = bci->usb_cur_target;
|
||||
}
|
||||
if (cur < 0) {
|
||||
cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1);
|
||||
if (cur < 0)
|
||||
return cur;
|
||||
status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
|
||||
if (status < 0)
|
||||
return status;
|
||||
cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN);
|
||||
}
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", cur);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show,
|
||||
twl4030_bci_max_current_store);
|
||||
|
||||
static void twl4030_bci_usb_work(struct work_struct *data)
|
||||
{
|
||||
struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
|
||||
|
@ -392,6 +729,12 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
|
|||
|
||||
dev_dbg(bci->dev, "OTG notify %lu\n", val);
|
||||
|
||||
/* reset current on each 'plug' event */
|
||||
if (allow_usb)
|
||||
bci->usb_cur_target = 500000;
|
||||
else
|
||||
bci->usb_cur_target = 100000;
|
||||
|
||||
bci->event = val;
|
||||
schedule_work(&bci->work);
|
||||
|
||||
|
@ -399,13 +742,66 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
|
|||
}
|
||||
|
||||
/*
|
||||
* TI provided formulas:
|
||||
* CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
|
||||
* CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
|
||||
* Here we use integer approximation of:
|
||||
* CGAIN == 0: val * 1.6618 - 0.85
|
||||
* CGAIN == 1: (val * 1.6618 - 0.85) * 2
|
||||
* sysfs charger enabled store
|
||||
*/
|
||||
static ssize_t
|
||||
twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
|
||||
int mode;
|
||||
int status;
|
||||
|
||||
if (sysfs_streq(buf, modes[0]))
|
||||
mode = 0;
|
||||
else if (sysfs_streq(buf, modes[1]))
|
||||
mode = 1;
|
||||
else if (sysfs_streq(buf, modes[2]))
|
||||
mode = 2;
|
||||
else
|
||||
return -EINVAL;
|
||||
if (dev == &bci->ac->dev) {
|
||||
if (mode == 2)
|
||||
return -EINVAL;
|
||||
twl4030_charger_enable_ac(bci, false);
|
||||
bci->ac_mode = mode;
|
||||
status = twl4030_charger_enable_ac(bci, true);
|
||||
} else {
|
||||
twl4030_charger_enable_usb(bci, false);
|
||||
bci->usb_mode = mode;
|
||||
status = twl4030_charger_enable_usb(bci, true);
|
||||
}
|
||||
return (status == 0) ? n : status;
|
||||
}
|
||||
|
||||
/*
|
||||
* sysfs charger enabled show
|
||||
*/
|
||||
static ssize_t
|
||||
twl4030_bci_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
|
||||
int len = 0;
|
||||
int i;
|
||||
int mode = bci->usb_mode;
|
||||
|
||||
if (dev == &bci->ac->dev)
|
||||
mode = bci->ac_mode;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(modes); i++)
|
||||
if (mode == i)
|
||||
len += snprintf(buf+len, PAGE_SIZE-len,
|
||||
"[%s] ", modes[i]);
|
||||
else
|
||||
len += snprintf(buf+len, PAGE_SIZE-len,
|
||||
"%s ", modes[i]);
|
||||
buf[len-1] = '\n';
|
||||
return len;
|
||||
}
|
||||
static DEVICE_ATTR(mode, 0644, twl4030_bci_mode_show,
|
||||
twl4030_bci_mode_store);
|
||||
|
||||
static int twl4030_charger_get_current(void)
|
||||
{
|
||||
int curr;
|
||||
|
@ -420,11 +816,7 @@ static int twl4030_charger_get_current(void)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = (curr * 16618 - 850 * 10000) / 10;
|
||||
if (bcictl1 & TWL4030_CGAIN)
|
||||
ret *= 2;
|
||||
|
||||
return ret;
|
||||
return regval2ua(curr, bcictl1 & TWL4030_CGAIN);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -476,6 +868,17 @@ static int twl4030_bci_get_property(struct power_supply *psy,
|
|||
is_charging = state & TWL4030_MSTATEC_USB;
|
||||
else
|
||||
is_charging = state & TWL4030_MSTATEC_AC;
|
||||
if (!is_charging) {
|
||||
u8 s;
|
||||
twl4030_bci_read(TWL4030_BCIMDEN, &s);
|
||||
if (psy->desc->type == POWER_SUPPLY_TYPE_USB)
|
||||
is_charging = s & 1;
|
||||
else
|
||||
is_charging = s & 2;
|
||||
if (is_charging)
|
||||
/* A little white lie */
|
||||
state = TWL4030_MSTATEC_QUICK1;
|
||||
}
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
|
@ -574,20 +977,31 @@ static const struct power_supply_desc twl4030_bci_usb_desc = {
|
|||
.get_property = twl4030_bci_get_property,
|
||||
};
|
||||
|
||||
static int __init twl4030_bci_probe(struct platform_device *pdev)
|
||||
static int twl4030_bci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct twl4030_bci *bci;
|
||||
const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data;
|
||||
int ret;
|
||||
u32 reg;
|
||||
|
||||
bci = kzalloc(sizeof(*bci), GFP_KERNEL);
|
||||
bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL);
|
||||
if (bci == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!pdata)
|
||||
pdata = twl4030_bci_parse_dt(&pdev->dev);
|
||||
|
||||
bci->ichg_eoc = 80100; /* Stop charging when current drops to here */
|
||||
bci->ichg_lo = 241000; /* Low threshold */
|
||||
bci->ichg_hi = 500000; /* High threshold */
|
||||
bci->ac_cur = 500000; /* 500mA */
|
||||
if (allow_usb)
|
||||
bci->usb_cur_target = 500000; /* 500mA */
|
||||
else
|
||||
bci->usb_cur_target = 100000; /* 100mA */
|
||||
bci->usb_mode = CHARGE_AUTO;
|
||||
bci->ac_mode = CHARGE_AUTO;
|
||||
|
||||
bci->dev = &pdev->dev;
|
||||
bci->irq_chg = platform_get_irq(pdev, 0);
|
||||
bci->irq_bci = platform_get_irq(pdev, 1);
|
||||
|
@ -596,47 +1010,46 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
|
|||
ret = twl4030_is_battery_present(bci);
|
||||
if (ret) {
|
||||
dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret);
|
||||
goto fail_no_battery;
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, bci);
|
||||
|
||||
bci->ac = power_supply_register(&pdev->dev, &twl4030_bci_ac_desc,
|
||||
bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc,
|
||||
NULL);
|
||||
if (IS_ERR(bci->ac)) {
|
||||
ret = PTR_ERR(bci->ac);
|
||||
dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
|
||||
goto fail_register_ac;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bci->usb_reg = regulator_get(bci->dev, "bci3v1");
|
||||
|
||||
bci->usb = power_supply_register(&pdev->dev, &twl4030_bci_usb_desc,
|
||||
bci->usb = devm_power_supply_register(&pdev->dev, &twl4030_bci_usb_desc,
|
||||
NULL);
|
||||
if (IS_ERR(bci->usb)) {
|
||||
ret = PTR_ERR(bci->usb);
|
||||
dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
|
||||
goto fail_register_usb;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(bci->irq_chg, NULL,
|
||||
ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
|
||||
twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name,
|
||||
bci);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "could not request irq %d, status %d\n",
|
||||
bci->irq_chg, ret);
|
||||
goto fail_chg_irq;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(bci->irq_bci, NULL,
|
||||
ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
|
||||
twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "could not request irq %d, status %d\n",
|
||||
bci->irq_bci, ret);
|
||||
goto fail_bci_irq;
|
||||
return ret;
|
||||
}
|
||||
|
||||
INIT_WORK(&bci->work, twl4030_bci_usb_work);
|
||||
INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
|
||||
|
||||
bci->usb_nb.notifier_call = twl4030_bci_usb_ncb;
|
||||
if (bci->dev->of_node) {
|
||||
|
@ -644,9 +1057,13 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
|
|||
|
||||
phynode = of_find_compatible_node(bci->dev->of_node->parent,
|
||||
NULL, "ti,twl4030-usb");
|
||||
if (phynode)
|
||||
if (phynode) {
|
||||
bci->transceiver = devm_usb_get_phy_by_node(
|
||||
bci->dev, phynode, &bci->usb_nb);
|
||||
if (IS_ERR(bci->transceiver) &&
|
||||
PTR_ERR(bci->transceiver) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable interrupts now. */
|
||||
|
@ -656,7 +1073,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
|
|||
TWL4030_INTERRUPTS_BCIIMR1A);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
|
||||
goto fail_unmask_interrupts;
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
|
||||
|
@ -665,8 +1082,23 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
|
|||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
|
||||
|
||||
twl4030_charger_enable_ac(true);
|
||||
twl4030_charger_enable_usb(bci, true);
|
||||
twl4030_charger_update_current(bci);
|
||||
if (device_create_file(&bci->usb->dev, &dev_attr_max_current))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
if (device_create_file(&bci->usb->dev, &dev_attr_mode))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
if (device_create_file(&bci->ac->dev, &dev_attr_mode))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
if (device_create_file(&bci->ac->dev, &dev_attr_max_current))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
|
||||
twl4030_charger_enable_ac(bci, true);
|
||||
if (!IS_ERR_OR_NULL(bci->transceiver))
|
||||
twl4030_bci_usb_ncb(&bci->usb_nb,
|
||||
bci->transceiver->last_event,
|
||||
NULL);
|
||||
else
|
||||
twl4030_charger_enable_usb(bci, false);
|
||||
if (pdata)
|
||||
twl4030_charger_enable_backup(pdata->bb_uvolt,
|
||||
pdata->bb_uamp);
|
||||
|
@ -674,42 +1106,26 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
|
|||
twl4030_charger_enable_backup(0, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_unmask_interrupts:
|
||||
free_irq(bci->irq_bci, bci);
|
||||
fail_bci_irq:
|
||||
free_irq(bci->irq_chg, bci);
|
||||
fail_chg_irq:
|
||||
power_supply_unregister(bci->usb);
|
||||
fail_register_usb:
|
||||
power_supply_unregister(bci->ac);
|
||||
fail_register_ac:
|
||||
fail_no_battery:
|
||||
kfree(bci);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit twl4030_bci_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct twl4030_bci *bci = platform_get_drvdata(pdev);
|
||||
|
||||
twl4030_charger_enable_ac(false);
|
||||
twl4030_charger_enable_ac(bci, false);
|
||||
twl4030_charger_enable_usb(bci, false);
|
||||
twl4030_charger_enable_backup(0, 0);
|
||||
|
||||
device_remove_file(&bci->usb->dev, &dev_attr_max_current);
|
||||
device_remove_file(&bci->usb->dev, &dev_attr_mode);
|
||||
device_remove_file(&bci->ac->dev, &dev_attr_max_current);
|
||||
device_remove_file(&bci->ac->dev, &dev_attr_mode);
|
||||
/* mask interrupts */
|
||||
twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
|
||||
TWL4030_INTERRUPTS_BCIIMR1A);
|
||||
twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
|
||||
TWL4030_INTERRUPTS_BCIIMR2A);
|
||||
|
||||
free_irq(bci->irq_bci, bci);
|
||||
free_irq(bci->irq_chg, bci);
|
||||
power_supply_unregister(bci->usb);
|
||||
power_supply_unregister(bci->ac);
|
||||
kfree(bci);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -720,14 +1136,14 @@ static const struct of_device_id twl_bci_of_match[] = {
|
|||
MODULE_DEVICE_TABLE(of, twl_bci_of_match);
|
||||
|
||||
static struct platform_driver twl4030_bci_driver = {
|
||||
.probe = twl4030_bci_probe,
|
||||
.driver = {
|
||||
.name = "twl4030_bci",
|
||||
.of_match_table = of_match_ptr(twl_bci_of_match),
|
||||
},
|
||||
.remove = __exit_p(twl4030_bci_remove),
|
||||
};
|
||||
|
||||
module_platform_driver_probe(twl4030_bci_driver, twl4030_bci_probe);
|
||||
module_platform_driver(twl4030_bci_driver);
|
||||
|
||||
MODULE_AUTHOR("Gražvydas Ignotas");
|
||||
MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
|
||||
|
|
Loading…
Reference in a new issue