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 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 = devm_kzalloc(&pdev->dev, 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                return ret;
 225        }
 226
 227        irq_base = platform_get_irq(pdev, 1);
 228        if (irq_base < 0) {
 229                dev_err(&pdev->dev, "Failed to get irq base: %d\n", irq_base);
 230                return irq_base;
 231        }
 232
 233        mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 234        if (!mem_base) {
 235                dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
 236                return -ENOENT;
 237        }
 238
 239        /* Only request the shared registers for the MFD driver */
 240        adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
 241                                        pdev->name);
 242        if (!adc->mem) {
 243                dev_err(&pdev->dev, "Failed to request mmio memory region\n");
 244                return -EBUSY;
 245        }
 246
 247        adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
 248        if (!adc->base) {
 249                ret = -EBUSY;
 250                dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
 251                goto err_release_mem_region;
 252        }
 253
 254        adc->clk = clk_get(&pdev->dev, "adc");
 255        if (IS_ERR(adc->clk)) {
 256                ret = PTR_ERR(adc->clk);
 257                dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
 258                goto err_iounmap;
 259        }
 260
 261        spin_lock_init(&adc->lock);
 262        atomic_set(&adc->clk_ref, 0);
 263
 264        platform_set_drvdata(pdev, adc);
 265
 266        gc = irq_alloc_generic_chip("INTC", 1, irq_base, adc->base,
 267                handle_level_irq);
 268
 269        ct = gc->chip_types;
 270        ct->regs.mask = JZ_REG_ADC_CTRL;
 271        ct->regs.ack = JZ_REG_ADC_STATUS;
 272        ct->chip.irq_mask = irq_gc_mask_set_bit;
 273        ct->chip.irq_unmask = irq_gc_mask_clr_bit;
 274        ct->chip.irq_ack = irq_gc_ack_set_bit;
 275
 276        irq_setup_generic_chip(gc, IRQ_MSK(5), 0, 0, IRQ_NOPROBE | IRQ_LEVEL);
 277
 278        adc->gc = gc;
 279
 280        irq_set_handler_data(adc->irq, gc);
 281        irq_set_chained_handler(adc->irq, jz4740_adc_irq_demux);
 282
 283        writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
 284        writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
 285
 286        ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
 287                              ARRAY_SIZE(jz4740_adc_cells), mem_base,
 288                              irq_base, NULL);
 289        if (ret < 0)
 290                goto err_clk_put;
 291
 292        return 0;
 293
 294err_clk_put:
 295        clk_put(adc->clk);
 296err_iounmap:
 297        platform_set_drvdata(pdev, NULL);
 298        iounmap(adc->base);
 299err_release_mem_region:
 300        release_mem_region(adc->mem->start, resource_size(adc->mem));
 301        return ret;
 302}
 303
 304static int jz4740_adc_remove(struct platform_device *pdev)
 305{
 306        struct jz4740_adc *adc = platform_get_drvdata(pdev);
 307
 308        mfd_remove_devices(&pdev->dev);
 309
 310        irq_remove_generic_chip(adc->gc, IRQ_MSK(5), IRQ_NOPROBE | IRQ_LEVEL, 0);
 311        kfree(adc->gc);
 312        irq_set_handler_data(adc->irq, NULL);
 313        irq_set_chained_handler(adc->irq, NULL);
 314
 315        iounmap(adc->base);
 316        release_mem_region(adc->mem->start, resource_size(adc->mem));
 317
 318        clk_put(adc->clk);
 319
 320        platform_set_drvdata(pdev, NULL);
 321
 322        return 0;
 323}
 324
 325static struct platform_driver jz4740_adc_driver = {
 326        .probe  = jz4740_adc_probe,
 327        .remove = jz4740_adc_remove,
 328        .driver = {
 329                .name = "jz4740-adc",
 330                .owner = THIS_MODULE,
 331        },
 332};
 333
 334module_platform_driver(jz4740_adc_driver);
 335
 336MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
 337MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 338MODULE_LICENSE("GPL");
 339MODULE_ALIAS("platform:jz4740-adc");
 340
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.