linux/drivers/mfd/adp5520.c
<<
>>
Prefs
   1/*
   2 * Base driver for Analog Devices ADP5520/ADP5501 MFD PMICs
   3 * LCD Backlight: drivers/video/backlight/adp5520_bl
   4 * LEDs         : drivers/led/leds-adp5520
   5 * GPIO         : drivers/gpio/adp5520-gpio (ADP5520 only)
   6 * Keys         : drivers/input/keyboard/adp5520-keys (ADP5520 only)
   7 *
   8 * Copyright 2009 Analog Devices Inc.
   9 *
  10 * Derived from da903x:
  11 * Copyright (C) 2008 Compulab, Ltd.
  12 *      Mike Rapoport <mike@compulab.co.il>
  13 *
  14 * Copyright (C) 2006-2008 Marvell International Ltd.
  15 *      Eric Miao <eric.miao@marvell.com>
  16 *
  17 * Licensed under the GPL-2 or later.
  18 */
  19
  20#include <linux/kernel.h>
  21#include <linux/module.h>
  22#include <linux/platform_device.h>
  23#include <linux/init.h>
  24#include <linux/slab.h>
  25#include <linux/interrupt.h>
  26#include <linux/irq.h>
  27#include <linux/err.h>
  28#include <linux/i2c.h>
  29
  30#include <linux/mfd/adp5520.h>
  31
  32struct adp5520_chip {
  33        struct i2c_client *client;
  34        struct device *dev;
  35        struct mutex lock;
  36        struct blocking_notifier_head notifier_list;
  37        int irq;
  38        unsigned long id;
  39        uint8_t mode;
  40};
  41
  42static int __adp5520_read(struct i2c_client *client,
  43                                int reg, uint8_t *val)
  44{
  45        int ret;
  46
  47        ret = i2c_smbus_read_byte_data(client, reg);
  48        if (ret < 0) {
  49                dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
  50                return ret;
  51        }
  52
  53        *val = (uint8_t)ret;
  54        return 0;
  55}
  56
  57static int __adp5520_write(struct i2c_client *client,
  58                                 int reg, uint8_t val)
  59{
  60        int ret;
  61
  62        ret = i2c_smbus_write_byte_data(client, reg, val);
  63        if (ret < 0) {
  64                dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
  65                                val, reg);
  66                return ret;
  67        }
  68        return 0;
  69}
  70
  71static int __adp5520_ack_bits(struct i2c_client *client, int reg,
  72                              uint8_t bit_mask)
  73{
  74        struct adp5520_chip *chip = i2c_get_clientdata(client);
  75        uint8_t reg_val;
  76        int ret;
  77
  78        mutex_lock(&chip->lock);
  79
  80        ret = __adp5520_read(client, reg, &reg_val);
  81
  82        if (!ret) {
  83                reg_val |= bit_mask;
  84                ret = __adp5520_write(client, reg, reg_val);
  85        }
  86
  87        mutex_unlock(&chip->lock);
  88        return ret;
  89}
  90
  91int adp5520_write(struct device *dev, int reg, uint8_t val)
  92{
  93        return __adp5520_write(to_i2c_client(dev), reg, val);
  94}
  95EXPORT_SYMBOL_GPL(adp5520_write);
  96
  97int adp5520_read(struct device *dev, int reg, uint8_t *val)
  98{
  99        return __adp5520_read(to_i2c_client(dev), reg, val);
 100}
 101EXPORT_SYMBOL_GPL(adp5520_read);
 102
 103int adp5520_set_bits(struct device *dev, int reg, uint8_t bit_mask)
 104{
 105        struct adp5520_chip *chip = dev_get_drvdata(dev);
 106        uint8_t reg_val;
 107        int ret;
 108
 109        mutex_lock(&chip->lock);
 110
 111        ret = __adp5520_read(chip->client, reg, &reg_val);
 112
 113        if (!ret && ((reg_val & bit_mask) != bit_mask)) {
 114                reg_val |= bit_mask;
 115                ret = __adp5520_write(chip->client, reg, reg_val);
 116        }
 117
 118        mutex_unlock(&chip->lock);
 119        return ret;
 120}
 121EXPORT_SYMBOL_GPL(adp5520_set_bits);
 122
 123int adp5520_clr_bits(struct device *dev, int reg, uint8_t bit_mask)
 124{
 125        struct adp5520_chip *chip = dev_get_drvdata(dev);
 126        uint8_t reg_val;
 127        int ret;
 128
 129        mutex_lock(&chip->lock);
 130
 131        ret = __adp5520_read(chip->client, reg, &reg_val);
 132
 133        if (!ret && (reg_val & bit_mask)) {
 134                reg_val &= ~bit_mask;
 135                ret = __adp5520_write(chip->client, reg, reg_val);
 136        }
 137
 138        mutex_unlock(&chip->lock);
 139        return ret;
 140}
 141EXPORT_SYMBOL_GPL(adp5520_clr_bits);
 142
 143int adp5520_register_notifier(struct device *dev, struct notifier_block *nb,
 144                                unsigned int events)
 145{
 146        struct adp5520_chip *chip = dev_get_drvdata(dev);
 147
 148        if (chip->irq) {
 149                adp5520_set_bits(chip->dev, ADP5520_INTERRUPT_ENABLE,
 150                        events & (ADP5520_KP_IEN | ADP5520_KR_IEN |
 151                        ADP5520_OVP_IEN | ADP5520_CMPR_IEN));
 152
 153                return blocking_notifier_chain_register(&chip->notifier_list,
 154                         nb);
 155        }
 156
 157        return -ENODEV;
 158}
 159EXPORT_SYMBOL_GPL(adp5520_register_notifier);
 160
 161int adp5520_unregister_notifier(struct device *dev, struct notifier_block *nb,
 162                                unsigned int events)
 163{
 164        struct adp5520_chip *chip = dev_get_drvdata(dev);
 165
 166        adp5520_clr_bits(chip->dev, ADP5520_INTERRUPT_ENABLE,
 167                events & (ADP5520_KP_IEN | ADP5520_KR_IEN |
 168                ADP5520_OVP_IEN | ADP5520_CMPR_IEN));
 169
 170        return blocking_notifier_chain_unregister(&chip->notifier_list, nb);
 171}
 172EXPORT_SYMBOL_GPL(adp5520_unregister_notifier);
 173
 174static irqreturn_t adp5520_irq_thread(int irq, void *data)
 175{
 176        struct adp5520_chip *chip = data;
 177        unsigned int events;
 178        uint8_t reg_val;
 179        int ret;
 180
 181        ret = __adp5520_read(chip->client, ADP5520_MODE_STATUS, &reg_val);
 182        if (ret)
 183                goto out;
 184
 185        events =  reg_val & (ADP5520_OVP_INT | ADP5520_CMPR_INT |
 186                ADP5520_GPI_INT | ADP5520_KR_INT | ADP5520_KP_INT);
 187
 188        blocking_notifier_call_chain(&chip->notifier_list, events, NULL);
 189        /* ACK, Sticky bits are W1C */
 190        __adp5520_ack_bits(chip->client, ADP5520_MODE_STATUS, events);
 191
 192out:
 193        return IRQ_HANDLED;
 194}
 195
 196static int __remove_subdev(struct device *dev, void *unused)
 197{
 198        platform_device_unregister(to_platform_device(dev));
 199        return 0;
 200}
 201
 202static int adp5520_remove_subdevs(struct adp5520_chip *chip)
 203{
 204        return device_for_each_child(chip->dev, NULL, __remove_subdev);
 205}
 206
 207static int adp5520_probe(struct i2c_client *client,
 208                                        const struct i2c_device_id *id)
 209{
 210        struct adp5520_platform_data *pdata = dev_get_platdata(&client->dev);
 211        struct platform_device *pdev;
 212        struct adp5520_chip *chip;
 213        int ret;
 214
 215        if (!i2c_check_functionality(client->adapter,
 216                                        I2C_FUNC_SMBUS_BYTE_DATA)) {
 217                dev_err(&client->dev, "SMBUS Word Data not Supported\n");
 218                return -EIO;
 219        }
 220
 221        if (pdata == NULL) {
 222                dev_err(&client->dev, "missing platform data\n");
 223                return -ENODEV;
 224        }
 225
 226        chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
 227        if (!chip)
 228                return -ENOMEM;
 229
 230        i2c_set_clientdata(client, chip);
 231        chip->client = client;
 232
 233        chip->dev = &client->dev;
 234        chip->irq = client->irq;
 235        chip->id = id->driver_data;
 236        mutex_init(&chip->lock);
 237
 238        if (chip->irq) {
 239                BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list);
 240
 241                ret = request_threaded_irq(chip->irq, NULL, adp5520_irq_thread,
 242                                IRQF_TRIGGER_LOW | IRQF_ONESHOT,
 243                                "adp5520", chip);
 244                if (ret) {
 245                        dev_err(&client->dev, "failed to request irq %d\n",
 246                                        chip->irq);
 247                        return ret;
 248                }
 249        }
 250
 251        ret = adp5520_write(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY);
 252        if (ret) {
 253                dev_err(&client->dev, "failed to write\n");
 254                goto out_free_irq;
 255        }
 256
 257        if (pdata->keys) {
 258                pdev = platform_device_register_data(chip->dev, "adp5520-keys",
 259                                chip->id, pdata->keys, sizeof(*pdata->keys));
 260                if (IS_ERR(pdev)) {
 261                        ret = PTR_ERR(pdev);
 262                        goto out_remove_subdevs;
 263                }
 264        }
 265
 266        if (pdata->gpio) {
 267                pdev = platform_device_register_data(chip->dev, "adp5520-gpio",
 268                                chip->id, pdata->gpio, sizeof(*pdata->gpio));
 269                if (IS_ERR(pdev)) {
 270                        ret = PTR_ERR(pdev);
 271                        goto out_remove_subdevs;
 272                }
 273        }
 274
 275        if (pdata->leds) {
 276                pdev = platform_device_register_data(chip->dev, "adp5520-led",
 277                                chip->id, pdata->leds, sizeof(*pdata->leds));
 278                if (IS_ERR(pdev)) {
 279                        ret = PTR_ERR(pdev);
 280                        goto out_remove_subdevs;
 281                }
 282        }
 283
 284        if (pdata->backlight) {
 285                pdev = platform_device_register_data(chip->dev,
 286                                                "adp5520-backlight",
 287                                                chip->id,
 288                                                pdata->backlight,
 289                                                sizeof(*pdata->backlight));
 290                if (IS_ERR(pdev)) {
 291                        ret = PTR_ERR(pdev);
 292                        goto out_remove_subdevs;
 293                }
 294        }
 295
 296        return 0;
 297
 298out_remove_subdevs:
 299        adp5520_remove_subdevs(chip);
 300
 301out_free_irq:
 302        if (chip->irq)
 303                free_irq(chip->irq, chip);
 304
 305        return ret;
 306}
 307
 308static int adp5520_remove(struct i2c_client *client)
 309{
 310        struct adp5520_chip *chip = dev_get_drvdata(&client->dev);
 311
 312        if (chip->irq)
 313                free_irq(chip->irq, chip);
 314
 315        adp5520_remove_subdevs(chip);
 316        adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0);
 317        return 0;
 318}
 319
 320#ifdef CONFIG_PM_SLEEP
 321static int adp5520_suspend(struct device *dev)
 322{
 323        struct i2c_client *client = to_i2c_client(dev);
 324        struct adp5520_chip *chip = dev_get_drvdata(&client->dev);
 325
 326        adp5520_read(chip->dev, ADP5520_MODE_STATUS, &chip->mode);
 327        /* All other bits are W1C */
 328        chip->mode &= ADP5520_BL_EN | ADP5520_DIM_EN | ADP5520_nSTNBY;
 329        adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0);
 330        return 0;
 331}
 332
 333static int adp5520_resume(struct device *dev)
 334{
 335        struct i2c_client *client = to_i2c_client(dev);
 336        struct adp5520_chip *chip = dev_get_drvdata(&client->dev);
 337
 338        adp5520_write(chip->dev, ADP5520_MODE_STATUS, chip->mode);
 339        return 0;
 340}
 341#endif
 342
 343static SIMPLE_DEV_PM_OPS(adp5520_pm, adp5520_suspend, adp5520_resume);
 344
 345static const struct i2c_device_id adp5520_id[] = {
 346        { "pmic-adp5520", ID_ADP5520 },
 347        { "pmic-adp5501", ID_ADP5501 },
 348        { }
 349};
 350MODULE_DEVICE_TABLE(i2c, adp5520_id);
 351
 352static struct i2c_driver adp5520_driver = {
 353        .driver = {
 354                .name   = "adp5520",
 355                .owner  = THIS_MODULE,
 356                .pm     = &adp5520_pm,
 357        },
 358        .probe          = adp5520_probe,
 359        .remove         = adp5520_remove,
 360        .id_table       = adp5520_id,
 361};
 362
 363module_i2c_driver(adp5520_driver);
 364
 365MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
 366MODULE_DESCRIPTION("ADP5520(01) PMIC-MFD Driver");
 367MODULE_LICENSE("GPL");
 368
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.