brintos

brintos / linux-shallow public Read only

0
0
Text · 7.6 KiB · 1d43458 Raw
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, &reg);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(&reg, 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