mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-05-24 10:39:52 +00:00
Second batch of AT91 cleanup for 3.18:
- 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 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQEcBAABAgAGBQJUDaNIAAoJEAf03oE53VmQ6lAH/RyGo8yEbupiRy7OQjc/k+Xo LkklJnnLzNSs42Y2GIRQ8XhlZwgVsoSfCWbgJL3M/YVgPzktETRREygOpYN7BN0w ibj3Y2ftQc/94va+QjoUJKcvP4UQULSczTuU0GeEy6XUoPSdiYZNqNRy/O5XdujK w4rTXBlAuVTRv6fppEPAEKcNH6fUW51D3x4/4qI9w0pzLXbvdS0lAjEo1bbbipp7 0PWJmDtkpeGn1wEdVyNkBY2ASEuRqjtWAL41joYGNtrcy43V79ZEn+TkTonfJ02L /qqRyuhHHk+0zOuBIVRlLw5BzddXaqpRXqClM8qdOkDvbz6eL9CcCNCUF3lHcgo= =Ao6T -----END PGP SIGNATURE----- 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: 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
This commit is contained in:
commit
c40c4028f0
4 changed files with 60 additions and 71 deletions
|
@ -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)
|
||||||
|
|
||||||
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)
|
||||||
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);
|
||||||
/**
|
/**
|
||||||
* 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);
|
||||||
* 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)
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
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 {
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
*/
|
*/
|
||||||
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];
|
||||||
#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
|
||||||
|
|
Loading…
Add table
Reference in a new issue