#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;
-module_param(allow_usb, bool, 1);
+static bool allow_usb = 1;
+module_param(allow_usb, bool, 0644);
MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current");
struct twl4030_bci {
struct power_supply usb;
struct otg_transceiver *transceiver;
struct notifier_block otg_nb;
+ struct work_struct work;
int irq_chg;
int irq_bci;
+
+ unsigned long event;
+ struct ratelimit_state ratelimit;
};
/*
}
/* 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;
}
-static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
- void *priv)
+static void twl4030_bci_usb_work(struct work_struct *data)
{
- struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, otg_nb);
+ struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
- dev_dbg(bci->dev, "OTG notify %lu\n", val);
-
- switch (val) {
+ switch (bci->event) {
case USB_EVENT_VBUS:
case USB_EVENT_CHARGER:
twl4030_charger_enable_usb(bci, true);
twl4030_charger_enable_usb(bci, false);
break;
}
+}
+
+static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
+ void *priv)
+{
+ struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, otg_nb);
+
+ dev_dbg(bci->dev, "OTG notify %lu\n", val);
+
+ bci->event = val;
+ schedule_work(&bci->work);
return NOTIFY_OK;
}
{
struct twl4030_bci *bci;
int ret;
- int reg;
+ u32 reg;
bci = kzalloc(sizeof(*bci), GFP_KERNEL);
if (bci == NULL)
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;
goto fail_bci_irq;
}
+ INIT_WORK(&bci->work, twl4030_bci_usb_work);
+
bci->transceiver = otg_get_transceiver();
if (bci->transceiver != NULL) {
bci->otg_nb.notifier_call = twl4030_bci_usb_ncb;
}
/* Enable interrupts now. */
- reg = ~(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 |
+ reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 |
TWL4030_TBATOR1 | TWL4030_BATSTS);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
TWL4030_INTERRUPTS_BCIIMR1A);
goto fail_unmask_interrupts;
}
- reg = ~(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
+ reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
TWL4030_INTERRUPTS_BCIIMR2A);
if (ret < 0)
}
module_exit(twl4030_bci_exit);
-MODULE_AUTHOR("Gražydas Ignotas");
+MODULE_AUTHOR("Gražvydas Ignotas");
MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:twl4030_bci");