@@ -22,6 +22,8 @@
#include <linux/clk-provider.h>
#include <linux/cpu_pm.h>
#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/err.h>
@@ -29,6 +31,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/irqchip/chained_irq.h>
#include <linux/platform_data/dmtimer-omap.h>
#include <clocksource/timer-ti-dm.h>
@@ -46,6 +49,7 @@
/* posted mode types */
#define OMAP_TIMER_NONPOSTED 0x00
#define OMAP_TIMER_POSTED 0x01
+#define OMAP_DM_TIMER_NR_IRQS 3 /* Capture, overflow, match */
/* register offsets with the write pending bit encoded */
#define WPSHIFT 16
@@ -142,6 +146,8 @@ struct dmtimer {
struct platform_device *pdev;
struct list_head node;
struct notifier_block nb;
+ struct irq_chip chip;
+ struct irq_domain *domain;
};
static u32 omap_reserved_systimers;
@@ -286,6 +292,232 @@ static inline void __omap_dm_timer_int_enable(struct dmtimer *timer,
dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, value);
}
+static inline void __omap_dm_timer_int_disable(struct dmtimer *timer,
+ unsigned int mask)
+{
+ u32 l = mask;
+
+ if (timer->revision == 1)
+ l = dmtimer_read(timer, timer->irq_ena) & ~mask;
+
+ dmtimer_write(timer, timer->irq_dis, l);
+ l = dmtimer_read(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
+ dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
+}
+
+/*
+ * Chained interrupt support for input pin event capture for PWM.
+ */
+static void dmtimer_chain_handler(struct irq_desc *desc)
+{
+ struct dmtimer *timer = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip;
+ unsigned int bit;
+ unsigned long l;
+
+ chip = irq_desc_get_chip(desc);
+ chained_irq_enter(chip, desc);
+ l = dmtimer_read(timer, timer->irq_stat);
+
+ for_each_set_bit(bit, &l, OMAP_DM_TIMER_NR_IRQS)
+ generic_handle_domain_irq(timer->domain, bit);
+
+ chained_irq_exit(chip, desc);
+}
+
+static struct lock_class_key dmtimer_lock_class;
+static struct lock_class_key dmtimer_request_class;
+
+static int dmtimer_irqdomain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct dmtimer *timer = d->host_data;
+
+ irq_set_chip_data(irq, timer);
+ irq_set_chip_and_handler(irq, &timer->chip,
+ handle_level_irq);
+ irq_set_lockdep_class(irq, &dmtimer_lock_class,
+ &dmtimer_request_class);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops dmtimer_irqdomain_ops = {
+ .map = dmtimer_irqdomain_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static void dmtimer_irq_mask(struct irq_data *d);
+static void dmtimer_irq_unmask(struct irq_data *d);
+
+static unsigned int dmtimer_irq_startup(struct irq_data *d)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u32 l, val;
+
+ switch (BIT(d->hwirq)) {
+ case OMAP_TIMER_INT_CAPTURE:
+ val = OMAP_TIMER_CTRL_GPOCFG;
+ break;
+ case OMAP_TIMER_INT_OVERFLOW:
+ goto unmask;
+ case OMAP_TIMER_INT_MATCH:
+ val = OMAP_TIMER_CTRL_CE;
+ break;
+ default:
+ return 0;
+ }
+
+ spin_lock_irqsave(&timer->lock, flags);
+ l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+ l |= val;
+ dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+ spin_unlock_irqrestore(&timer->lock, flags);
+
+unmask:
+ dmtimer_irq_unmask(d);
+
+ return 0;
+}
+
+static void dmtimer_irq_shutdown(struct irq_data *d)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u32 l, val;
+
+ switch (BIT(d->hwirq)) {
+ case OMAP_TIMER_INT_CAPTURE:
+ val = OMAP_TIMER_CTRL_GPOCFG;
+ break;
+ case OMAP_TIMER_INT_OVERFLOW:
+ goto mask;
+ case OMAP_TIMER_INT_MATCH:
+ val = OMAP_TIMER_CTRL_CE;
+ break;
+ default:
+ return;
+ }
+
+ spin_lock_irqsave(&timer->lock, flags);
+ l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+ l &= ~val;
+ dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+ spin_unlock_irqrestore(&timer->lock, flags);
+
+mask:
+ dmtimer_irq_mask(d);
+}
+
+static void dmtimer_irq_mask(struct irq_data *d)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+ u32 mask = BIT(d->hwirq);
+
+ __omap_dm_timer_int_disable(timer, mask);
+}
+
+static void dmtimer_irq_unmask(struct irq_data *d)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+ u32 mask = BIT(d->hwirq);
+
+ __omap_dm_timer_int_enable(timer, mask);
+}
+
+static void dmtimer_irq_ack(struct irq_data *d)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+
+ dmtimer_write(timer, timer->irq_stat, BIT(d->hwirq));
+}
+
+static void dmtimer_irq_mask_ack(struct irq_data *d)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+ u32 mask = BIT(d->hwirq);
+
+ __omap_dm_timer_int_disable(timer, mask);
+ dmtimer_write(timer, timer->irq_stat, mask);
+}
+
+static int dmtimer_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u32 l;
+
+ /* TIMER_TCLR register TCM bits match IRQ_TYPE values for edge */
+ switch (type) {
+ case IRQ_TYPE_NONE:
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_EDGE_BOTH:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Configure TCLR register TCM bits */
+ spin_lock_irqsave(&timer->lock, flags);
+ l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+ l &= ~(3 << 8);
+ l |= (type << 8);
+ dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+ spin_unlock_irqrestore(&timer->lock, flags);
+
+ return 0;
+}
+
+static int dmtimer_irq_set_wake(struct irq_data *d, unsigned int state)
+{
+ if (state)
+ dmtimer_irq_unmask(d);
+ else
+ dmtimer_irq_mask(d);
+
+ return 0;
+}
+
+static int dmtimer_register_irqchip(struct dmtimer *timer)
+{
+ struct device *dev = &timer->pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ /* Can be removed if omap1 starts using timer-ti-dm-systimer */
+ if (timer->omap1)
+ return 0;
+
+ timer->chip.name = dev->driver->name;
+ timer->chip.irq_startup = dmtimer_irq_startup;
+ timer->chip.irq_shutdown = dmtimer_irq_shutdown;
+ timer->chip.irq_mask = dmtimer_irq_mask;
+ timer->chip.irq_unmask = dmtimer_irq_unmask;
+ timer->chip.irq_ack = dmtimer_irq_ack;
+ timer->chip.irq_mask_ack = dmtimer_irq_mask_ack;
+ timer->chip.irq_set_type = dmtimer_irq_set_type;
+ timer->chip.irq_set_wake = dmtimer_irq_set_wake;
+
+ irq_set_chained_handler_and_data(timer->irq,
+ dmtimer_chain_handler,
+ timer);
+
+ timer->domain = irq_domain_add_simple(np, OMAP_DM_TIMER_NR_IRQS, 0,
+ &dmtimer_irqdomain_ops,
+ timer);
+ if (!timer->domain) {
+ irq_set_chained_handler(timer->irq, NULL);
+ return -EINVAL;
+ }
+
+ /* Initialize dev for runtime PM, see irq_chip_pm_get() */
+ timer->domain->dev = dev;
+
+ return 0;
+}
+
static inline unsigned int
__omap_dm_timer_read_counter(struct dmtimer *timer)
{
@@ -966,7 +1198,6 @@ static int omap_dm_timer_set_int_disable(struct omap_dm_timer *cookie, u32 mask)
{
struct dmtimer *timer;
struct device *dev;
- u32 l = mask;
int rc;
timer = to_dmtimer(cookie);
@@ -978,12 +1209,7 @@ static int omap_dm_timer_set_int_disable(struct omap_dm_timer *cookie, u32 mask)
if (rc)
return rc;
- if (timer->revision == 1)
- l = dmtimer_read(timer, timer->irq_ena) & ~mask;
-
- dmtimer_write(timer, timer->irq_dis, l);
- l = dmtimer_read(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
- dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
+ __omap_dm_timer_int_disable(timer, mask);
pm_runtime_put_sync(dev);
@@ -1173,6 +1399,12 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
/* Clear timer configuration */
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
+ ret = dmtimer_register_irqchip(timer);
+ if (ret) {
+ dev_err(dev, "irqchip register failed: %i\n", ret);
+ goto err_put;
+ }
+
pm_runtime_put(dev);
}
@@ -1185,6 +1417,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
return 0;
+err_put:
+ pm_runtime_put(dev);
err_disable:
pm_runtime_disable(dev);
return ret;
@@ -1205,15 +1439,20 @@ static int omap_dm_timer_remove(struct platform_device *pdev)
int ret = -EINVAL;
spin_lock_irqsave(&dm_timer_lock, flags);
- list_for_each_entry(timer, &omap_timer_list, node)
+ list_for_each_entry(timer, &omap_timer_list, node) {
if (!strcmp(dev_name(&timer->pdev->dev),
dev_name(&pdev->dev))) {
+ if (timer->domain) {
+ irq_domain_remove(timer->domain);
+ irq_set_chained_handler(timer->irq, NULL);
+ }
if (!(timer->capability & OMAP_TIMER_ALWON))
cpu_pm_unregister_notifier(&timer->nb);
list_del(&timer->node);
ret = 0;
break;
}
+ }
spin_unlock_irqrestore(&dm_timer_lock, flags);
pm_runtime_disable(&pdev->dev);
@@ -35,6 +35,7 @@
#include <linux/clk.h>
#include <linux/err.h>
+#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>