1/* 2 * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> 3 * JZ4740 SoC ADC driver 4 * 5 * This program is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License as published by the 7 * Free Software Foundation; either version 2 of the License, or (at your 8 * option) any later version. 9 * 10 * You should have received a copy of the GNU General Public License along 11 * with this program; if not, write to the Free Software Foundation, Inc., 12 * 675 Mass Ave, Cambridge, MA 02139, USA. 13 * 14 * This driver synchronizes access to the JZ4740 ADC core between the 15 * JZ4740 battery and hwmon drivers. 16 */ 17 18#include <linux/err.h> 19#include <linux/irq.h> 20#include <linux/interrupt.h> 21#include <linux/kernel.h> 22#include <linux/module.h> 23#include <linux/platform_device.h> 24#include <linux/slab.h> 25#include <linux/spinlock.h> 26 27#include <linux/clk.h> 28#include <linux/mfd/core.h> 29 30#include <linux/jz4740-adc.h> 31 32 33#define JZ_REG_ADC_ENABLE 0x00 34#define JZ_REG_ADC_CFG 0x04 35#define JZ_REG_ADC_CTRL 0x08 36#define JZ_REG_ADC_STATUS 0x0c 37 38#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10 39#define JZ_REG_ADC_BATTERY_BASE 0x1c 40#define JZ_REG_ADC_HWMON_BASE 0x20 41 42#define JZ_ADC_ENABLE_TOUCH BIT(2) 43#define JZ_ADC_ENABLE_BATTERY BIT(1) 44#define JZ_ADC_ENABLE_ADCIN BIT(0) 45 46enum { 47 JZ_ADC_IRQ_ADCIN = 0, 48 JZ_ADC_IRQ_BATTERY, 49 JZ_ADC_IRQ_TOUCH, 50 JZ_ADC_IRQ_PENUP, 51 JZ_ADC_IRQ_PENDOWN, 52}; 53 54struct jz4740_adc { 55 struct resource *mem; 56 void __iomem *base; 57 58 int irq; 59 int irq_base; 60 61 struct clk *clk; 62 atomic_t clk_ref; 63 64 spinlock_t lock; 65}; 66 67static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq, 68 bool masked) 69{ 70 unsigned long flags; 71 uint8_t val; 72 73 irq -= adc->irq_base; 74 75 spin_lock_irqsave(&adc->lock, flags); 76 77 val = readb(adc->base + JZ_REG_ADC_CTRL); 78 if (masked) 79 val |= BIT(irq); 80 else 81 val &= ~BIT(irq); 82 writeb(val, adc->base + JZ_REG_ADC_CTRL); 83 84 spin_unlock_irqrestore(&adc->lock, flags); 85} 86 87static void jz4740_adc_irq_mask(unsigned int irq) 88{ 89 struct jz4740_adc *adc = get_irq_chip_data(irq); 90 jz4740_adc_irq_set_masked(adc, irq, true); 91} 92 93static void jz4740_adc_irq_unmask(unsigned int irq) 94{ 95 struct jz4740_adc *adc = get_irq_chip_data(irq); 96 jz4740_adc_irq_set_masked(adc, irq, false); 97} 98 99static void jz4740_adc_irq_ack(unsigned int irq) 100{ 101 struct jz4740_adc *adc = get_irq_chip_data(irq); 102 103 irq -= adc->irq_base; 104 writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS); 105} 106 107static struct irq_chip jz4740_adc_irq_chip = { 108 .name = "jz4740-adc", 109 .mask = jz4740_adc_irq_mask, 110 .unmask = jz4740_adc_irq_unmask, 111 .ack = jz4740_adc_irq_ack, 112}; 113 114static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc) 115{ 116 struct jz4740_adc *adc = get_irq_desc_data(desc); 117 uint8_t status; 118 unsigned int i; 119 120 status = readb(adc->base + JZ_REG_ADC_STATUS); 121 122 for (i = 0; i < 5; ++i) { 123 if (status & BIT(i)) 124 generic_handle_irq(adc->irq_base + i); 125 } 126} 127 128 129/* Refcounting for the ADC clock is done in here instead of in the clock 130 * framework, because it is the only clock which is shared between multiple 131 * devices and thus is the only clock which needs refcounting */ 132static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc) 133{ 134 if (atomic_inc_return(&adc->clk_ref) == 1) 135 clk_enable(adc->clk); 136} 137 138static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc) 139{ 140 if (atomic_dec_return(&adc->clk_ref) == 0) 141 clk_disable(adc->clk); 142} 143 144static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine, 145 bool enabled) 146{ 147 unsigned long flags; 148 uint8_t val; 149 150 spin_lock_irqsave(&adc->lock, flags); 151 152 val = readb(adc->base + JZ_REG_ADC_ENABLE); 153 if (enabled) 154 val |= BIT(engine); 155 else 156 val &= BIT(engine); 157 writeb(val, adc->base + JZ_REG_ADC_ENABLE); 158 159 spin_unlock_irqrestore(&adc->lock, flags); 160} 161 162static int jz4740_adc_cell_enable(struct platform_device *pdev) 163{ 164 struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent); 165 166 jz4740_adc_clk_enable(adc); 167 jz4740_adc_set_enabled(adc, pdev->id, true); 168 169 return 0; 170} 171 172static int jz4740_adc_cell_disable(struct platform_device *pdev) 173{ 174 struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent); 175 176 jz4740_adc_set_enabled(adc, pdev->id, false); 177 jz4740_adc_clk_disable(adc); 178 179 return 0; 180} 181 182int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val) 183{ 184 struct jz4740_adc *adc = dev_get_drvdata(dev); 185 unsigned long flags; 186 uint32_t cfg; 187 188 if (!adc) 189 return -ENODEV; 190 191 spin_lock_irqsave(&adc->lock, flags); 192 193 cfg = readl(adc->base + JZ_REG_ADC_CFG); 194 195 cfg &= ~mask; 196 cfg |= val; 197 198 writel(cfg, adc->base + JZ_REG_ADC_CFG); 199 200 spin_unlock_irqrestore(&adc->lock, flags); 201 202 return 0; 203} 204EXPORT_SYMBOL_GPL(jz4740_adc_set_config); 205 206static struct resource jz4740_hwmon_resources[] = { 207 { 208 .start = JZ_ADC_IRQ_ADCIN, 209 .flags = IORESOURCE_IRQ, 210 }, 211 { 212 .start = JZ_REG_ADC_HWMON_BASE, 213 .end = JZ_REG_ADC_HWMON_BASE + 3, 214 .flags = IORESOURCE_MEM, 215 }, 216}; 217 218static struct resource jz4740_battery_resources[] = { 219 { 220 .start = JZ_ADC_IRQ_BATTERY, 221 .flags = IORESOURCE_IRQ, 222 }, 223 { 224 .start = JZ_REG_ADC_BATTERY_BASE, 225 .end = JZ_REG_ADC_BATTERY_BASE + 3, 226 .flags = IORESOURCE_MEM, 227 }, 228}; 229 230const struct mfd_cell jz4740_adc_cells[] = { 231 { 232 .id = 0, 233 .name = "jz4740-hwmon", 234 .num_resources = ARRAY_SIZE(jz4740_hwmon_resources), 235 .resources = jz4740_hwmon_resources, 236 .platform_data = (void *)&jz4740_adc_cells[0], 237 .data_size = sizeof(struct mfd_cell), 238 239 .enable = jz4740_adc_cell_enable, 240 .disable = jz4740_adc_cell_disable, 241 }, 242 { 243 .id = 1, 244 .name = "jz4740-battery", 245 .num_resources = ARRAY_SIZE(jz4740_battery_resources), 246 .resources = jz4740_battery_resources, 247 .platform_data = (void *)&jz4740_adc_cells[1], 248 .data_size = sizeof(struct mfd_cell), 249 250 .enable = jz4740_adc_cell_enable, 251 .disable = jz4740_adc_cell_disable, 252 }, 253}; 254 255static int __devinit jz4740_adc_probe(struct platform_device *pdev) 256{ 257 int ret; 258 struct jz4740_adc *adc; 259 struct resource *mem_base; 260 int irq; 261 262 adc = kmalloc(sizeof(*adc), GFP_KERNEL); 263 if (!adc) { 264 dev_err(&pdev->dev, "Failed to allocate driver structure\n"); 265 return -ENOMEM; 266 } 267 268 adc->irq = platform_get_irq(pdev, 0); 269 if (adc->irq < 0) { 270 ret = adc->irq; 271 dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); 272 goto err_free; 273 } 274 275 adc->irq_base = platform_get_irq(pdev, 1); 276 if (adc->irq_base < 0) { 277 ret = adc->irq_base; 278 dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret); 279 goto err_free; 280 } 281 282 mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); 283 if (!mem_base) { 284 ret = -ENOENT; 285 dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); 286 goto err_free; 287 } 288 289 /* Only request the shared registers for the MFD driver */ 290 adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS, 291 pdev->name); 292 if (!adc->mem) { 293 ret = -EBUSY; 294 dev_err(&pdev->dev, "Failed to request mmio memory region\n"); 295 goto err_free; 296 } 297 298 adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem)); 299 if (!adc->base) { 300 ret = -EBUSY; 301 dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); 302 goto err_release_mem_region; 303 } 304 305 adc->clk = clk_get(&pdev->dev, "adc"); 306 if (IS_ERR(adc->clk)) { 307 ret = PTR_ERR(adc->clk); 308 dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); 309 goto err_iounmap; 310 } 311 312 spin_lock_init(&adc->lock); 313 atomic_set(&adc->clk_ref, 0); 314 315 platform_set_drvdata(pdev, adc); 316 317 for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) { 318 set_irq_chip_data(irq, adc); 319 set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip, 320 handle_level_irq); 321 } 322 323 set_irq_data(adc->irq, adc); 324 set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux); 325 326 writeb(0x00, adc->base + JZ_REG_ADC_ENABLE); 327 writeb(0xff, adc->base + JZ_REG_ADC_CTRL); 328 329 ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells, 330 ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base); 331 if (ret < 0) 332 goto err_clk_put; 333 334 return 0; 335 336err_clk_put: 337 clk_put(adc->clk); 338err_iounmap: 339 platform_set_drvdata(pdev, NULL); 340 iounmap(adc->base); 341err_release_mem_region: 342 release_mem_region(adc->mem->start, resource_size(adc->mem)); 343err_free: 344 kfree(adc); 345 346 return ret; 347} 348 349static int __devexit jz4740_adc_remove(struct platform_device *pdev) 350{ 351 struct jz4740_adc *adc = platform_get_drvdata(pdev); 352 353 mfd_remove_devices(&pdev->dev); 354 355 set_irq_data(adc->irq, NULL); 356 set_irq_chained_handler(adc->irq, NULL); 357 358 iounmap(adc->base); 359 release_mem_region(adc->mem->start, resource_size(adc->mem)); 360 361 clk_put(adc->clk); 362 363 platform_set_drvdata(pdev, NULL); 364 365 kfree(adc); 366 367 return 0; 368} 369 370struct platform_driver jz4740_adc_driver = { 371 .probe = jz4740_adc_probe, 372 .remove = __devexit_p(jz4740_adc_remove), 373 .driver = { 374 .name = "jz4740-adc", 375 .owner = THIS_MODULE, 376 }, 377}; 378 379static int __init jz4740_adc_init(void) 380{ 381 return platform_driver_register(&jz4740_adc_driver); 382} 383module_init(jz4740_adc_init); 384 385static void __exit jz4740_adc_exit(void) 386{ 387 platform_driver_unregister(&jz4740_adc_driver); 388} 389module_exit(jz4740_adc_exit); 390 391MODULE_DESCRIPTION("JZ4740 SoC ADC driver"); 392MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 393MODULE_LICENSE("GPL"); 394MODULE_ALIAS("platform:jz4740-adc"); 395