1c7b76dceSAaro Koskinen /* 295e50f6aSAaro Koskinen * Retu/Tahvo MFD driver 3c7b76dceSAaro Koskinen * 4c7b76dceSAaro Koskinen * Copyright (C) 2004, 2005 Nokia Corporation 5c7b76dceSAaro Koskinen * 6c7b76dceSAaro Koskinen * Based on code written by Juha Yrjölä, David Weinehall and Mikko Ylinen. 7c7b76dceSAaro Koskinen * Rewritten by Aaro Koskinen. 8c7b76dceSAaro Koskinen * 9c7b76dceSAaro Koskinen * This file is subject to the terms and conditions of the GNU General 10c7b76dceSAaro Koskinen * Public License. See the file "COPYING" in the main directory of this 11c7b76dceSAaro Koskinen * archive for more details. 12c7b76dceSAaro Koskinen * 13c7b76dceSAaro Koskinen * This program is distributed in the hope that it will be useful, 14c7b76dceSAaro Koskinen * but WITHOUT ANY WARRANTY; without even the implied warranty of 15c7b76dceSAaro Koskinen * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16c7b76dceSAaro Koskinen * GNU General Public License for more details. 17c7b76dceSAaro Koskinen */ 18c7b76dceSAaro Koskinen 19c7b76dceSAaro Koskinen #include <linux/err.h> 20c7b76dceSAaro Koskinen #include <linux/i2c.h> 21c7b76dceSAaro Koskinen #include <linux/irq.h> 22c7b76dceSAaro Koskinen #include <linux/slab.h> 23c7b76dceSAaro Koskinen #include <linux/mutex.h> 24c7b76dceSAaro Koskinen #include <linux/module.h> 25c7b76dceSAaro Koskinen #include <linux/regmap.h> 26c7b76dceSAaro Koskinen #include <linux/mfd/core.h> 27c7b76dceSAaro Koskinen #include <linux/mfd/retu.h> 28c7b76dceSAaro Koskinen #include <linux/interrupt.h> 29c7b76dceSAaro Koskinen #include <linux/moduleparam.h> 30c7b76dceSAaro Koskinen 31c7b76dceSAaro Koskinen /* Registers */ 32c7b76dceSAaro Koskinen #define RETU_REG_ASICR 0x00 /* ASIC ID and revision */ 33c7b76dceSAaro Koskinen #define RETU_REG_ASICR_VILMA (1 << 7) /* Bit indicating Vilma */ 34c7b76dceSAaro Koskinen #define RETU_REG_IDR 0x01 /* Interrupt ID */ 3595e50f6aSAaro Koskinen #define RETU_REG_IMR 0x02 /* Interrupt mask (Retu) */ 3695e50f6aSAaro Koskinen #define TAHVO_REG_IMR 0x03 /* Interrupt mask (Tahvo) */ 37c7b76dceSAaro Koskinen 38c7b76dceSAaro Koskinen /* Interrupt sources */ 39c7b76dceSAaro Koskinen #define RETU_INT_PWR 0 /* Power button */ 40c7b76dceSAaro Koskinen 41c7b76dceSAaro Koskinen struct retu_dev { 42c7b76dceSAaro Koskinen struct regmap *regmap; 43c7b76dceSAaro Koskinen struct device *dev; 44c7b76dceSAaro Koskinen struct mutex mutex; 45c7b76dceSAaro Koskinen struct regmap_irq_chip_data *irq_data; 46c7b76dceSAaro Koskinen }; 47c7b76dceSAaro Koskinen 48c4a164f4SRikard Falkeborn static const struct resource retu_pwrbutton_res[] = { 49c7b76dceSAaro Koskinen { 50c7b76dceSAaro Koskinen .name = "retu-pwrbutton", 51c7b76dceSAaro Koskinen .start = RETU_INT_PWR, 52c7b76dceSAaro Koskinen .end = RETU_INT_PWR, 53c7b76dceSAaro Koskinen .flags = IORESOURCE_IRQ, 54c7b76dceSAaro Koskinen }, 55c7b76dceSAaro Koskinen }; 56c7b76dceSAaro Koskinen 575ac98553SGeert Uytterhoeven static const struct mfd_cell retu_devs[] = { 58c7b76dceSAaro Koskinen { 59c7b76dceSAaro Koskinen .name = "retu-wdt" 60c7b76dceSAaro Koskinen }, 61c7b76dceSAaro Koskinen { 62c7b76dceSAaro Koskinen .name = "retu-pwrbutton", 63c7b76dceSAaro Koskinen .resources = retu_pwrbutton_res, 64c7b76dceSAaro Koskinen .num_resources = ARRAY_SIZE(retu_pwrbutton_res), 65c7b76dceSAaro Koskinen } 66c7b76dceSAaro Koskinen }; 67c7b76dceSAaro Koskinen 68c7b76dceSAaro Koskinen static struct regmap_irq retu_irqs[] = { 69c7b76dceSAaro Koskinen [RETU_INT_PWR] = { 70c7b76dceSAaro Koskinen .mask = 1 << RETU_INT_PWR, 71c7b76dceSAaro Koskinen } 72c7b76dceSAaro Koskinen }; 73c7b76dceSAaro Koskinen 74c7b76dceSAaro Koskinen static struct regmap_irq_chip retu_irq_chip = { 75c7b76dceSAaro Koskinen .name = "RETU", 76c7b76dceSAaro Koskinen .irqs = retu_irqs, 77c7b76dceSAaro Koskinen .num_irqs = ARRAY_SIZE(retu_irqs), 78c7b76dceSAaro Koskinen .num_regs = 1, 79c7b76dceSAaro Koskinen .status_base = RETU_REG_IDR, 80c7b76dceSAaro Koskinen .mask_base = RETU_REG_IMR, 81c7b76dceSAaro Koskinen .ack_base = RETU_REG_IDR, 82c7b76dceSAaro Koskinen }; 83c7b76dceSAaro Koskinen 84c7b76dceSAaro Koskinen /* Retu device registered for the power off. */ 85c7b76dceSAaro Koskinen static struct retu_dev *retu_pm_power_off; 86c7b76dceSAaro Koskinen 87c4a164f4SRikard Falkeborn static const struct resource tahvo_usb_res[] = { 8895e50f6aSAaro Koskinen { 8995e50f6aSAaro Koskinen .name = "tahvo-usb", 9095e50f6aSAaro Koskinen .start = TAHVO_INT_VBUS, 9195e50f6aSAaro Koskinen .end = TAHVO_INT_VBUS, 9295e50f6aSAaro Koskinen .flags = IORESOURCE_IRQ, 9395e50f6aSAaro Koskinen }, 9495e50f6aSAaro Koskinen }; 9595e50f6aSAaro Koskinen 965ac98553SGeert Uytterhoeven static const struct mfd_cell tahvo_devs[] = { 9795e50f6aSAaro Koskinen { 9895e50f6aSAaro Koskinen .name = "tahvo-usb", 9995e50f6aSAaro Koskinen .resources = tahvo_usb_res, 10095e50f6aSAaro Koskinen .num_resources = ARRAY_SIZE(tahvo_usb_res), 10195e50f6aSAaro Koskinen }, 10295e50f6aSAaro Koskinen }; 10395e50f6aSAaro Koskinen 10495e50f6aSAaro Koskinen static struct regmap_irq tahvo_irqs[] = { 10595e50f6aSAaro Koskinen [TAHVO_INT_VBUS] = { 10695e50f6aSAaro Koskinen .mask = 1 << TAHVO_INT_VBUS, 10795e50f6aSAaro Koskinen } 10895e50f6aSAaro Koskinen }; 10995e50f6aSAaro Koskinen 11095e50f6aSAaro Koskinen static struct regmap_irq_chip tahvo_irq_chip = { 11195e50f6aSAaro Koskinen .name = "TAHVO", 11295e50f6aSAaro Koskinen .irqs = tahvo_irqs, 11395e50f6aSAaro Koskinen .num_irqs = ARRAY_SIZE(tahvo_irqs), 11495e50f6aSAaro Koskinen .num_regs = 1, 11595e50f6aSAaro Koskinen .status_base = RETU_REG_IDR, 11695e50f6aSAaro Koskinen .mask_base = TAHVO_REG_IMR, 11795e50f6aSAaro Koskinen .ack_base = RETU_REG_IDR, 11895e50f6aSAaro Koskinen }; 11995e50f6aSAaro Koskinen 12095e50f6aSAaro Koskinen static const struct retu_data { 12195e50f6aSAaro Koskinen char *chip_name; 12295e50f6aSAaro Koskinen char *companion_name; 12395e50f6aSAaro Koskinen struct regmap_irq_chip *irq_chip; 1245ac98553SGeert Uytterhoeven const struct mfd_cell *children; 12595e50f6aSAaro Koskinen int nchildren; 12695e50f6aSAaro Koskinen } retu_data[] = { 12795e50f6aSAaro Koskinen [0] = { 12895e50f6aSAaro Koskinen .chip_name = "Retu", 12995e50f6aSAaro Koskinen .companion_name = "Vilma", 13095e50f6aSAaro Koskinen .irq_chip = &retu_irq_chip, 13195e50f6aSAaro Koskinen .children = retu_devs, 13295e50f6aSAaro Koskinen .nchildren = ARRAY_SIZE(retu_devs), 13395e50f6aSAaro Koskinen }, 13495e50f6aSAaro Koskinen [1] = { 13595e50f6aSAaro Koskinen .chip_name = "Tahvo", 13695e50f6aSAaro Koskinen .companion_name = "Betty", 13795e50f6aSAaro Koskinen .irq_chip = &tahvo_irq_chip, 13895e50f6aSAaro Koskinen .children = tahvo_devs, 13995e50f6aSAaro Koskinen .nchildren = ARRAY_SIZE(tahvo_devs), 14095e50f6aSAaro Koskinen } 14195e50f6aSAaro Koskinen }; 14295e50f6aSAaro Koskinen 143c7b76dceSAaro Koskinen int retu_read(struct retu_dev *rdev, u8 reg) 144c7b76dceSAaro Koskinen { 145c7b76dceSAaro Koskinen int ret; 146c7b76dceSAaro Koskinen int value; 147c7b76dceSAaro Koskinen 148c7b76dceSAaro Koskinen mutex_lock(&rdev->mutex); 149c7b76dceSAaro Koskinen ret = regmap_read(rdev->regmap, reg, &value); 150c7b76dceSAaro Koskinen mutex_unlock(&rdev->mutex); 151c7b76dceSAaro Koskinen 152c7b76dceSAaro Koskinen return ret ? ret : value; 153c7b76dceSAaro Koskinen } 154c7b76dceSAaro Koskinen EXPORT_SYMBOL_GPL(retu_read); 155c7b76dceSAaro Koskinen 156c7b76dceSAaro Koskinen int retu_write(struct retu_dev *rdev, u8 reg, u16 data) 157c7b76dceSAaro Koskinen { 158c7b76dceSAaro Koskinen int ret; 159c7b76dceSAaro Koskinen 160c7b76dceSAaro Koskinen mutex_lock(&rdev->mutex); 161c7b76dceSAaro Koskinen ret = regmap_write(rdev->regmap, reg, data); 162c7b76dceSAaro Koskinen mutex_unlock(&rdev->mutex); 163c7b76dceSAaro Koskinen 164c7b76dceSAaro Koskinen return ret; 165c7b76dceSAaro Koskinen } 166c7b76dceSAaro Koskinen EXPORT_SYMBOL_GPL(retu_write); 167c7b76dceSAaro Koskinen 168c7b76dceSAaro Koskinen static void retu_power_off(void) 169c7b76dceSAaro Koskinen { 170c7b76dceSAaro Koskinen struct retu_dev *rdev = retu_pm_power_off; 171c7b76dceSAaro Koskinen int reg; 172c7b76dceSAaro Koskinen 173c7b76dceSAaro Koskinen mutex_lock(&retu_pm_power_off->mutex); 174c7b76dceSAaro Koskinen 175c7b76dceSAaro Koskinen /* Ignore power button state */ 176c7b76dceSAaro Koskinen regmap_read(rdev->regmap, RETU_REG_CC1, ®); 177c7b76dceSAaro Koskinen regmap_write(rdev->regmap, RETU_REG_CC1, reg | 2); 178c7b76dceSAaro Koskinen 179c7b76dceSAaro Koskinen /* Expire watchdog immediately */ 180c7b76dceSAaro Koskinen regmap_write(rdev->regmap, RETU_REG_WATCHDOG, 0); 181c7b76dceSAaro Koskinen 182c7b76dceSAaro Koskinen /* Wait for poweroff */ 183c7b76dceSAaro Koskinen for (;;) 184c7b76dceSAaro Koskinen cpu_relax(); 185c7b76dceSAaro Koskinen 186c7b76dceSAaro Koskinen mutex_unlock(&retu_pm_power_off->mutex); 187c7b76dceSAaro Koskinen } 188c7b76dceSAaro Koskinen 189c7b76dceSAaro Koskinen static int retu_regmap_read(void *context, const void *reg, size_t reg_size, 190c7b76dceSAaro Koskinen void *val, size_t val_size) 191c7b76dceSAaro Koskinen { 192c7b76dceSAaro Koskinen int ret; 193c7b76dceSAaro Koskinen struct device *dev = context; 194c7b76dceSAaro Koskinen struct i2c_client *i2c = to_i2c_client(dev); 195c7b76dceSAaro Koskinen 196c7b76dceSAaro Koskinen BUG_ON(reg_size != 1 || val_size != 2); 197c7b76dceSAaro Koskinen 198c7b76dceSAaro Koskinen ret = i2c_smbus_read_word_data(i2c, *(u8 const *)reg); 199c7b76dceSAaro Koskinen if (ret < 0) 200c7b76dceSAaro Koskinen return ret; 201c7b76dceSAaro Koskinen 202c7b76dceSAaro Koskinen *(u16 *)val = ret; 203c7b76dceSAaro Koskinen return 0; 204c7b76dceSAaro Koskinen } 205c7b76dceSAaro Koskinen 206c7b76dceSAaro Koskinen static int retu_regmap_write(void *context, const void *data, size_t count) 207c7b76dceSAaro Koskinen { 208c7b76dceSAaro Koskinen u8 reg; 209c7b76dceSAaro Koskinen u16 val; 210c7b76dceSAaro Koskinen struct device *dev = context; 211c7b76dceSAaro Koskinen struct i2c_client *i2c = to_i2c_client(dev); 212c7b76dceSAaro Koskinen 213c7b76dceSAaro Koskinen BUG_ON(count != sizeof(reg) + sizeof(val)); 214c7b76dceSAaro Koskinen memcpy(®, data, sizeof(reg)); 215c7b76dceSAaro Koskinen memcpy(&val, data + sizeof(reg), sizeof(val)); 216c7b76dceSAaro Koskinen return i2c_smbus_write_word_data(i2c, reg, val); 217c7b76dceSAaro Koskinen } 218c7b76dceSAaro Koskinen 219c7b76dceSAaro Koskinen static struct regmap_bus retu_bus = { 220c7b76dceSAaro Koskinen .read = retu_regmap_read, 221c7b76dceSAaro Koskinen .write = retu_regmap_write, 222c7b76dceSAaro Koskinen .val_format_endian_default = REGMAP_ENDIAN_NATIVE, 223c7b76dceSAaro Koskinen }; 224c7b76dceSAaro Koskinen 2251b33d5e2SKrzysztof Kozlowski static const struct regmap_config retu_config = { 226c7b76dceSAaro Koskinen .reg_bits = 8, 227c7b76dceSAaro Koskinen .val_bits = 16, 228c7b76dceSAaro Koskinen }; 229c7b76dceSAaro Koskinen 230612b95cdSGreg Kroah-Hartman static int retu_probe(struct i2c_client *i2c, const struct i2c_device_id *id) 231c7b76dceSAaro Koskinen { 23295e50f6aSAaro Koskinen struct retu_data const *rdat; 233c7b76dceSAaro Koskinen struct retu_dev *rdev; 234c7b76dceSAaro Koskinen int ret; 235c7b76dceSAaro Koskinen 23695e50f6aSAaro Koskinen if (i2c->addr > ARRAY_SIZE(retu_data)) 23795e50f6aSAaro Koskinen return -ENODEV; 23895e50f6aSAaro Koskinen rdat = &retu_data[i2c->addr - 1]; 23995e50f6aSAaro Koskinen 240c7b76dceSAaro Koskinen rdev = devm_kzalloc(&i2c->dev, sizeof(*rdev), GFP_KERNEL); 241c7b76dceSAaro Koskinen if (rdev == NULL) 242c7b76dceSAaro Koskinen return -ENOMEM; 243c7b76dceSAaro Koskinen 244c7b76dceSAaro Koskinen i2c_set_clientdata(i2c, rdev); 245c7b76dceSAaro Koskinen rdev->dev = &i2c->dev; 246c7b76dceSAaro Koskinen mutex_init(&rdev->mutex); 247c7b76dceSAaro Koskinen rdev->regmap = devm_regmap_init(&i2c->dev, &retu_bus, &i2c->dev, 248c7b76dceSAaro Koskinen &retu_config); 249c7b76dceSAaro Koskinen if (IS_ERR(rdev->regmap)) 250c7b76dceSAaro Koskinen return PTR_ERR(rdev->regmap); 251c7b76dceSAaro Koskinen 252c7b76dceSAaro Koskinen ret = retu_read(rdev, RETU_REG_ASICR); 253c7b76dceSAaro Koskinen if (ret < 0) { 25495e50f6aSAaro Koskinen dev_err(rdev->dev, "could not read %s revision: %d\n", 25595e50f6aSAaro Koskinen rdat->chip_name, ret); 256c7b76dceSAaro Koskinen return ret; 257c7b76dceSAaro Koskinen } 258c7b76dceSAaro Koskinen 25995e50f6aSAaro Koskinen dev_info(rdev->dev, "%s%s%s v%d.%d found\n", rdat->chip_name, 26095e50f6aSAaro Koskinen (ret & RETU_REG_ASICR_VILMA) ? " & " : "", 26195e50f6aSAaro Koskinen (ret & RETU_REG_ASICR_VILMA) ? rdat->companion_name : "", 262c7b76dceSAaro Koskinen (ret >> 4) & 0x7, ret & 0xf); 263c7b76dceSAaro Koskinen 26495e50f6aSAaro Koskinen /* Mask all interrupts. */ 26595e50f6aSAaro Koskinen ret = retu_write(rdev, rdat->irq_chip->mask_base, 0xffff); 266c7b76dceSAaro Koskinen if (ret < 0) 267c7b76dceSAaro Koskinen return ret; 268c7b76dceSAaro Koskinen 269c7b76dceSAaro Koskinen ret = regmap_add_irq_chip(rdev->regmap, i2c->irq, IRQF_ONESHOT, -1, 27095e50f6aSAaro Koskinen rdat->irq_chip, &rdev->irq_data); 271c7b76dceSAaro Koskinen if (ret < 0) 272c7b76dceSAaro Koskinen return ret; 273c7b76dceSAaro Koskinen 27495e50f6aSAaro Koskinen ret = mfd_add_devices(rdev->dev, -1, rdat->children, rdat->nchildren, 275c7b76dceSAaro Koskinen NULL, regmap_irq_chip_get_base(rdev->irq_data), 276c7b76dceSAaro Koskinen NULL); 277c7b76dceSAaro Koskinen if (ret < 0) { 278c7b76dceSAaro Koskinen regmap_del_irq_chip(i2c->irq, rdev->irq_data); 279c7b76dceSAaro Koskinen return ret; 280c7b76dceSAaro Koskinen } 281c7b76dceSAaro Koskinen 28295e50f6aSAaro Koskinen if (i2c->addr == 1 && !pm_power_off) { 283c7b76dceSAaro Koskinen retu_pm_power_off = rdev; 284c7b76dceSAaro Koskinen pm_power_off = retu_power_off; 285c7b76dceSAaro Koskinen } 286c7b76dceSAaro Koskinen 287c7b76dceSAaro Koskinen return 0; 288c7b76dceSAaro Koskinen } 289c7b76dceSAaro Koskinen 290*ed5c2f5fSUwe Kleine-König static void retu_remove(struct i2c_client *i2c) 291c7b76dceSAaro Koskinen { 292c7b76dceSAaro Koskinen struct retu_dev *rdev = i2c_get_clientdata(i2c); 293c7b76dceSAaro Koskinen 294c7b76dceSAaro Koskinen if (retu_pm_power_off == rdev) { 295c7b76dceSAaro Koskinen pm_power_off = NULL; 296c7b76dceSAaro Koskinen retu_pm_power_off = NULL; 297c7b76dceSAaro Koskinen } 298c7b76dceSAaro Koskinen mfd_remove_devices(rdev->dev); 299c7b76dceSAaro Koskinen regmap_del_irq_chip(i2c->irq, rdev->irq_data); 300c7b76dceSAaro Koskinen } 301c7b76dceSAaro Koskinen 302c7b76dceSAaro Koskinen static const struct i2c_device_id retu_id[] = { 303408bc03bSJavier Martinez Canillas { "retu", 0 }, 304408bc03bSJavier Martinez Canillas { "tahvo", 0 }, 305c7b76dceSAaro Koskinen { } 306c7b76dceSAaro Koskinen }; 307c7b76dceSAaro Koskinen MODULE_DEVICE_TABLE(i2c, retu_id); 308c7b76dceSAaro Koskinen 30946c20bdfSJavier Martinez Canillas static const struct of_device_id retu_of_match[] = { 31046c20bdfSJavier Martinez Canillas { .compatible = "nokia,retu" }, 31146c20bdfSJavier Martinez Canillas { .compatible = "nokia,tahvo" }, 31246c20bdfSJavier Martinez Canillas { } 31346c20bdfSJavier Martinez Canillas }; 31446c20bdfSJavier Martinez Canillas MODULE_DEVICE_TABLE(of, retu_of_match); 31546c20bdfSJavier Martinez Canillas 316c7b76dceSAaro Koskinen static struct i2c_driver retu_driver = { 317c7b76dceSAaro Koskinen .driver = { 318c7b76dceSAaro Koskinen .name = "retu-mfd", 31946c20bdfSJavier Martinez Canillas .of_match_table = retu_of_match, 320c7b76dceSAaro Koskinen }, 321c7b76dceSAaro Koskinen .probe = retu_probe, 322c7b76dceSAaro Koskinen .remove = retu_remove, 323c7b76dceSAaro Koskinen .id_table = retu_id, 324c7b76dceSAaro Koskinen }; 325c7b76dceSAaro Koskinen module_i2c_driver(retu_driver); 326c7b76dceSAaro Koskinen 327c7b76dceSAaro Koskinen MODULE_DESCRIPTION("Retu MFD driver"); 328c7b76dceSAaro Koskinen MODULE_AUTHOR("Juha Yrjölä"); 329c7b76dceSAaro Koskinen MODULE_AUTHOR("David Weinehall"); 330c7b76dceSAaro Koskinen MODULE_AUTHOR("Mikko Ylinen"); 331c7b76dceSAaro Koskinen MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); 332c7b76dceSAaro Koskinen MODULE_LICENSE("GPL"); 333