333 lines · c
1/*2 * Retu/Tahvo MFD driver3 *4 * Copyright (C) 2004, 2005 Nokia Corporation5 *6 * Based on code written by Juha Yrjölä, David Weinehall and Mikko Ylinen.7 * Rewritten by Aaro Koskinen.8 *9 * This file is subject to the terms and conditions of the GNU General10 * Public License. See the file "COPYING" in the main directory of this11 * archive for more details.12 *13 * This program is distributed in the hope that it will be useful,14 * but WITHOUT ANY WARRANTY; without even the implied warranty of15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16 * GNU General Public License for more details.17 */18 19#include <linux/err.h>20#include <linux/i2c.h>21#include <linux/irq.h>22#include <linux/slab.h>23#include <linux/mutex.h>24#include <linux/module.h>25#include <linux/regmap.h>26#include <linux/mfd/core.h>27#include <linux/mfd/retu.h>28#include <linux/interrupt.h>29#include <linux/moduleparam.h>30 31/* Registers */32#define RETU_REG_ASICR 0x00 /* ASIC ID and revision */33#define RETU_REG_ASICR_VILMA (1 << 7) /* Bit indicating Vilma */34#define RETU_REG_IDR 0x01 /* Interrupt ID */35#define RETU_REG_IMR 0x02 /* Interrupt mask (Retu) */36#define TAHVO_REG_IMR 0x03 /* Interrupt mask (Tahvo) */37 38/* Interrupt sources */39#define RETU_INT_PWR 0 /* Power button */40 41struct retu_dev {42 struct regmap *regmap;43 struct device *dev;44 struct mutex mutex;45 struct regmap_irq_chip_data *irq_data;46};47 48static const struct resource retu_pwrbutton_res[] = {49 {50 .name = "retu-pwrbutton",51 .start = RETU_INT_PWR,52 .end = RETU_INT_PWR,53 .flags = IORESOURCE_IRQ,54 },55};56 57static const struct mfd_cell retu_devs[] = {58 {59 .name = "retu-wdt"60 },61 {62 .name = "retu-pwrbutton",63 .resources = retu_pwrbutton_res,64 .num_resources = ARRAY_SIZE(retu_pwrbutton_res),65 }66};67 68static const struct regmap_irq retu_irqs[] = {69 [RETU_INT_PWR] = {70 .mask = 1 << RETU_INT_PWR,71 }72};73 74static const struct regmap_irq_chip retu_irq_chip = {75 .name = "RETU",76 .irqs = retu_irqs,77 .num_irqs = ARRAY_SIZE(retu_irqs),78 .num_regs = 1,79 .status_base = RETU_REG_IDR,80 .mask_base = RETU_REG_IMR,81 .ack_base = RETU_REG_IDR,82};83 84/* Retu device registered for the power off. */85static struct retu_dev *retu_pm_power_off;86 87static const struct resource tahvo_usb_res[] = {88 {89 .name = "tahvo-usb",90 .start = TAHVO_INT_VBUS,91 .end = TAHVO_INT_VBUS,92 .flags = IORESOURCE_IRQ,93 },94};95 96static const struct mfd_cell tahvo_devs[] = {97 {98 .name = "tahvo-usb",99 .resources = tahvo_usb_res,100 .num_resources = ARRAY_SIZE(tahvo_usb_res),101 },102};103 104static const struct regmap_irq tahvo_irqs[] = {105 [TAHVO_INT_VBUS] = {106 .mask = 1 << TAHVO_INT_VBUS,107 }108};109 110static const struct regmap_irq_chip tahvo_irq_chip = {111 .name = "TAHVO",112 .irqs = tahvo_irqs,113 .num_irqs = ARRAY_SIZE(tahvo_irqs),114 .num_regs = 1,115 .status_base = RETU_REG_IDR,116 .mask_base = TAHVO_REG_IMR,117 .ack_base = RETU_REG_IDR,118};119 120static const struct retu_data {121 char *chip_name;122 char *companion_name;123 const struct regmap_irq_chip *irq_chip;124 const struct mfd_cell *children;125 int nchildren;126} retu_data[] = {127 [0] = {128 .chip_name = "Retu",129 .companion_name = "Vilma",130 .irq_chip = &retu_irq_chip,131 .children = retu_devs,132 .nchildren = ARRAY_SIZE(retu_devs),133 },134 [1] = {135 .chip_name = "Tahvo",136 .companion_name = "Betty",137 .irq_chip = &tahvo_irq_chip,138 .children = tahvo_devs,139 .nchildren = ARRAY_SIZE(tahvo_devs),140 }141};142 143int retu_read(struct retu_dev *rdev, u8 reg)144{145 int ret;146 int value;147 148 mutex_lock(&rdev->mutex);149 ret = regmap_read(rdev->regmap, reg, &value);150 mutex_unlock(&rdev->mutex);151 152 return ret ? ret : value;153}154EXPORT_SYMBOL_GPL(retu_read);155 156int retu_write(struct retu_dev *rdev, u8 reg, u16 data)157{158 int ret;159 160 mutex_lock(&rdev->mutex);161 ret = regmap_write(rdev->regmap, reg, data);162 mutex_unlock(&rdev->mutex);163 164 return ret;165}166EXPORT_SYMBOL_GPL(retu_write);167 168static void retu_power_off(void)169{170 struct retu_dev *rdev = retu_pm_power_off;171 int reg;172 173 mutex_lock(&retu_pm_power_off->mutex);174 175 /* Ignore power button state */176 regmap_read(rdev->regmap, RETU_REG_CC1, ®);177 regmap_write(rdev->regmap, RETU_REG_CC1, reg | 2);178 179 /* Expire watchdog immediately */180 regmap_write(rdev->regmap, RETU_REG_WATCHDOG, 0);181 182 /* Wait for poweroff */183 for (;;)184 cpu_relax();185 186 mutex_unlock(&retu_pm_power_off->mutex);187}188 189static int retu_regmap_read(void *context, const void *reg, size_t reg_size,190 void *val, size_t val_size)191{192 int ret;193 struct device *dev = context;194 struct i2c_client *i2c = to_i2c_client(dev);195 196 BUG_ON(reg_size != 1 || val_size != 2);197 198 ret = i2c_smbus_read_word_data(i2c, *(u8 const *)reg);199 if (ret < 0)200 return ret;201 202 *(u16 *)val = ret;203 return 0;204}205 206static int retu_regmap_write(void *context, const void *data, size_t count)207{208 u8 reg;209 u16 val;210 struct device *dev = context;211 struct i2c_client *i2c = to_i2c_client(dev);212 213 BUG_ON(count != sizeof(reg) + sizeof(val));214 memcpy(®, data, sizeof(reg));215 memcpy(&val, data + sizeof(reg), sizeof(val));216 return i2c_smbus_write_word_data(i2c, reg, val);217}218 219static const struct regmap_bus retu_bus = {220 .read = retu_regmap_read,221 .write = retu_regmap_write,222 .val_format_endian_default = REGMAP_ENDIAN_NATIVE,223};224 225static const struct regmap_config retu_config = {226 .reg_bits = 8,227 .val_bits = 16,228};229 230static int retu_probe(struct i2c_client *i2c)231{232 struct retu_data const *rdat;233 struct retu_dev *rdev;234 int ret;235 236 if (i2c->addr > ARRAY_SIZE(retu_data))237 return -ENODEV;238 rdat = &retu_data[i2c->addr - 1];239 240 rdev = devm_kzalloc(&i2c->dev, sizeof(*rdev), GFP_KERNEL);241 if (rdev == NULL)242 return -ENOMEM;243 244 i2c_set_clientdata(i2c, rdev);245 rdev->dev = &i2c->dev;246 mutex_init(&rdev->mutex);247 rdev->regmap = devm_regmap_init(&i2c->dev, &retu_bus, &i2c->dev,248 &retu_config);249 if (IS_ERR(rdev->regmap))250 return PTR_ERR(rdev->regmap);251 252 ret = retu_read(rdev, RETU_REG_ASICR);253 if (ret < 0) {254 dev_err(rdev->dev, "could not read %s revision: %d\n",255 rdat->chip_name, ret);256 return ret;257 }258 259 dev_info(rdev->dev, "%s%s%s v%d.%d found\n", rdat->chip_name,260 (ret & RETU_REG_ASICR_VILMA) ? " & " : "",261 (ret & RETU_REG_ASICR_VILMA) ? rdat->companion_name : "",262 (ret >> 4) & 0x7, ret & 0xf);263 264 /* Mask all interrupts. */265 ret = retu_write(rdev, rdat->irq_chip->mask_base, 0xffff);266 if (ret < 0)267 return ret;268 269 ret = regmap_add_irq_chip(rdev->regmap, i2c->irq, IRQF_ONESHOT, -1,270 rdat->irq_chip, &rdev->irq_data);271 if (ret < 0)272 return ret;273 274 ret = mfd_add_devices(rdev->dev, -1, rdat->children, rdat->nchildren,275 NULL, regmap_irq_chip_get_base(rdev->irq_data),276 NULL);277 if (ret < 0) {278 regmap_del_irq_chip(i2c->irq, rdev->irq_data);279 return ret;280 }281 282 if (i2c->addr == 1 && !pm_power_off) {283 retu_pm_power_off = rdev;284 pm_power_off = retu_power_off;285 }286 287 return 0;288}289 290static void retu_remove(struct i2c_client *i2c)291{292 struct retu_dev *rdev = i2c_get_clientdata(i2c);293 294 if (retu_pm_power_off == rdev) {295 pm_power_off = NULL;296 retu_pm_power_off = NULL;297 }298 mfd_remove_devices(rdev->dev);299 regmap_del_irq_chip(i2c->irq, rdev->irq_data);300}301 302static const struct i2c_device_id retu_id[] = {303 { "retu" },304 { "tahvo" },305 { }306};307MODULE_DEVICE_TABLE(i2c, retu_id);308 309static const struct of_device_id retu_of_match[] = {310 { .compatible = "nokia,retu" },311 { .compatible = "nokia,tahvo" },312 { }313};314MODULE_DEVICE_TABLE(of, retu_of_match);315 316static struct i2c_driver retu_driver = {317 .driver = {318 .name = "retu-mfd",319 .of_match_table = retu_of_match,320 },321 .probe = retu_probe,322 .remove = retu_remove,323 .id_table = retu_id,324};325module_i2c_driver(retu_driver);326 327MODULE_DESCRIPTION("Retu MFD driver");328MODULE_AUTHOR("Juha Yrjölä");329MODULE_AUTHOR("David Weinehall");330MODULE_AUTHOR("Mikko Ylinen");331MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");332MODULE_LICENSE("GPL");333