Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mfd: Add JZ4740 ADC driver

This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
demultiplex IRQs and synchronize access to shared registers between the
battery, hwmon and (future) touchscreen driver.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>

authored by

Lars-Peter Clausen and committed by
Samuel Ortiz
91f4debf b12c35e2

+425
+8
drivers/mfd/Kconfig
··· 514 514 host many different types of MODULbus daughterboards, including 515 515 CAN and GPIO controllers. 516 516 517 + config MFD_JZ4740_ADC 518 + tristate "Support for the JZ4740 SoC ADC core" 519 + select MFD_CORE 520 + depends on MACH_JZ4740 521 + help 522 + Say yes here if you want support for the ADC unit in the JZ4740 SoC. 523 + This driver is necessary for jz4740-battery and jz4740-hwmon driver. 524 + 517 525 endif # MFD_SUPPORT 518 526 519 527 menu "Multimedia Capabilities Port drivers"
+1
drivers/mfd/Makefile
··· 72 72 obj-$(CONFIG_LPC_SCH) += lpc_sch.o 73 73 obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o 74 74 obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o 75 + obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
+384
drivers/mfd/jz4740-adc.c
··· 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 + 46 + enum { 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 + 54 + struct 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 + 67 + static 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 + 87 + static 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 + 93 + static 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 + 99 + static 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 + 107 + static 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 + 114 + static 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 */ 132 + static 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 + 138 + static 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 + 144 + static 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 + 162 + static 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 + 172 + static 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 + 182 + int 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 + } 204 + EXPORT_SYMBOL_GPL(jz4740_adc_set_config); 205 + 206 + static 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 + 218 + static 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 + 230 + const 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 + 255 + static 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 + 264 + adc->irq = platform_get_irq(pdev, 0); 265 + if (adc->irq < 0) { 266 + ret = adc->irq; 267 + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); 268 + goto err_free; 269 + } 270 + 271 + adc->irq_base = platform_get_irq(pdev, 1); 272 + if (adc->irq_base < 0) { 273 + ret = adc->irq_base; 274 + dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret); 275 + goto err_free; 276 + } 277 + 278 + mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); 279 + if (!mem_base) { 280 + ret = -ENOENT; 281 + dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); 282 + goto err_free; 283 + } 284 + 285 + /* Only request the shared registers for the MFD driver */ 286 + adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS, 287 + pdev->name); 288 + if (!adc->mem) { 289 + ret = -EBUSY; 290 + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); 291 + goto err_free; 292 + } 293 + 294 + adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem)); 295 + if (!adc->base) { 296 + ret = -EBUSY; 297 + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); 298 + goto err_release_mem_region; 299 + } 300 + 301 + adc->clk = clk_get(&pdev->dev, "adc"); 302 + if (IS_ERR(adc->clk)) { 303 + ret = PTR_ERR(adc->clk); 304 + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); 305 + goto err_iounmap; 306 + } 307 + 308 + spin_lock_init(&adc->lock); 309 + atomic_set(&adc->clk_ref, 0); 310 + 311 + platform_set_drvdata(pdev, adc); 312 + 313 + for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) { 314 + set_irq_chip_data(irq, adc); 315 + set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip, 316 + handle_level_irq); 317 + } 318 + 319 + set_irq_data(adc->irq, adc); 320 + set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux); 321 + 322 + writeb(0x00, adc->base + JZ_REG_ADC_ENABLE); 323 + writeb(0xff, adc->base + JZ_REG_ADC_CTRL); 324 + 325 + return mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells, 326 + ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base); 327 + 328 + err_iounmap: 329 + platform_set_drvdata(pdev, NULL); 330 + iounmap(adc->base); 331 + err_release_mem_region: 332 + release_mem_region(adc->mem->start, resource_size(adc->mem)); 333 + err_free: 334 + kfree(adc); 335 + 336 + return ret; 337 + } 338 + 339 + static int __devexit jz4740_adc_remove(struct platform_device *pdev) 340 + { 341 + struct jz4740_adc *adc = platform_get_drvdata(pdev); 342 + 343 + mfd_remove_devices(&pdev->dev); 344 + 345 + set_irq_data(adc->irq, NULL); 346 + set_irq_chained_handler(adc->irq, NULL); 347 + 348 + iounmap(adc->base); 349 + release_mem_region(adc->mem->start, resource_size(adc->mem)); 350 + 351 + clk_put(adc->clk); 352 + 353 + platform_set_drvdata(pdev, NULL); 354 + 355 + kfree(adc); 356 + 357 + return 0; 358 + } 359 + 360 + struct platform_driver jz4740_adc_driver = { 361 + .probe = jz4740_adc_probe, 362 + .remove = __devexit_p(jz4740_adc_remove), 363 + .driver = { 364 + .name = "jz4740-adc", 365 + .owner = THIS_MODULE, 366 + }, 367 + }; 368 + 369 + static int __init jz4740_adc_init(void) 370 + { 371 + return platform_driver_register(&jz4740_adc_driver); 372 + } 373 + module_init(jz4740_adc_init); 374 + 375 + static void __exit jz4740_adc_exit(void) 376 + { 377 + platform_driver_unregister(&jz4740_adc_driver); 378 + } 379 + module_exit(jz4740_adc_exit); 380 + 381 + MODULE_DESCRIPTION("JZ4740 SoC ADC driver"); 382 + MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 383 + MODULE_LICENSE("GPL"); 384 + MODULE_ALIAS("platform:jz4740-adc");
+32
include/linux/jz4740-adc.h
··· 1 + 2 + #ifndef __LINUX_JZ4740_ADC 3 + #define __LINUX_JZ4740_ADC 4 + 5 + #include <linux/device.h> 6 + 7 + /* 8 + * jz4740_adc_set_config - Configure a JZ4740 adc device 9 + * @dev: Pointer to a jz4740-adc device 10 + * @mask: Mask for the config value to be set 11 + * @val: Value to be set 12 + * 13 + * This function can be used by the JZ4740 ADC mfd cells to configure their 14 + * options in the shared config register. 15 + */ 16 + int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val); 17 + 18 + #define JZ_ADC_CONFIG_SPZZ BIT(31) 19 + #define JZ_ADC_CONFIG_EX_IN BIT(30) 20 + #define JZ_ADC_CONFIG_DNUM_MASK (0x7 << 16) 21 + #define JZ_ADC_CONFIG_DMA_ENABLE BIT(15) 22 + #define JZ_ADC_CONFIG_XYZ_MASK (0x2 << 13) 23 + #define JZ_ADC_CONFIG_SAMPLE_NUM_MASK (0x7 << 10) 24 + #define JZ_ADC_CONFIG_CLKDIV_MASK (0xf << 5) 25 + #define JZ_ADC_CONFIG_BAT_MB BIT(4) 26 + 27 + #define JZ_ADC_CONFIG_DNUM(dnum) ((dnum) << 16) 28 + #define JZ_ADC_CONFIG_XYZ_OFFSET(dnum) ((xyz) << 13) 29 + #define JZ_ADC_CONFIG_SAMPLE_NUM(x) ((x) << 10) 30 + #define JZ_ADC_CONFIG_CLKDIV(div) ((div) << 5) 31 + 32 + #endif