Commit c40c4028 authored by Arnd Bergmann's avatar Arnd Bergmann

Merge tag 'at91-cleanup2' of git://github.com/at91linux/linux-at91 into next/cleanup

Pull "Second batch of AT91 cleanup for 3.18" from Nicolas Ferre:
- Timer Counter (TC) fixup and cleanup:
  - fix segmentation fault when kexec-ing a kernel by masking
    TC interrupts at shutdown and probe time
  - use modern driver model: devm_*, probe function, sanitize IRQ request
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>

* tag 'at91-cleanup2' of git://github.com/at91linux/linux-at91:
  clocksource: tcb_clksrc: sanitize IRQ request
  ARM: at91/tclib: mask interruptions at shutdown and probe
  ARM: at91/tclib: move initialization from alloc to probe
  ARM: at91/tclib: prefer using of devm_* functions
parents 32dc5ca0 d07a1ecd
...@@ -178,12 +178,6 @@ static irqreturn_t ch2_irq(int irq, void *handle) ...@@ -178,12 +178,6 @@ static irqreturn_t ch2_irq(int irq, void *handle)
return IRQ_NONE; return IRQ_NONE;
} }
static struct irqaction tc_irqaction = {
.name = "tc_clkevt",
.flags = IRQF_TIMER,
.handler = ch2_irq,
};
static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx)
{ {
int ret; int ret;
...@@ -198,15 +192,16 @@ static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) ...@@ -198,15 +192,16 @@ static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx)
clkevt.regs = tc->regs; clkevt.regs = tc->regs;
clkevt.clk = t2_clk; clkevt.clk = t2_clk;
tc_irqaction.dev_id = &clkevt;
timer_clock = clk32k_divisor_idx; timer_clock = clk32k_divisor_idx;
clkevt.clkevt.cpumask = cpumask_of(0); clkevt.clkevt.cpumask = cpumask_of(0);
ret = setup_irq(irq, &tc_irqaction); ret = request_irq(irq, ch2_irq, IRQF_TIMER, "tc_clkevt", &clkevt);
if (ret) if (ret) {
clk_disable_unprepare(t2_clk);
return ret; return ret;
}
clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff); clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff);
...@@ -279,7 +274,7 @@ static int __init tcb_clksrc_init(void) ...@@ -279,7 +274,7 @@ static int __init tcb_clksrc_init(void)
int i; int i;
int ret; int ret;
tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK, clksrc.name); tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK);
if (!tc) { if (!tc) {
pr_debug("can't alloc TC for clocksource\n"); pr_debug("can't alloc TC for clocksource\n");
return -ENODEV; return -ENODEV;
......
...@@ -35,60 +35,31 @@ static LIST_HEAD(tc_list); ...@@ -35,60 +35,31 @@ static LIST_HEAD(tc_list);
/** /**
* atmel_tc_alloc - allocate a specified TC block * atmel_tc_alloc - allocate a specified TC block
* @block: which block to allocate * @block: which block to allocate
* @name: name to be associated with the iomem resource
* *
* Caller allocates a block. If it is available, a pointer to a * Caller allocates a block. If it is available, a pointer to a
* pre-initialized struct atmel_tc is returned. The caller can access * pre-initialized struct atmel_tc is returned. The caller can access
* the registers directly through the "regs" field. * the registers directly through the "regs" field.
*/ */
struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name) struct atmel_tc *atmel_tc_alloc(unsigned block)
{ {
struct atmel_tc *tc; struct atmel_tc *tc;
struct platform_device *pdev = NULL; struct platform_device *pdev = NULL;
struct resource *r;
size_t size;
spin_lock(&tc_list_lock); spin_lock(&tc_list_lock);
list_for_each_entry(tc, &tc_list, node) { list_for_each_entry(tc, &tc_list, node) {
if (tc->pdev->dev.of_node) { if (tc->allocated)
if (of_alias_get_id(tc->pdev->dev.of_node, "tcb") continue;
== block) {
pdev = tc->pdev; if ((tc->pdev->dev.of_node && tc->id == block) ||
break; (tc->pdev->id == block)) {
}
} else if (tc->pdev->id == block) {
pdev = tc->pdev; pdev = tc->pdev;
tc->allocated = true;
break; break;
} }
} }
if (!pdev || tc->iomem)
goto fail;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r)
goto fail;
size = resource_size(r);
r = request_mem_region(r->start, size, name);
if (!r)
goto fail;
tc->regs = ioremap(r->start, size);
if (!tc->regs)
goto fail_ioremap;
tc->iomem = r;
out:
spin_unlock(&tc_list_lock); spin_unlock(&tc_list_lock);
return tc;
fail_ioremap: return pdev ? tc : NULL;
release_mem_region(r->start, size);
fail:
tc = NULL;
goto out;
} }
EXPORT_SYMBOL_GPL(atmel_tc_alloc); EXPORT_SYMBOL_GPL(atmel_tc_alloc);
...@@ -96,19 +67,14 @@ EXPORT_SYMBOL_GPL(atmel_tc_alloc); ...@@ -96,19 +67,14 @@ EXPORT_SYMBOL_GPL(atmel_tc_alloc);
* atmel_tc_free - release a specified TC block * atmel_tc_free - release a specified TC block
* @tc: Timer/counter block that was returned by atmel_tc_alloc() * @tc: Timer/counter block that was returned by atmel_tc_alloc()
* *
* This reverses the effect of atmel_tc_alloc(), unmapping the I/O * This reverses the effect of atmel_tc_alloc(), invalidating the resource
* registers, invalidating the resource returned by that routine and * returned by that routine and making the TC available to other drivers.
* making the TC available to other drivers.
*/ */
void atmel_tc_free(struct atmel_tc *tc) void atmel_tc_free(struct atmel_tc *tc)
{ {
spin_lock(&tc_list_lock); spin_lock(&tc_list_lock);
if (tc->regs) { if (tc->allocated)
iounmap(tc->regs); tc->allocated = false;
release_mem_region(tc->iomem->start, resource_size(tc->iomem));
tc->regs = NULL;
tc->iomem = NULL;
}
spin_unlock(&tc_list_lock); spin_unlock(&tc_list_lock);
} }
EXPORT_SYMBOL_GPL(atmel_tc_free); EXPORT_SYMBOL_GPL(atmel_tc_free);
...@@ -142,25 +108,27 @@ static int __init tc_probe(struct platform_device *pdev) ...@@ -142,25 +108,27 @@ static int __init tc_probe(struct platform_device *pdev)
struct atmel_tc *tc; struct atmel_tc *tc;
struct clk *clk; struct clk *clk;
int irq; int irq;
struct resource *r;
if (!platform_get_resource(pdev, IORESOURCE_MEM, 0)) unsigned int i;
return -EINVAL;
irq = platform_get_irq(pdev, 0); irq = platform_get_irq(pdev, 0);
if (irq < 0) if (irq < 0)
return -EINVAL; return -EINVAL;
tc = kzalloc(sizeof(struct atmel_tc), GFP_KERNEL); tc = devm_kzalloc(&pdev->dev, sizeof(struct atmel_tc), GFP_KERNEL);
if (!tc) if (!tc)
return -ENOMEM; return -ENOMEM;
tc->pdev = pdev; tc->pdev = pdev;
clk = clk_get(&pdev->dev, "t0_clk"); clk = devm_clk_get(&pdev->dev, "t0_clk");
if (IS_ERR(clk)) { if (IS_ERR(clk))
kfree(tc); return PTR_ERR(clk);
return -EINVAL;
} r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tc->regs = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(tc->regs))
return PTR_ERR(tc->regs);
/* Now take SoC information if available */ /* Now take SoC information if available */
if (pdev->dev.of_node) { if (pdev->dev.of_node) {
...@@ -168,13 +136,17 @@ static int __init tc_probe(struct platform_device *pdev) ...@@ -168,13 +136,17 @@ static int __init tc_probe(struct platform_device *pdev)
match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node); match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node);
if (match) if (match)
tc->tcb_config = match->data; tc->tcb_config = match->data;
tc->id = of_alias_get_id(tc->pdev->dev.of_node, "tcb");
} else {
tc->id = pdev->id;
} }
tc->clk[0] = clk; tc->clk[0] = clk;
tc->clk[1] = clk_get(&pdev->dev, "t1_clk"); tc->clk[1] = devm_clk_get(&pdev->dev, "t1_clk");
if (IS_ERR(tc->clk[1])) if (IS_ERR(tc->clk[1]))
tc->clk[1] = clk; tc->clk[1] = clk;
tc->clk[2] = clk_get(&pdev->dev, "t2_clk"); tc->clk[2] = devm_clk_get(&pdev->dev, "t2_clk");
if (IS_ERR(tc->clk[2])) if (IS_ERR(tc->clk[2]))
tc->clk[2] = clk; tc->clk[2] = clk;
...@@ -186,18 +158,33 @@ static int __init tc_probe(struct platform_device *pdev) ...@@ -186,18 +158,33 @@ static int __init tc_probe(struct platform_device *pdev)
if (tc->irq[2] < 0) if (tc->irq[2] < 0)
tc->irq[2] = irq; tc->irq[2] = irq;
for (i = 0; i < 3; i++)
writel(ATMEL_TC_ALL_IRQ, tc->regs + ATMEL_TC_REG(i, IDR));
spin_lock(&tc_list_lock); spin_lock(&tc_list_lock);
list_add_tail(&tc->node, &tc_list); list_add_tail(&tc->node, &tc_list);
spin_unlock(&tc_list_lock); spin_unlock(&tc_list_lock);
platform_set_drvdata(pdev, tc);
return 0; return 0;
} }
static void tc_shutdown(struct platform_device *pdev)
{
int i;
struct atmel_tc *tc = platform_get_drvdata(pdev);
for (i = 0; i < 3; i++)
writel(ATMEL_TC_ALL_IRQ, tc->regs + ATMEL_TC_REG(i, IDR));
}
static struct platform_driver tc_driver = { static struct platform_driver tc_driver = {
.driver = { .driver = {
.name = "atmel_tcb", .name = "atmel_tcb",
.of_match_table = of_match_ptr(atmel_tcb_dt_ids), .of_match_table = of_match_ptr(atmel_tcb_dt_ids),
}, },
.shutdown = tc_shutdown,
}; };
static int __init tc_init(void) static int __init tc_init(void)
......
...@@ -379,7 +379,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) ...@@ -379,7 +379,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
return err; return err;
} }
tc = atmel_tc_alloc(tcblock, "tcb-pwm"); tc = atmel_tc_alloc(tcblock);
if (tc == NULL) { if (tc == NULL) {
dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n");
return -ENOMEM; return -ENOMEM;
......
...@@ -44,12 +44,13 @@ struct atmel_tcb_config { ...@@ -44,12 +44,13 @@ struct atmel_tcb_config {
/** /**
* struct atmel_tc - information about a Timer/Counter Block * struct atmel_tc - information about a Timer/Counter Block
* @pdev: physical device * @pdev: physical device
* @iomem: resource associated with the I/O register
* @regs: mapping through which the I/O registers can be accessed * @regs: mapping through which the I/O registers can be accessed
* @id: block id
* @tcb_config: configuration data from SoC * @tcb_config: configuration data from SoC
* @irq: irq for each of the three channels * @irq: irq for each of the three channels
* @clk: internal clock source for each of the three channels * @clk: internal clock source for each of the three channels
* @node: list node, for tclib internal use * @node: list node, for tclib internal use
* @allocated: if already used, for tclib internal use
* *
* On some platforms, each TC channel has its own clocks and IRQs, * On some platforms, each TC channel has its own clocks and IRQs,
* while on others, all TC channels share the same clock and IRQ. * while on others, all TC channels share the same clock and IRQ.
...@@ -61,15 +62,16 @@ struct atmel_tcb_config { ...@@ -61,15 +62,16 @@ struct atmel_tcb_config {
*/ */
struct atmel_tc { struct atmel_tc {
struct platform_device *pdev; struct platform_device *pdev;
struct resource *iomem;
void __iomem *regs; void __iomem *regs;
int id;
const struct atmel_tcb_config *tcb_config; const struct atmel_tcb_config *tcb_config;
int irq[3]; int irq[3];
struct clk *clk[3]; struct clk *clk[3];
struct list_head node; struct list_head node;
bool allocated;
}; };
extern struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name); extern struct atmel_tc *atmel_tc_alloc(unsigned block);
extern void atmel_tc_free(struct atmel_tc *tc); extern void atmel_tc_free(struct atmel_tc *tc);
/* platform-specific ATMEL_TC_TIMER_CLOCKx divisors (0 means 32KiHz) */ /* platform-specific ATMEL_TC_TIMER_CLOCKx divisors (0 means 32KiHz) */
...@@ -258,5 +260,10 @@ extern const u8 atmel_tc_divisors[5]; ...@@ -258,5 +260,10 @@ extern const u8 atmel_tc_divisors[5];
#define ATMEL_TC_LDRAS (1 << 5) /* RA loading */ #define ATMEL_TC_LDRAS (1 << 5) /* RA loading */
#define ATMEL_TC_LDRBS (1 << 6) /* RB loading */ #define ATMEL_TC_LDRBS (1 << 6) /* RB loading */
#define ATMEL_TC_ETRGS (1 << 7) /* external trigger */ #define ATMEL_TC_ETRGS (1 << 7) /* external trigger */
#define ATMEL_TC_ALL_IRQ (ATMEL_TC_COVFS | ATMEL_TC_LOVRS | \
ATMEL_TC_CPAS | ATMEL_TC_CPBS | \
ATMEL_TC_CPCS | ATMEL_TC_LDRAS | \
ATMEL_TC_LDRBS | ATMEL_TC_ETRGS) \
/* all IRQs */
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment