#include <linux/init.h>
#include <linux/time.h>
#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
#include <linux/io.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>
#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
/* In module TWL4030_MODULE_INT */
-#define REG_PWR_ISR1 0x00
-#define REG_PWR_IMR1 0x01
-#define USB_PRES (1 << 2)
#define REG_PWR_EDR1 0x05
#define USB_PRES_FALLING (1 << 4)
#define USB_PRES_RISING (1 << 5)
-#define REG_PWR_SIH_CTRL 0x07
-#define COR (1 << 2)
/* bits in OTG_CTRL */
#define OTG_XCEIV_OUTPUTS \
struct twl4030_usb {
+ struct work_struct irq_work;
struct otg_transceiver otg;
struct device *dev;
+ /* for vbus reporting with irqs disabled */
+ spinlock_t lock;
+
/* pin configuration */
enum twl4030_usb_mode usb_mode;
+
+ unsigned vbus:1;
int irq;
u8 asleep;
+ bool irq_enabled;
};
/* internal define on top of container_of */
}
}
-static void usb_irq_enable(struct twl4030_usb *twl, int rising, int falling)
+static void usb_irq_enable(struct twl4030_usb *twl, int trigger)
{
- u8 val;
-
- /* edge setup */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_EDR1) < 0);
+ set_irq_type(twl->irq, trigger);
- val &= ~(USB_PRES_RISING | USB_PRES_FALLING);
- if (rising)
- val = val | USB_PRES_RISING;
- if (falling)
- val = val | USB_PRES_FALLING;
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_EDR1) < 0);
-
- /* un-mask interrupt */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_IMR1) < 0);
-
- val &= ~USB_PRES;
-
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_IMR1) < 0);
+ if (!twl->irq_enabled) {
+ enable_irq(twl->irq);
+ twl->irq_enabled = true;
+ }
}
static void usb_irq_disable(struct twl4030_usb *twl)
{
- u8 val;
-
- /* undo edge setup */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_EDR1) < 0);
- val &= ~(USB_PRES_RISING | USB_PRES_FALLING);
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_EDR1) < 0);
-
- /* mask interrupt */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_IMR1) < 0);
- val |= USB_PRES;
-
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_IMR1) < 0);
+ if (twl->irq_enabled) {
+ disable_irq(twl->irq);
+ twl->irq_enabled = false;
+ }
}
static void twl4030_phy_power(struct twl4030_usb *twl, int on)
if (!controller_off)
/* enable rising edge interrupt to detect cable attach */
- usb_irq_enable(twl, 1, 0);
+ usb_irq_enable(twl, IRQ_TYPE_EDGE_RISING);
twl4030_phy_power(twl, 0);
twl->asleep = 1;
return;
/* enable falling edge interrupt to detect cable detach */
- usb_irq_enable(twl, 0, 1);
+ usb_irq_enable(twl, IRQ_TYPE_EDGE_FALLING);
twl4030_phy_power(twl, 1);
twl4030_i2c_access(twl, 1);
twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, PROTECT_KEY);
}
+static ssize_t twl4030_usb_vbus_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+ unsigned long flags;
+ int ret = -EINVAL;
+
+ spin_lock_irqsave(&twl->lock, flags);
+ ret = sprintf(buf, "%s\n", twl->vbus ? "on" : "off");
+ spin_unlock_irqrestore(&twl->lock, flags);
+
+ return ret;
+}
+static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
+
+static void twl4030_usb_irq_work(struct work_struct *work)
+{
+ struct twl4030_usb *twl = container_of(work,
+ struct twl4030_usb, irq_work);
+
+ sysfs_notify(&twl->dev->kobj, NULL, "vbus");
+}
+
static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
{
struct twl4030_usb *twl = _twl;
u8 val;
+#ifdef CONFIG_LOCKDEP
+ /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+ * we don't want and can't tolerate. Although it might be
+ * friendlier not to borrow this thread context...
+ */
+ local_irq_enable();
+#endif
+
+ /* FIXME stop accessing PWR_EDR1 ... if nothing else, we
+ * know which edges we told the IRQ to trigger on. And
+ * there seem to be OTG_specific registers and irqs that
+ * provide the right info without guessing like this:
+ * USB_INT_STS, ID_STATUS, STS_HW_CONDITIONS, etc.
+ */
+
/* action based on cable attach or detach */
WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
&val, REG_PWR_EDR1) < 0);
if (val & USB_PRES_RISING) {
twl4030_phy_resume(twl);
twl4030charger_usb_en(1);
+ twl->vbus = 1;
} else {
twl4030charger_usb_en(0);
+ twl->vbus = 0;
twl4030_phy_suspend(twl, 0);
}
+ schedule_work(&twl->irq_work);
return IRQ_HANDLED;
}
struct twl4030_usb_data *pdata = pdev->dev.platform_data;
struct twl4030_usb *twl;
int status;
+ u8 vbus;
twl = kzalloc(sizeof *twl, GFP_KERNEL);
if (!twl)
return -EINVAL;
}
+ WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
+ &vbus, REG_PWR_EDR1) < 0);
+ vbus &= USB_PRES_RISING;
+
twl->dev = &pdev->dev;
- twl->irq = TWL4030_PWRIRQ_USB_PRES;
+ twl->irq = platform_get_irq(pdev, 0);
twl->otg.set_host = twl4030_set_host;
twl->otg.set_peripheral = twl4030_set_peripheral;
twl->otg.set_suspend = twl4030_set_suspend;
twl->usb_mode = pdata->usb_mode;
+ twl->vbus = vbus ? 1 : 0;
- usb_irq_disable(twl);
+ /* init spinlock for workqueue */
+ spin_lock_init(&twl->lock);
+
+ /* init irq workqueue before request_irq */
+ INIT_WORK(&twl->irq_work, twl4030_usb_irq_work);
+
+ twl->irq_enabled = true;
status = request_irq(twl->irq, twl4030_usb_irq, 0, "twl4030_usb", twl);
if (status < 0) {
dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
return status;
}
-
twl4030_usb_ldo_init(twl);
twl4030_phy_power(twl, 1);
twl4030_i2c_access(twl, 1);
platform_set_drvdata(pdev, twl);
dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
+ if (device_create_file(&pdev->dev, &dev_attr_vbus))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+
return 0;
}
usb_irq_disable(twl);
free_irq(twl->irq, twl);
+ device_remove_file(twl->dev, &dev_attr_vbus);
/* set transceiver mode to power on defaults */
twl4030_usb_set_mode(twl, -1);
{
return platform_driver_register(&twl4030_driver);
}
-module_init(twl4030_usb_init);
+subsys_initcall(twl4030_usb_init);
static void __exit twl4030_usb_exit(void)
{