1/* 2 * Retu/Tahvo MFD driver 3 * 4 * Copyright (C) 2004, 2005 Nokia Corporation 5 * 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 General 10 * Public License. See the file "COPYING" in the main directory of this 11 * 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 of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * 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 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 struct regmap_irq retu_irqs[] = { 69 [RETU_INT_PWR] = { 70 .mask = 1 << RETU_INT_PWR, 71 } 72}; 73 74static 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 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 struct regmap_irq tahvo_irqs[] = { 105 [TAHVO_INT_VBUS] = { 106 .mask = 1 << TAHVO_INT_VBUS, 107 } 108}; 109 110static 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 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 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, const struct i2c_device_id *id) 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 int 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 return 0; 302} 303 304static const struct i2c_device_id retu_id[] = { 305 { "retu-mfd", 0 }, 306 { "tahvo-mfd", 0 }, 307 { } 308}; 309MODULE_DEVICE_TABLE(i2c, retu_id); 310 311static struct i2c_driver retu_driver = { 312 .driver = { 313 .name = "retu-mfd", 314 }, 315 .probe = retu_probe, 316 .remove = retu_remove, 317 .id_table = retu_id, 318}; 319module_i2c_driver(retu_driver); 320 321MODULE_DESCRIPTION("Retu MFD driver"); 322MODULE_AUTHOR("Juha Yrj��l��"); 323MODULE_AUTHOR("David Weinehall"); 324MODULE_AUTHOR("Mikko Ylinen"); 325MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); 326MODULE_LICENSE("GPL"); 327