linux/drivers/mfd/jz4740-adc.c
<<
>>
Prefs
   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/io.h>
  20#include <linux/irq.h>
  21#include <linux/interrupt.h>
  22#include <linux/kernel.h>
  23#include <linux/module.h>
  24#include <linux/platform_device.h>
  25#include <linux/slab.h>
  26#include <linux/spinlock.h>
  27
  28#include <linux/clk.h>
  29#include <linux/mfd/core.h>
  30
  31#include <linux/jz4740-adc.h>
  32
  33
  34#define JZ_REG_ADC_ENABLE       0x00
  35#define JZ_REG_ADC_CFG          0x04
  36#define JZ_REG_ADC_CTRL         0x08
  37#define JZ_REG_ADC_STATUS       0x0c
  38
  39#define JZ_REG_ADC_TOUCHSCREEN_BASE     0x10
  40#define JZ_REG_ADC_BATTERY_BASE 0x1c
  41#define JZ_REG_ADC_HWMON_BASE   0x20
  42
  43#define JZ_ADC_ENABLE_TOUCH     BIT(2)
  44#define JZ_ADC_ENABLE_BATTERY   BIT(1)
  45#define JZ_ADC_ENABLE_ADCIN     BIT(0)
  46
  47enum {
  48        JZ_ADC_IRQ_ADCIN = 0,
  49        JZ_ADC_IRQ_BATTERY,
  50        JZ_ADC_IRQ_TOUCH,
  51        JZ_ADC_IRQ_PENUP,
  52        JZ_ADC_IRQ_PENDOWN,
  53};
  54
  55struct jz4740_adc {
  56        struct resource *mem;
  57        void __iomem *base;
  58
  59        int irq;
  60        struct irq_chip_generic *gc;
  61
  62        struct clk *clk;
  63        atomic_t clk_ref;
  64
  65        spinlock_t lock;
  66};
  67
  68static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
  69{
  70        struct irq_chip_generic *gc = irq_desc_get_handler_data(desc);
  71        uint8_t status;
  72        unsigned int i;
  73
  74        status = readb(gc->reg_base + JZ_REG_ADC_STATUS);
  75
  76        for (i = 0; i < 5; ++i) {
  77                if (status & BIT(i))
  78                        generic_handle_irq(gc->irq_base + i);
  79        }
  80}
  81
  82
  83/* Refcounting for the ADC clock is done in here instead of in the clock
  84 * framework, because it is the only clock which is shared between multiple
  85 * devices and thus is the only clock which needs refcounting */
  86static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
  87{
  88        if (atomic_inc_return(&adc->clk_ref) == 1)
  89                clk_enable(adc->clk);
  90}
  91
  92static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
  93{
  94        if (atomic_dec_return(&adc->clk_ref) == 0)
  95                clk_disable(adc->clk);
  96}
  97
  98static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
  99        bool enabled)
 100{
 101        unsigned long flags;
 102        uint8_t val;
 103
 104        spin_lock_irqsave(&adc->lock, flags);
 105
 106        val = readb(adc->base + JZ_REG_ADC_ENABLE);
 107        if (enabled)
 108                val |= BIT(engine);
 109        else
 110                val &= ~BIT(engine);
 111        writeb(val, adc->base + JZ_REG_ADC_ENABLE);
 112
 113        spin_unlock_irqrestore(&adc->lock, flags);
 114}
 115
 116static int jz4740_adc_cell_enable(struct platform_device *pdev)
 117{
 118        struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
 119
 120        jz4740_adc_clk_enable(adc);
 121        jz4740_adc_set_enabled(adc, pdev->id, true);
 122
 123        return 0;
 124}
 125
 126static int jz4740_adc_cell_disable(struct platform_device *pdev)
 127{
 128        struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
 129
 130        jz4740_adc_set_enabled(adc, pdev->id, false);
 131        jz4740_adc_clk_disable(adc);
 132
 133        return 0;
 134}
 135
 136int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
 137{
 138        struct jz4740_adc *adc = dev_get_drvdata(dev);
 139        unsigned long flags;
 140        uint32_t cfg;
 141
 142        if (!adc)
 143                return -ENODEV;
 144
 145        spin_lock_irqsave(&adc->lock, flags);
 146
 147        cfg = readl(adc->base + JZ_REG_ADC_CFG);
 148
 149        cfg &= ~mask;
 150        cfg |= val;
 151
 152        writel(cfg, adc->base + JZ_REG_ADC_CFG);
 153
 154        spin_unlock_irqrestore(&adc->lock, flags);
 155
 156        return 0;
 157}
 158EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
 159
 160static struct resource jz4740_hwmon_resources[] = {
 161        {
 162                .start = JZ_ADC_IRQ_ADCIN,
 163                .flags = IORESOURCE_IRQ,
 164        },
 165        {
 166                .start  = JZ_REG_ADC_HWMON_BASE,
 167                .end    = JZ_REG_ADC_HWMON_BASE + 3,
 168                .flags  = IORESOURCE_MEM,
 169        },
 170};
 171
 172static struct resource jz4740_battery_resources[] = {
 173        {
 174                .start = JZ_ADC_IRQ_BATTERY,
 175                .flags = IORESOURCE_IRQ,
 176        },
 177        {
 178                .start  = JZ_REG_ADC_BATTERY_BASE,
 179                .end    = JZ_REG_ADC_BATTERY_BASE + 3,
 180                .flags  = IORESOURCE_MEM,
 181        },
 182};
 183
 184static struct mfd_cell jz4740_adc_cells[] = {
 185        {
 186                .id = 0,
 187                .name = "jz4740-hwmon",
 188                .num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
 189                .resources = jz4740_hwmon_resources,
 190
 191                .enable = jz4740_adc_cell_enable,
 192                .disable = jz4740_adc_cell_disable,
 193        },
 194        {
 195                .id = 1,
 196                .name = "jz4740-battery",
 197                .num_resources = ARRAY_SIZE(jz4740_battery_resources),
 198                .resources = jz4740_battery_resources,
 199
 200                .enable = jz4740_adc_cell_enable,
 201                .disable = jz4740_adc_cell_disable,
 202        },
 203};
 204
 205static int __devinit jz4740_adc_probe(struct platform_device *pdev)
 206{
 207        struct irq_chip_generic *gc;
 208        struct irq_chip_type *ct;
 209        struct jz4740_adc *adc;
 210        struct resource *mem_base;
 211        int ret;
 212        int irq_base;
 213
 214        adc = kmalloc(sizeof(*adc), GFP_KERNEL);
 215        if (!adc) {
 216                dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 217                return -ENOMEM;
 218        }
 219
 220        adc->irq = platform_get_irq(pdev, 0);
 221        if (adc->irq < 0) {
 222                ret = adc->irq;
 223                dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
 224                goto err_free;
 225        }
 226
 227        irq_base = platform_get_irq(pdev, 1);
 228        if (irq_base < 0) {
 229                ret = irq_base;
 230                dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
 231                goto err_free;
 232        }
 233
 234        mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 235        if (!mem_base) {
 236                ret = -ENOENT;
 237                dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
 238                goto err_free;
 239        }
 240
 241        /* Only request the shared registers for the MFD driver */
 242        adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
 243                                        pdev->name);
 244        if (!adc->mem) {
 245                ret = -EBUSY;
 246                dev_err(&pdev->dev, "Failed to request mmio memory region\n");
 247                goto err_free;
 248        }
 249
 250        adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
 251        if (!adc->base) {
 252                ret = -EBUSY;
 253                dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
 254                goto err_release_mem_region;
 255        }
 256
 257        adc->clk = clk_get(&pdev->dev, "adc");
 258        if (IS_ERR(adc->clk)) {
 259                ret = PTR_ERR(adc->clk);
 260                dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
 261                goto err_iounmap;
 262        }
 263
 264        spin_lock_init(&adc->lock);
 265        atomic_set(&adc->clk_ref, 0);
 266
 267        platform_set_drvdata(pdev, adc);
 268
 269        gc = irq_alloc_generic_chip("INTC", 1, irq_base, adc->base,
 270                handle_level_irq);
 271
 272        ct = gc->chip_types;
 273        ct->regs.mask = JZ_REG_ADC_CTRL;
 274        ct->regs.ack = JZ_REG_ADC_STATUS;
 275        ct->chip.irq_mask = irq_gc_mask_set_bit;
 276        ct->chip.irq_unmask = irq_gc_mask_clr_bit;
 277        ct->chip.irq_ack = irq_gc_ack_set_bit;
 278
 279        irq_setup_generic_chip(gc, IRQ_MSK(5), 0, 0, IRQ_NOPROBE | IRQ_LEVEL);
 280
 281        adc->gc = gc;
 282
 283        irq_set_handler_data(adc->irq, gc);
 284        irq_set_chained_handler(adc->irq, jz4740_adc_irq_demux);
 285
 286        writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
 287        writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
 288
 289        ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
 290                              ARRAY_SIZE(jz4740_adc_cells), mem_base,
 291                              irq_base, NULL);
 292        if (ret < 0)
 293                goto err_clk_put;
 294
 295        return 0;
 296
 297err_clk_put:
 298        clk_put(adc->clk);
 299err_iounmap:
 300        platform_set_drvdata(pdev, NULL);
 301        iounmap(adc->base);
 302err_release_mem_region:
 303        release_mem_region(adc->mem->start, resource_size(adc->mem));
 304err_free:
 305        kfree(adc);
 306
 307        return ret;
 308}
 309
 310static int __devexit jz4740_adc_remove(struct platform_device *pdev)
 311{
 312        struct jz4740_adc *adc = platform_get_drvdata(pdev);
 313
 314        mfd_remove_devices(&pdev->dev);
 315
 316        irq_remove_generic_chip(adc->gc, IRQ_MSK(5), IRQ_NOPROBE | IRQ_LEVEL, 0);
 317        kfree(adc->gc);
 318        irq_set_handler_data(adc->irq, NULL);
 319        irq_set_chained_handler(adc->irq, NULL);
 320
 321        iounmap(adc->base);
 322        release_mem_region(adc->mem->start, resource_size(adc->mem));
 323
 324        clk_put(adc->clk);
 325
 326        platform_set_drvdata(pdev, NULL);
 327
 328        kfree(adc);
 329
 330        return 0;
 331}
 332
 333static struct platform_driver jz4740_adc_driver = {
 334        .probe  = jz4740_adc_probe,
 335        .remove = __devexit_p(jz4740_adc_remove),
 336        .driver = {
 337                .name = "jz4740-adc",
 338                .owner = THIS_MODULE,
 339        },
 340};
 341
 342module_platform_driver(jz4740_adc_driver);
 343
 344MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
 345MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 346MODULE_LICENSE("GPL");
 347MODULE_ALIAS("platform:jz4740-adc");
 348
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.