Skip to content

Commit

Permalink
Merge tag 'at91-cleanup2' of git://github.com/at91linux/linux-at91 in…
Browse files Browse the repository at this point in the history
…to 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: Arnd 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
  • Loading branch information
Arnd Bergmann committed Sep 9, 2014
2 parents 32dc5ca + d07a1ec commit c40c402
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 71 deletions.
15 changes: 5 additions & 10 deletions drivers/clocksource/tcb_clksrc.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,6 @@ static irqreturn_t ch2_irq(int irq, void *handle)
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)
{
int ret;
Expand All @@ -198,15 +192,16 @@ static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx)

clkevt.regs = tc->regs;
clkevt.clk = t2_clk;
tc_irqaction.dev_id = &clkevt;

timer_clock = clk32k_divisor_idx;

clkevt.clkevt.cpumask = cpumask_of(0);

ret = setup_irq(irq, &tc_irqaction);
if (ret)
ret = request_irq(irq, ch2_irq, IRQF_TIMER, "tc_clkevt", &clkevt);
if (ret) {
clk_disable_unprepare(t2_clk);
return ret;
}

clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff);

Expand Down Expand Up @@ -279,7 +274,7 @@ static int __init tcb_clksrc_init(void)
int i;
int ret;

tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK, clksrc.name);
tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK);
if (!tc) {
pr_debug("can't alloc TC for clocksource\n");
return -ENODEV;
Expand Down
101 changes: 44 additions & 57 deletions drivers/misc/atmel_tclib.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,80 +35,46 @@ static LIST_HEAD(tc_list);
/**
* atmel_tc_alloc - allocate a specified TC block
* @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
* pre-initialized struct atmel_tc is returned. The caller can access
* 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 platform_device *pdev = NULL;
struct resource *r;
size_t size;

spin_lock(&tc_list_lock);
list_for_each_entry(tc, &tc_list, node) {
if (tc->pdev->dev.of_node) {
if (of_alias_get_id(tc->pdev->dev.of_node, "tcb")
== block) {
pdev = tc->pdev;
break;
}
} else if (tc->pdev->id == block) {
if (tc->allocated)
continue;

if ((tc->pdev->dev.of_node && tc->id == block) ||
(tc->pdev->id == block)) {
pdev = tc->pdev;
tc->allocated = true;
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);
return tc;

fail_ioremap:
release_mem_region(r->start, size);
fail:
tc = NULL;
goto out;
return pdev ? tc : NULL;
}
EXPORT_SYMBOL_GPL(atmel_tc_alloc);

/**
* atmel_tc_free - release a specified TC block
* @tc: Timer/counter block that was returned by atmel_tc_alloc()
*
* This reverses the effect of atmel_tc_alloc(), unmapping the I/O
* registers, invalidating the resource returned by that routine and
* making the TC available to other drivers.
* This reverses the effect of atmel_tc_alloc(), invalidating the resource
* returned by that routine and making the TC available to other drivers.
*/
void atmel_tc_free(struct atmel_tc *tc)
{
spin_lock(&tc_list_lock);
if (tc->regs) {
iounmap(tc->regs);
release_mem_region(tc->iomem->start, resource_size(tc->iomem));
tc->regs = NULL;
tc->iomem = NULL;
}
if (tc->allocated)
tc->allocated = false;
spin_unlock(&tc_list_lock);
}
EXPORT_SYMBOL_GPL(atmel_tc_free);
Expand Down Expand Up @@ -142,39 +108,45 @@ static int __init tc_probe(struct platform_device *pdev)
struct atmel_tc *tc;
struct clk *clk;
int irq;

if (!platform_get_resource(pdev, IORESOURCE_MEM, 0))
return -EINVAL;
struct resource *r;
unsigned int i;

irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -EINVAL;

tc = kzalloc(sizeof(struct atmel_tc), GFP_KERNEL);
tc = devm_kzalloc(&pdev->dev, sizeof(struct atmel_tc), GFP_KERNEL);
if (!tc)
return -ENOMEM;

tc->pdev = pdev;

clk = clk_get(&pdev->dev, "t0_clk");
if (IS_ERR(clk)) {
kfree(tc);
return -EINVAL;
}
clk = devm_clk_get(&pdev->dev, "t0_clk");
if (IS_ERR(clk))
return PTR_ERR(clk);

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 */
if (pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node);
if (match)
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[1] = clk_get(&pdev->dev, "t1_clk");
tc->clk[1] = devm_clk_get(&pdev->dev, "t1_clk");
if (IS_ERR(tc->clk[1]))
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]))
tc->clk[2] = clk;

Expand All @@ -186,18 +158,33 @@ static int __init tc_probe(struct platform_device *pdev)
if (tc->irq[2] < 0)
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);
list_add_tail(&tc->node, &tc_list);
spin_unlock(&tc_list_lock);

platform_set_drvdata(pdev, tc);

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 = {
.driver = {
.name = "atmel_tcb",
.of_match_table = of_match_ptr(atmel_tcb_dt_ids),
},
.shutdown = tc_shutdown,
};

static int __init tc_init(void)
Expand Down
2 changes: 1 addition & 1 deletion drivers/pwm/pwm-atmel-tcb.c
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
return err;
}

tc = atmel_tc_alloc(tcblock, "tcb-pwm");
tc = atmel_tc_alloc(tcblock);
if (tc == NULL) {
dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n");
return -ENOMEM;
Expand Down
13 changes: 10 additions & 3 deletions include/linux/atmel_tc.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ struct atmel_tcb_config {
/**
* struct atmel_tc - information about a Timer/Counter Block
* @pdev: physical device
* @iomem: resource associated with the I/O register
* @regs: mapping through which the I/O registers can be accessed
* @id: block id
* @tcb_config: configuration data from SoC
* @irq: irq for each of the three channels
* @clk: internal clock source for each of the three channels
* @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,
* while on others, all TC channels share the same clock and IRQ.
Expand All @@ -61,15 +62,16 @@ struct atmel_tcb_config {
*/
struct atmel_tc {
struct platform_device *pdev;
struct resource *iomem;
void __iomem *regs;
int id;
const struct atmel_tcb_config *tcb_config;
int irq[3];
struct clk *clk[3];
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);

/* platform-specific ATMEL_TC_TIMER_CLOCKx divisors (0 means 32KiHz) */
Expand Down Expand Up @@ -258,5 +260,10 @@ extern const u8 atmel_tc_divisors[5];
#define ATMEL_TC_LDRAS (1 << 5) /* RA loading */
#define ATMEL_TC_LDRBS (1 << 6) /* RB loading */
#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

0 comments on commit c40c402

Please sign in to comment.