diff options
| author | Arnd Bergmann <arnd@arndb.de> | 2014-09-09 11:03:17 -0400 |
|---|---|---|
| committer | Arnd Bergmann <arnd@arndb.de> | 2014-09-09 11:03:17 -0400 |
| commit | c40c4028f048a077a8950fd95e5d8901f773a63f (patch) | |
| tree | 4d1abd711def0811b3c2af8e3c89322235bb9e10 | |
| parent | 32dc5ca0c10c859e0e4fcc457e7c0a0c2c4b15ae (diff) | |
| parent | d07a1ecdfb96b26dd665b54fee22fc7417b1cb08 (diff) | |
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
| -rw-r--r-- | drivers/clocksource/tcb_clksrc.c | 15 | ||||
| -rw-r--r-- | drivers/misc/atmel_tclib.c | 101 | ||||
| -rw-r--r-- | drivers/pwm/pwm-atmel-tcb.c | 2 | ||||
| -rw-r--r-- | include/linux/atmel_tc.h | 13 |
4 files changed, 60 insertions, 71 deletions
diff --git a/drivers/clocksource/tcb_clksrc.c b/drivers/clocksource/tcb_clksrc.c index a8d7ea14f183..8bdbc45c6dad 100644 --- a/drivers/clocksource/tcb_clksrc.c +++ b/drivers/clocksource/tcb_clksrc.c | |||
| @@ -178,12 +178,6 @@ static irqreturn_t ch2_irq(int irq, void *handle) | |||
| 178 | return IRQ_NONE; | 178 | return IRQ_NONE; |
| 179 | } | 179 | } |
| 180 | 180 | ||
| 181 | static struct irqaction tc_irqaction = { | ||
| 182 | .name = "tc_clkevt", | ||
| 183 | .flags = IRQF_TIMER, | ||
| 184 | .handler = ch2_irq, | ||
| 185 | }; | ||
| 186 | |||
| 187 | static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) | 181 | static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) |
| 188 | { | 182 | { |
| 189 | int ret; | 183 | int ret; |
| @@ -198,15 +192,16 @@ static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) | |||
| 198 | 192 | ||
| 199 | clkevt.regs = tc->regs; | 193 | clkevt.regs = tc->regs; |
| 200 | clkevt.clk = t2_clk; | 194 | clkevt.clk = t2_clk; |
| 201 | tc_irqaction.dev_id = &clkevt; | ||
| 202 | 195 | ||
| 203 | timer_clock = clk32k_divisor_idx; | 196 | timer_clock = clk32k_divisor_idx; |
| 204 | 197 | ||
| 205 | clkevt.clkevt.cpumask = cpumask_of(0); | 198 | clkevt.clkevt.cpumask = cpumask_of(0); |
| 206 | 199 | ||
| 207 | ret = setup_irq(irq, &tc_irqaction); | 200 | ret = request_irq(irq, ch2_irq, IRQF_TIMER, "tc_clkevt", &clkevt); |
| 208 | if (ret) | 201 | if (ret) { |
| 202 | clk_disable_unprepare(t2_clk); | ||
| 209 | return ret; | 203 | return ret; |
| 204 | } | ||
| 210 | 205 | ||
| 211 | clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff); | 206 | clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff); |
| 212 | 207 | ||
| @@ -279,7 +274,7 @@ static int __init tcb_clksrc_init(void) | |||
| 279 | int i; | 274 | int i; |
| 280 | int ret; | 275 | int ret; |
| 281 | 276 | ||
| 282 | tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK, clksrc.name); | 277 | tc = atmel_tc_alloc(CONFIG_ATMEL_TCB_CLKSRC_BLOCK); |
| 283 | if (!tc) { | 278 | if (!tc) { |
| 284 | pr_debug("can't alloc TC for clocksource\n"); | 279 | pr_debug("can't alloc TC for clocksource\n"); |
| 285 | return -ENODEV; | 280 | return -ENODEV; |
diff --git a/drivers/misc/atmel_tclib.c b/drivers/misc/atmel_tclib.c index c8d8e38d0d8a..0ca05c3ec8d6 100644 --- a/drivers/misc/atmel_tclib.c +++ b/drivers/misc/atmel_tclib.c | |||
| @@ -35,60 +35,31 @@ static LIST_HEAD(tc_list); | |||
| 35 | /** | 35 | /** |
| 36 | * atmel_tc_alloc - allocate a specified TC block | 36 | * atmel_tc_alloc - allocate a specified TC block |
| 37 | * @block: which block to allocate | 37 | * @block: which block to allocate |
| 38 | * @name: name to be associated with the iomem resource | ||
| 39 | * | 38 | * |
| 40 | * Caller allocates a block. If it is available, a pointer to a | 39 | * Caller allocates a block. If it is available, a pointer to a |
| 41 | * pre-initialized struct atmel_tc is returned. The caller can access | 40 | * pre-initialized struct atmel_tc is returned. The caller can access |
| 42 | * the registers directly through the "regs" field. | 41 | * the registers directly through the "regs" field. |
| 43 | */ | 42 | */ |
| 44 | struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name) | 43 | struct atmel_tc *atmel_tc_alloc(unsigned block) |
| 45 | { | 44 | { |
| 46 | struct atmel_tc *tc; | 45 | struct atmel_tc *tc; |
| 47 | struct platform_device *pdev = NULL; | 46 | struct platform_device *pdev = NULL; |
| 48 | struct resource *r; | ||
| 49 | size_t size; | ||
| 50 | 47 | ||
| 51 | spin_lock(&tc_list_lock); | 48 | spin_lock(&tc_list_lock); |
| 52 | list_for_each_entry(tc, &tc_list, node) { | 49 | list_for_each_entry(tc, &tc_list, node) { |
| 53 | if (tc->pdev->dev.of_node) { | 50 | if (tc->allocated) |
| 54 | if (of_alias_get_id(tc->pdev->dev.of_node, "tcb") | 51 | continue; |
| 55 | == block) { | 52 | |
| 56 | pdev = tc->pdev; | 53 | if ((tc->pdev->dev.of_node && tc->id == block) || |
| 57 | break; | 54 | (tc->pdev->id == block)) { |
| 58 | } | ||
| 59 | } else if (tc->pdev->id == block) { | ||
| 60 | pdev = tc->pdev; | 55 | pdev = tc->pdev; |
| 56 | tc->allocated = true; | ||
| 61 | break; | 57 | break; |
| 62 | } | 58 | } |
| 63 | } | 59 | } |
| 64 | |||
| 65 | if (!pdev || tc->iomem) | ||
| 66 | goto fail; | ||
| 67 | |||
| 68 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 69 | if (!r) | ||
| 70 | goto fail; | ||
| 71 | |||
| 72 | size = resource_size(r); | ||
| 73 | r = request_mem_region(r->start, size, name); | ||
| 74 | if (!r) | ||
| 75 | goto fail; | ||
| 76 | |||
| 77 | tc->regs = ioremap(r->start, size); | ||
| 78 | if (!tc->regs) | ||
| 79 | goto fail_ioremap; | ||
| 80 | |||
| 81 | tc->iomem = r; | ||
| 82 | |||
| 83 | out: | ||
| 84 | spin_unlock(&tc_list_lock); | 60 | spin_unlock(&tc_list_lock); |
| 85 | return tc; | ||
| 86 | 61 | ||
| 87 | fail_ioremap: | 62 | return pdev ? tc : NULL; |
| 88 | release_mem_region(r->start, size); | ||
| 89 | fail: | ||
| 90 | tc = NULL; | ||
| 91 | goto out; | ||
| 92 | } | 63 | } |
| 93 | EXPORT_SYMBOL_GPL(atmel_tc_alloc); | 64 | EXPORT_SYMBOL_GPL(atmel_tc_alloc); |
| 94 | 65 | ||
| @@ -96,19 +67,14 @@ EXPORT_SYMBOL_GPL(atmel_tc_alloc); | |||
| 96 | * atmel_tc_free - release a specified TC block | 67 | * atmel_tc_free - release a specified TC block |
| 97 | * @tc: Timer/counter block that was returned by atmel_tc_alloc() | 68 | * @tc: Timer/counter block that was returned by atmel_tc_alloc() |
| 98 | * | 69 | * |
| 99 | * This reverses the effect of atmel_tc_alloc(), unmapping the I/O | 70 | * This reverses the effect of atmel_tc_alloc(), invalidating the resource |
| 100 | * registers, invalidating the resource returned by that routine and | 71 | * returned by that routine and making the TC available to other drivers. |
| 101 | * making the TC available to other drivers. | ||
| 102 | */ | 72 | */ |
| 103 | void atmel_tc_free(struct atmel_tc *tc) | 73 | void atmel_tc_free(struct atmel_tc *tc) |
| 104 | { | 74 | { |
| 105 | spin_lock(&tc_list_lock); | 75 | spin_lock(&tc_list_lock); |
| 106 | if (tc->regs) { | 76 | if (tc->allocated) |
| 107 | iounmap(tc->regs); | 77 | tc->allocated = false; |
| 108 | release_mem_region(tc->iomem->start, resource_size(tc->iomem)); | ||
| 109 | tc->regs = NULL; | ||
| 110 | tc->iomem = NULL; | ||
| 111 | } | ||
| 112 | spin_unlock(&tc_list_lock); | 78 | spin_unlock(&tc_list_lock); |
| 113 | } | 79 | } |
| 114 | EXPORT_SYMBOL_GPL(atmel_tc_free); | 80 | EXPORT_SYMBOL_GPL(atmel_tc_free); |
| @@ -142,25 +108,27 @@ static int __init tc_probe(struct platform_device *pdev) | |||
| 142 | struct atmel_tc *tc; | 108 | struct atmel_tc *tc; |
| 143 | struct clk *clk; | 109 | struct clk *clk; |
| 144 | int irq; | 110 | int irq; |
| 145 | 111 | struct resource *r; | |
| 146 | if (!platform_get_resource(pdev, IORESOURCE_MEM, 0)) | 112 | unsigned int i; |
| 147 | return -EINVAL; | ||
| 148 | 113 | ||
| 149 | irq = platform_get_irq(pdev, 0); | 114 | irq = platform_get_irq(pdev, 0); |
| 150 | if (irq < 0) | 115 | if (irq < 0) |
| 151 | return -EINVAL; | 116 | return -EINVAL; |
| 152 | 117 | ||
| 153 | tc = kzalloc(sizeof(struct atmel_tc), GFP_KERNEL); | 118 | tc = devm_kzalloc(&pdev->dev, sizeof(struct atmel_tc), GFP_KERNEL); |
| 154 | if (!tc) | 119 | if (!tc) |
| 155 | return -ENOMEM; | 120 | return -ENOMEM; |
| 156 | 121 | ||
| 157 | tc->pdev = pdev; | 122 | tc->pdev = pdev; |
| 158 | 123 | ||
| 159 | clk = clk_get(&pdev->dev, "t0_clk"); | 124 | clk = devm_clk_get(&pdev->dev, "t0_clk"); |
| 160 | if (IS_ERR(clk)) { | 125 | if (IS_ERR(clk)) |
| 161 | kfree(tc); | 126 | return PTR_ERR(clk); |
| 162 | return -EINVAL; | 127 | |
| 163 | } | 128 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| 129 | tc->regs = devm_ioremap_resource(&pdev->dev, r); | ||
| 130 | if (IS_ERR(tc->regs)) | ||
| 131 | return PTR_ERR(tc->regs); | ||
| 164 | 132 | ||
| 165 | /* Now take SoC information if available */ | 133 | /* Now take SoC information if available */ |
| 166 | if (pdev->dev.of_node) { | 134 | if (pdev->dev.of_node) { |
| @@ -168,13 +136,17 @@ static int __init tc_probe(struct platform_device *pdev) | |||
| 168 | match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node); | 136 | match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node); |
| 169 | if (match) | 137 | if (match) |
| 170 | tc->tcb_config = match->data; | 138 | tc->tcb_config = match->data; |
| 139 | |||
| 140 | tc->id = of_alias_get_id(tc->pdev->dev.of_node, "tcb"); | ||
| 141 | } else { | ||
| 142 | tc->id = pdev->id; | ||
| 171 | } | 143 | } |
| 172 | 144 | ||
| 173 | tc->clk[0] = clk; | 145 | tc->clk[0] = clk; |
| 174 | tc->clk[1] = clk_get(&pdev->dev, "t1_clk"); | 146 | tc->clk[1] = devm_clk_get(&pdev->dev, "t1_clk"); |
| 175 | if (IS_ERR(tc->clk[1])) | 147 | if (IS_ERR(tc->clk[1])) |
| 176 | tc->clk[1] = clk; | 148 | tc->clk[1] = clk; |
| 177 | tc->clk[2] = clk_get(&pdev->dev, "t2_clk"); | 149 | tc->clk[2] = devm_clk_get(&pdev->dev, "t2_clk"); |
| 178 | if (IS_ERR(tc->clk[2])) | 150 | if (IS_ERR(tc->clk[2])) |
| 179 | tc->clk[2] = clk; | 151 | tc->clk[2] = clk; |
| 180 | 152 | ||
| @@ -186,18 +158,33 @@ static int __init tc_probe(struct platform_device *pdev) | |||
| 186 | if (tc->irq[2] < 0) | 158 | if (tc->irq[2] < 0) |
| 187 | tc->irq[2] = irq; | 159 | tc->irq[2] = irq; |
| 188 | 160 | ||
| 161 | for (i = 0; i < 3; i++) | ||
| 162 | writel(ATMEL_TC_ALL_IRQ, tc->regs + ATMEL_TC_REG(i, IDR)); | ||
| 163 | |||
| 189 | spin_lock(&tc_list_lock); | 164 | spin_lock(&tc_list_lock); |
| 190 | list_add_tail(&tc->node, &tc_list); | 165 | list_add_tail(&tc->node, &tc_list); |
| 191 | spin_unlock(&tc_list_lock); | 166 | spin_unlock(&tc_list_lock); |
| 192 | 167 | ||
| 168 | platform_set_drvdata(pdev, tc); | ||
| 169 | |||
| 193 | return 0; | 170 | return 0; |
| 194 | } | 171 | } |
| 195 | 172 | ||
| 173 | static void tc_shutdown(struct platform_device *pdev) | ||
| 174 | { | ||
| 175 | int i; | ||
| 176 | struct atmel_tc *tc = platform_get_drvdata(pdev); | ||
| 177 | |||
| 178 | for (i = 0; i < 3; i++) | ||
| 179 | writel(ATMEL_TC_ALL_IRQ, tc->regs + ATMEL_TC_REG(i, IDR)); | ||
| 180 | } | ||
| 181 | |||
| 196 | static struct platform_driver tc_driver = { | 182 | static struct platform_driver tc_driver = { |
| 197 | .driver = { | 183 | .driver = { |
| 198 | .name = "atmel_tcb", | 184 | .name = "atmel_tcb", |
| 199 | .of_match_table = of_match_ptr(atmel_tcb_dt_ids), | 185 | .of_match_table = of_match_ptr(atmel_tcb_dt_ids), |
| 200 | }, | 186 | }, |
| 187 | .shutdown = tc_shutdown, | ||
| 201 | }; | 188 | }; |
| 202 | 189 | ||
| 203 | static int __init tc_init(void) | 190 | static int __init tc_init(void) |
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index f3dcd02390f1..d56e5b717431 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c | |||
| @@ -379,7 +379,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) | |||
| 379 | return err; | 379 | return err; |
| 380 | } | 380 | } |
| 381 | 381 | ||
| 382 | tc = atmel_tc_alloc(tcblock, "tcb-pwm"); | 382 | tc = atmel_tc_alloc(tcblock); |
| 383 | if (tc == NULL) { | 383 | if (tc == NULL) { |
| 384 | dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); | 384 | dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); |
| 385 | return -ENOMEM; | 385 | return -ENOMEM; |
diff --git a/include/linux/atmel_tc.h b/include/linux/atmel_tc.h index 89a931babecf..b87c1c7c242a 100644 --- a/include/linux/atmel_tc.h +++ b/include/linux/atmel_tc.h | |||
| @@ -44,12 +44,13 @@ struct atmel_tcb_config { | |||
| 44 | /** | 44 | /** |
| 45 | * struct atmel_tc - information about a Timer/Counter Block | 45 | * struct atmel_tc - information about a Timer/Counter Block |
| 46 | * @pdev: physical device | 46 | * @pdev: physical device |
| 47 | * @iomem: resource associated with the I/O register | ||
| 48 | * @regs: mapping through which the I/O registers can be accessed | 47 | * @regs: mapping through which the I/O registers can be accessed |
| 48 | * @id: block id | ||
| 49 | * @tcb_config: configuration data from SoC | 49 | * @tcb_config: configuration data from SoC |
| 50 | * @irq: irq for each of the three channels | 50 | * @irq: irq for each of the three channels |
| 51 | * @clk: internal clock source for each of the three channels | 51 | * @clk: internal clock source for each of the three channels |
| 52 | * @node: list node, for tclib internal use | 52 | * @node: list node, for tclib internal use |
| 53 | * @allocated: if already used, for tclib internal use | ||
| 53 | * | 54 | * |
| 54 | * On some platforms, each TC channel has its own clocks and IRQs, | 55 | * On some platforms, each TC channel has its own clocks and IRQs, |
| 55 | * while on others, all TC channels share the same clock and IRQ. | 56 | * while on others, all TC channels share the same clock and IRQ. |
| @@ -61,15 +62,16 @@ struct atmel_tcb_config { | |||
| 61 | */ | 62 | */ |
| 62 | struct atmel_tc { | 63 | struct atmel_tc { |
| 63 | struct platform_device *pdev; | 64 | struct platform_device *pdev; |
| 64 | struct resource *iomem; | ||
| 65 | void __iomem *regs; | 65 | void __iomem *regs; |
| 66 | int id; | ||
| 66 | const struct atmel_tcb_config *tcb_config; | 67 | const struct atmel_tcb_config *tcb_config; |
| 67 | int irq[3]; | 68 | int irq[3]; |
| 68 | struct clk *clk[3]; | 69 | struct clk *clk[3]; |
| 69 | struct list_head node; | 70 | struct list_head node; |
| 71 | bool allocated; | ||
| 70 | }; | 72 | }; |
| 71 | 73 | ||
| 72 | extern struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name); | 74 | extern struct atmel_tc *atmel_tc_alloc(unsigned block); |
| 73 | extern void atmel_tc_free(struct atmel_tc *tc); | 75 | extern void atmel_tc_free(struct atmel_tc *tc); |
| 74 | 76 | ||
| 75 | /* platform-specific ATMEL_TC_TIMER_CLOCKx divisors (0 means 32KiHz) */ | 77 | /* platform-specific ATMEL_TC_TIMER_CLOCKx divisors (0 means 32KiHz) */ |
| @@ -258,5 +260,10 @@ extern const u8 atmel_tc_divisors[5]; | |||
| 258 | #define ATMEL_TC_LDRAS (1 << 5) /* RA loading */ | 260 | #define ATMEL_TC_LDRAS (1 << 5) /* RA loading */ |
| 259 | #define ATMEL_TC_LDRBS (1 << 6) /* RB loading */ | 261 | #define ATMEL_TC_LDRBS (1 << 6) /* RB loading */ |
| 260 | #define ATMEL_TC_ETRGS (1 << 7) /* external trigger */ | 262 | #define ATMEL_TC_ETRGS (1 << 7) /* external trigger */ |
| 263 | #define ATMEL_TC_ALL_IRQ (ATMEL_TC_COVFS | ATMEL_TC_LOVRS | \ | ||
| 264 | ATMEL_TC_CPAS | ATMEL_TC_CPBS | \ | ||
| 265 | ATMEL_TC_CPCS | ATMEL_TC_LDRAS | \ | ||
| 266 | ATMEL_TC_LDRBS | ATMEL_TC_ETRGS) \ | ||
| 267 | /* all IRQs */ | ||
| 261 | 268 | ||
| 262 | #endif | 269 | #endif |
