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