#include <linux/power_supply.h>
#include <linux/notifier.h>
#include <linux/usb/otg.h>
+#include <linux/ratelimit.h>
#define TWL4030_BCIMSTATEC 0x02
#define TWL4030_BCIICHG 0x08
#define TWL4030_MSTATEC_COMPLETE1 0x0b
#define TWL4030_MSTATEC_COMPLETE4 0x0e
-static bool allow_usb;
+static bool allow_usb = 1;
module_param(allow_usb, bool, 0644);
MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current");
struct work_struct work;
int irq_chg;
int irq_bci;
+ bool ac_charge_enable;
+ bool usb_charge_enable;
unsigned long event;
+ struct ratelimit_state ratelimit;
};
/*
static int twl4030_clear_set_boot_bci(u8 clear, u8 set)
{
- return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0,
+ return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, clear,
TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set,
TWL4030_PM_MASTER_BOOT_BCI);
}
}
/*
- * Enable/Disable USB Charge funtionality.
+ * Enable/Disable USB Charge functionality.
*/
static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
{
int ret;
if (enable) {
+ if (!bci->usb_charge_enable)
+ return -EACCES;
+
/* Check for USB charger conneted */
if (!twl4030_bci_have_vbus(bci))
return -ENODEV;
}
/* various monitoring events, for now we just log them here */
- if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
+ if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1) &&
+ __ratelimit(&bci->ratelimit))
dev_warn(bci->dev, "battery temperature out of range\n");
- if (irqs1 & TWL4030_BATSTS)
+ if (irqs1 & TWL4030_BATSTS && __ratelimit(&bci->ratelimit))
dev_crit(bci->dev, "battery disconnected\n");
- if (irqs2 & TWL4030_VBATOV)
+ if (irqs2 & TWL4030_VBATOV && __ratelimit(&bci->ratelimit))
dev_crit(bci->dev, "VBAT overvoltage\n");
- if (irqs2 & TWL4030_VBUSOV)
+ if (irqs2 & TWL4030_VBUSOV && __ratelimit(&bci->ratelimit))
dev_crit(bci->dev, "VBUS overvoltage\n");
- if (irqs2 & TWL4030_ACCHGOV)
+ if (irqs2 & TWL4030_ACCHGOV && __ratelimit(&bci->ratelimit))
dev_crit(bci->dev, "Ac charger overvoltage\n");
+ /* ack the interrupts */
+ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, irqs1,
+ TWL4030_INTERRUPTS_BCIISR1A);
+ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, irqs2,
+ TWL4030_INTERRUPTS_BCIISR2A);
+
return IRQ_HANDLED;
}
return ret;
}
+static ssize_t twl4030_bci_ac_show_enable(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 boot_bci;
+ int ret;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci,
+ TWL4030_PM_MASTER_BOOT_BCI);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", (boot_bci & TWL4030_BCIAUTOAC) ? 1 : 0);
+}
+
+static ssize_t twl4030_bci_ac_store_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct twl4030_bci *bci = container_of(psy, struct twl4030_bci, ac);
+ unsigned long enable;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &enable);
+ if (ret || enable > 1)
+ return -EINVAL;
+
+ bci->ac_charge_enable = enable;
+ twl4030_charger_enable_ac(enable);
+
+ return count;
+}
+static const struct device_attribute dev_attr_enable_ac =
+ __ATTR(enable, S_IRUGO | S_IWUSR, twl4030_bci_ac_show_enable,
+ twl4030_bci_ac_store_enable);
+
+static ssize_t twl4030_bci_usb_show_enable(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 boot_bci;
+ int ret;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci,
+ TWL4030_PM_MASTER_BOOT_BCI);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", (boot_bci & TWL4030_BCIAUTOUSB) ? 1 : 0);
+}
+
+static ssize_t twl4030_bci_usb_store_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct twl4030_bci *bci = container_of(psy, struct twl4030_bci, usb);
+ unsigned long enable;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &enable);
+ if (ret || enable > 1)
+ return -EINVAL;
+
+ bci->usb_charge_enable = enable;
+ twl4030_charger_enable_usb(bci, enable);
+
+ return count;
+}
+static const struct device_attribute dev_attr_enable_usb =
+ __ATTR(enable, S_IRUGO | S_IWUSR, twl4030_bci_usb_show_enable,
+ twl4030_bci_usb_store_enable);
+
/*
* Returns the main charge FSM state
* Or < 0 on failure.
static int __init twl4030_bci_probe(struct platform_device *pdev)
{
+ const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data;
struct twl4030_bci *bci;
int ret;
u32 reg;
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "No platform data\n");
+ return -EINVAL;
+ }
+
bci = kzalloc(sizeof(*bci), GFP_KERNEL);
if (bci == NULL)
return -ENOMEM;
platform_set_drvdata(pdev, bci);
+ ratelimit_state_init(&bci->ratelimit, HZ, 2);
+
bci->ac.name = "twl4030_ac";
bci->ac.type = POWER_SUPPLY_TYPE_MAINS;
bci->ac.properties = twl4030_charger_props;
bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props);
bci->ac.get_property = twl4030_bci_get_property;
+ bci->ac.supplied_to = pdata->supplied_to;
+ bci->ac.num_supplicants = pdata->num_supplicants;
ret = power_supply_register(&pdev->dev, &bci->ac);
if (ret) {
bci->usb.properties = twl4030_charger_props;
bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props);
bci->usb.get_property = twl4030_bci_get_property;
+ bci->usb.supplied_to = pdata->supplied_to;
+ bci->usb.num_supplicants = pdata->num_supplicants;
ret = power_supply_register(&pdev->dev, &bci->usb);
if (ret) {
otg_register_notifier(bci->transceiver, &bci->otg_nb);
}
+ ret = device_create_file(bci->ac.dev, &dev_attr_enable_ac);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to create sysfs file: %d\n", ret);
+ goto fail_sysfs1;
+ }
+
+ ret = device_create_file(bci->usb.dev, &dev_attr_enable_usb);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to create sysfs file: %d\n", ret);
+ goto fail_sysfs2;
+ }
+
/* Enable interrupts now. */
reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 |
TWL4030_TBATOR1 | TWL4030_BATSTS);
if (ret < 0)
dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
+ bci->ac_charge_enable = true;
+ bci->usb_charge_enable = true;
twl4030_charger_enable_ac(true);
twl4030_charger_enable_usb(bci, true);
return 0;
fail_unmask_interrupts:
+ device_remove_file(bci->usb.dev, &dev_attr_enable_usb);
+fail_sysfs2:
+ device_remove_file(bci->ac.dev, &dev_attr_enable_ac);
+fail_sysfs1:
if (bci->transceiver != NULL) {
otg_unregister_notifier(bci->transceiver, &bci->otg_nb);
otg_put_transceiver(bci->transceiver);
{
struct twl4030_bci *bci = platform_get_drvdata(pdev);
+ device_remove_file(bci->usb.dev, &dev_attr_enable_usb);
+ device_remove_file(bci->ac.dev, &dev_attr_enable_ac);
+
twl4030_charger_enable_ac(false);
twl4030_charger_enable_usb(bci, false);