diff options
| author | Takashi Iwai <tiwai@suse.de> | 2011-08-08 14:30:29 +0200 | 
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2011-08-08 14:30:29 +0200 | 
| commit | 0a2d31b62dba9b5b92a38c67c9cc42630513662a (patch) | |
| tree | f755d74ec85248de645e10c45ed1a2ed467530f6 /drivers/mfd | |
| parent | 8039290a91c5dc4414093c086987a5d7738fe2fd (diff) | |
| parent | df944f66784e6d4f2f50739263a4947885d8b6ae (diff) | |
Merge branch 'fix/kconfig' into for-linus
Diffstat (limited to 'drivers/mfd')
36 files changed, 3255 insertions, 743 deletions
| diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0f09c057e796..21574bdf485f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -171,6 +171,37 @@ config MFD_TPS6586X  	  This driver can also be built as a module.  If so, the module  	  will be called tps6586x. +config MFD_TPS65910 +	bool "TPS65910 Power Management chip" +	depends on I2C=y && GPIOLIB +	select MFD_CORE +	select GPIO_TPS65910 +	help +	  if you say yes here you get support for the TPS65910 series of +	  Power Management chips. + +config MFD_TPS65912 +	bool +	depends on GPIOLIB + +config MFD_TPS65912_I2C +	bool "TPS95612 Power Management chip with I2C" +	select MFD_CORE +	select MFD_TPS65912 +	depends on I2C=y && GPIOLIB +	help +	  If you say yes here you get support for the TPS65912 series of +	  PM chips with I2C interface. + +config MFD_TPS65912_SPI +	bool "TPS65912 Power Management chip with SPI" +	select MFD_CORE +	select MFD_TPS65912 +	depends on SPI_MASTER && GPIOLIB +	help +	  If you say yes here you get support for the TPS65912 series of +	  PM chips with SPI interface. +  config MENELAUS  	bool "Texas Instruments TWL92330/Menelaus PM chip"  	depends on I2C=y && ARCH_OMAP2 @@ -218,7 +249,7 @@ config TWL4030_POWER  	  and load scripts controlling which resources are switched off/on  	  or reset when a sleep, wakeup or warm reset event occurs. -config TWL4030_CODEC +config MFD_TWL4030_AUDIO  	bool  	depends on TWL4030_CORE  	select MFD_CORE @@ -233,6 +264,12 @@ config TWL6030_PWM  	  Say yes here if you want support for TWL6030 PWM.  	  This is used to control charging LED brightness. +config TWL6040_CORE +	bool +	depends on TWL4030_CORE && GENERIC_HARDIRQS +	select MFD_CORE +	default n +  config MFD_STMPE  	bool "Support STMicroelectronics STMPE"  	depends on I2C=y && GENERIC_HARDIRQS @@ -656,8 +693,9 @@ config MFD_JANZ_CMODIO  	  CAN and GPIO controllers.  config MFD_JZ4740_ADC -	tristate "Support for the JZ4740 SoC ADC core" +	bool "Support for the JZ4740 SoC ADC core"  	select MFD_CORE +	select GENERIC_IRQ_CHIP  	depends on MACH_JZ4740  	help  	  Say yes here if you want support for the ADC unit in the JZ4740 SoC. @@ -719,14 +757,18 @@ config MFD_PM8XXX_IRQ  	  This is required to use certain other PM 8xxx features, such as GPIO  	  and MPP. -config MFD_TPS65910 -	bool "TPS65910 Power Management chip" -	depends on I2C=y && GPIOLIB +config TPS65911_COMPARATOR +	tristate + +config MFD_AAT2870_CORE +	bool "Support for the AnalogicTech AAT2870"  	select MFD_CORE -	select GPIO_TPS65910 +	depends on I2C=y && GPIOLIB  	help -	  if you say yes here you get support for the TPS65910 series of -	  Power Management chips. +	  If you say yes here you get support for the AAT2870. +	  This driver provides common support for accessing the device, +	  additional drivers must be enabled in order to use the +	  functionality of the device.  endif # MFD_SUPPORT diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index efe3cc33ed92..c58020303d18 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_MFD_TC6393XB)	+= tc6393xb.o tmio_core.o  obj-$(CONFIG_MFD_WM8400)	+= wm8400-core.o  wm831x-objs			:= wm831x-core.o wm831x-irq.o wm831x-otp.o +wm831x-objs			+= wm831x-auxadc.o  obj-$(CONFIG_MFD_WM831X)	+= wm831x.o  obj-$(CONFIG_MFD_WM831X_I2C)	+= wm831x-i2c.o  obj-$(CONFIG_MFD_WM831X_SPI)	+= wm831x-spi.o @@ -35,13 +36,19 @@ obj-$(CONFIG_MFD_WM8994)	+= wm8994-core.o wm8994-irq.o  obj-$(CONFIG_TPS6105X)		+= tps6105x.o  obj-$(CONFIG_TPS65010)		+= tps65010.o  obj-$(CONFIG_TPS6507X)		+= tps6507x.o +obj-$(CONFIG_MFD_TPS65910)	+= tps65910.o tps65910-irq.o +tps65912-objs                   := tps65912-core.o tps65912-irq.o +obj-$(CONFIG_MFD_TPS65912)	+= tps65912.o +obj-$(CONFIG_MFD_TPS65912_I2C)	+= tps65912-i2c.o +obj-$(CONFIG_MFD_TPS65912_SPI)  += tps65912-spi.o  obj-$(CONFIG_MENELAUS)		+= menelaus.o  obj-$(CONFIG_TWL4030_CORE)	+= twl-core.o twl4030-irq.o twl6030-irq.o  obj-$(CONFIG_TWL4030_MADC)      += twl4030-madc.o  obj-$(CONFIG_TWL4030_POWER)    += twl4030-power.o -obj-$(CONFIG_TWL4030_CODEC)	+= twl4030-codec.o +obj-$(CONFIG_MFD_TWL4030_AUDIO)	+= twl4030-audio.o  obj-$(CONFIG_TWL6030_PWM)	+= twl6030-pwm.o +obj-$(CONFIG_TWL6040_CORE)	+= twl6040-core.o twl6040-irq.o  obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o @@ -93,4 +100,5 @@ obj-$(CONFIG_MFD_CS5535)	+= cs5535-mfd.o  obj-$(CONFIG_MFD_OMAP_USB_HOST)	+= omap-usb-host.o  obj-$(CONFIG_MFD_PM8921_CORE) 	+= pm8921-core.o  obj-$(CONFIG_MFD_PM8XXX_IRQ) 	+= pm8xxx-irq.o -obj-$(CONFIG_MFD_TPS65910)	+= tps65910.o tps65910-irq.o +obj-$(CONFIG_TPS65911_COMPARATOR)	+= tps65911-comparator.o +obj-$(CONFIG_MFD_AAT2870_CORE)	+= aat2870-core.o diff --git a/drivers/mfd/aat2870-core.c b/drivers/mfd/aat2870-core.c new file mode 100644 index 000000000000..345dc658ef06 --- /dev/null +++ b/drivers/mfd/aat2870-core.c @@ -0,0 +1,535 @@ +/* + * linux/drivers/mfd/aat2870-core.c + * + * Copyright (c) 2011, NVIDIA Corporation. + * Author: Jin Park <jinyoungp@nvidia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/mfd/aat2870.h> +#include <linux/regulator/machine.h> + +static struct aat2870_register aat2870_regs[AAT2870_REG_NUM] = { +	/* readable, writeable, value */ +	{ 0, 1, 0x00 },	/* 0x00 AAT2870_BL_CH_EN */ +	{ 0, 1, 0x16 },	/* 0x01 AAT2870_BLM */ +	{ 0, 1, 0x16 },	/* 0x02 AAT2870_BLS */ +	{ 0, 1, 0x56 },	/* 0x03 AAT2870_BL1 */ +	{ 0, 1, 0x56 },	/* 0x04 AAT2870_BL2 */ +	{ 0, 1, 0x56 },	/* 0x05 AAT2870_BL3 */ +	{ 0, 1, 0x56 },	/* 0x06 AAT2870_BL4 */ +	{ 0, 1, 0x56 },	/* 0x07 AAT2870_BL5 */ +	{ 0, 1, 0x56 },	/* 0x08 AAT2870_BL6 */ +	{ 0, 1, 0x56 },	/* 0x09 AAT2870_BL7 */ +	{ 0, 1, 0x56 },	/* 0x0A AAT2870_BL8 */ +	{ 0, 1, 0x00 },	/* 0x0B AAT2870_FLR */ +	{ 0, 1, 0x03 },	/* 0x0C AAT2870_FM */ +	{ 0, 1, 0x03 },	/* 0x0D AAT2870_FS */ +	{ 0, 1, 0x10 },	/* 0x0E AAT2870_ALS_CFG0 */ +	{ 0, 1, 0x06 },	/* 0x0F AAT2870_ALS_CFG1 */ +	{ 0, 1, 0x00 },	/* 0x10 AAT2870_ALS_CFG2 */ +	{ 1, 0, 0x00 },	/* 0x11 AAT2870_AMB */ +	{ 0, 1, 0x00 },	/* 0x12 AAT2870_ALS0 */ +	{ 0, 1, 0x00 },	/* 0x13 AAT2870_ALS1 */ +	{ 0, 1, 0x00 },	/* 0x14 AAT2870_ALS2 */ +	{ 0, 1, 0x00 },	/* 0x15 AAT2870_ALS3 */ +	{ 0, 1, 0x00 },	/* 0x16 AAT2870_ALS4 */ +	{ 0, 1, 0x00 },	/* 0x17 AAT2870_ALS5 */ +	{ 0, 1, 0x00 },	/* 0x18 AAT2870_ALS6 */ +	{ 0, 1, 0x00 },	/* 0x19 AAT2870_ALS7 */ +	{ 0, 1, 0x00 },	/* 0x1A AAT2870_ALS8 */ +	{ 0, 1, 0x00 },	/* 0x1B AAT2870_ALS9 */ +	{ 0, 1, 0x00 },	/* 0x1C AAT2870_ALSA */ +	{ 0, 1, 0x00 },	/* 0x1D AAT2870_ALSB */ +	{ 0, 1, 0x00 },	/* 0x1E AAT2870_ALSC */ +	{ 0, 1, 0x00 },	/* 0x1F AAT2870_ALSD */ +	{ 0, 1, 0x00 },	/* 0x20 AAT2870_ALSE */ +	{ 0, 1, 0x00 },	/* 0x21 AAT2870_ALSF */ +	{ 0, 1, 0x00 },	/* 0x22 AAT2870_SUB_SET */ +	{ 0, 1, 0x00 },	/* 0x23 AAT2870_SUB_CTRL */ +	{ 0, 1, 0x00 },	/* 0x24 AAT2870_LDO_AB */ +	{ 0, 1, 0x00 },	/* 0x25 AAT2870_LDO_CD */ +	{ 0, 1, 0x00 },	/* 0x26 AAT2870_LDO_EN */ +}; + +static struct mfd_cell aat2870_devs[] = { +	{ +		.name = "aat2870-backlight", +		.id = AAT2870_ID_BL, +		.pdata_size = sizeof(struct aat2870_bl_platform_data), +	}, +	{ +		.name = "aat2870-regulator", +		.id = AAT2870_ID_LDOA, +		.pdata_size = sizeof(struct regulator_init_data), +	}, +	{ +		.name = "aat2870-regulator", +		.id = AAT2870_ID_LDOB, +		.pdata_size = sizeof(struct regulator_init_data), +	}, +	{ +		.name = "aat2870-regulator", +		.id = AAT2870_ID_LDOC, +		.pdata_size = sizeof(struct regulator_init_data), +	}, +	{ +		.name = "aat2870-regulator", +		.id = AAT2870_ID_LDOD, +		.pdata_size = sizeof(struct regulator_init_data), +	}, +}; + +static int __aat2870_read(struct aat2870_data *aat2870, u8 addr, u8 *val) +{ +	int ret; + +	if (addr >= AAT2870_REG_NUM) { +		dev_err(aat2870->dev, "Invalid address, 0x%02x\n", addr); +		return -EINVAL; +	} + +	if (!aat2870->reg_cache[addr].readable) { +		*val = aat2870->reg_cache[addr].value; +		goto out; +	} + +	ret = i2c_master_send(aat2870->client, &addr, 1); +	if (ret < 0) +		return ret; +	if (ret != 1) +		return -EIO; + +	ret = i2c_master_recv(aat2870->client, val, 1); +	if (ret < 0) +		return ret; +	if (ret != 1) +		return -EIO; + +out: +	dev_dbg(aat2870->dev, "read: addr=0x%02x, val=0x%02x\n", addr, *val); +	return 0; +} + +static int __aat2870_write(struct aat2870_data *aat2870, u8 addr, u8 val) +{ +	u8 msg[2]; +	int ret; + +	if (addr >= AAT2870_REG_NUM) { +		dev_err(aat2870->dev, "Invalid address, 0x%02x\n", addr); +		return -EINVAL; +	} + +	if (!aat2870->reg_cache[addr].writeable) { +		dev_err(aat2870->dev, "Address 0x%02x is not writeable\n", +			addr); +		return -EINVAL; +	} + +	msg[0] = addr; +	msg[1] = val; +	ret = i2c_master_send(aat2870->client, msg, 2); +	if (ret < 0) +		return ret; +	if (ret != 2) +		return -EIO; + +	aat2870->reg_cache[addr].value = val; + +	dev_dbg(aat2870->dev, "write: addr=0x%02x, val=0x%02x\n", addr, val); +	return 0; +} + +static int aat2870_read(struct aat2870_data *aat2870, u8 addr, u8 *val) +{ +	int ret; + +	mutex_lock(&aat2870->io_lock); +	ret = __aat2870_read(aat2870, addr, val); +	mutex_unlock(&aat2870->io_lock); + +	return ret; +} + +static int aat2870_write(struct aat2870_data *aat2870, u8 addr, u8 val) +{ +	int ret; + +	mutex_lock(&aat2870->io_lock); +	ret = __aat2870_write(aat2870, addr, val); +	mutex_unlock(&aat2870->io_lock); + +	return ret; +} + +static int aat2870_update(struct aat2870_data *aat2870, u8 addr, u8 mask, +			  u8 val) +{ +	int change; +	u8 old_val, new_val; +	int ret; + +	mutex_lock(&aat2870->io_lock); + +	ret = __aat2870_read(aat2870, addr, &old_val); +	if (ret) +		goto out_unlock; + +	new_val = (old_val & ~mask) | (val & mask); +	change = old_val != new_val; +	if (change) +		ret = __aat2870_write(aat2870, addr, new_val); + +out_unlock: +	mutex_unlock(&aat2870->io_lock); + +	return ret; +} + +static inline void aat2870_enable(struct aat2870_data *aat2870) +{ +	if (aat2870->en_pin >= 0) +		gpio_set_value(aat2870->en_pin, 1); + +	aat2870->is_enable = 1; +} + +static inline void aat2870_disable(struct aat2870_data *aat2870) +{ +	if (aat2870->en_pin >= 0) +		gpio_set_value(aat2870->en_pin, 0); + +	aat2870->is_enable = 0; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t aat2870_dump_reg(struct aat2870_data *aat2870, char *buf) +{ +	u8 addr, val; +	ssize_t count = 0; +	int ret; + +	count += sprintf(buf, "aat2870 registers\n"); +	for (addr = 0; addr < AAT2870_REG_NUM; addr++) { +		count += sprintf(buf + count, "0x%02x: ", addr); +		if (count >= PAGE_SIZE - 1) +			break; + +		ret = aat2870->read(aat2870, addr, &val); +		if (ret == 0) +			count += snprintf(buf + count, PAGE_SIZE - count, +					  "0x%02x", val); +		else +			count += snprintf(buf + count, PAGE_SIZE - count, +					  "<read fail: %d>", ret); + +		if (count >= PAGE_SIZE - 1) +			break; + +		count += snprintf(buf + count, PAGE_SIZE - count, "\n"); +		if (count >= PAGE_SIZE - 1) +			break; +	} + +	/* Truncate count; min() would cause a warning */ +	if (count >= PAGE_SIZE) +		count = PAGE_SIZE - 1; + +	return count; +} + +static int aat2870_reg_open_file(struct inode *inode, struct file *file) +{ +	file->private_data = inode->i_private; + +	return 0; +} + +static ssize_t aat2870_reg_read_file(struct file *file, char __user *user_buf, +				     size_t count, loff_t *ppos) +{ +	struct aat2870_data *aat2870 = file->private_data; +	char *buf; +	ssize_t ret; + +	buf = kmalloc(PAGE_SIZE, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	ret = aat2870_dump_reg(aat2870, buf); +	if (ret >= 0) +		ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + +	kfree(buf); + +	return ret; +} + +static ssize_t aat2870_reg_write_file(struct file *file, +				      const char __user *user_buf, size_t count, +				      loff_t *ppos) +{ +	struct aat2870_data *aat2870 = file->private_data; +	char buf[32]; +	int buf_size; +	char *start = buf; +	unsigned long addr, val; +	int ret; + +	buf_size = min(count, (sizeof(buf)-1)); +	if (copy_from_user(buf, user_buf, buf_size)) { +		dev_err(aat2870->dev, "Failed to copy from user\n"); +		return -EFAULT; +	} +	buf[buf_size] = 0; + +	while (*start == ' ') +		start++; + +	addr = simple_strtoul(start, &start, 16); +	if (addr >= AAT2870_REG_NUM) { +		dev_err(aat2870->dev, "Invalid address, 0x%lx\n", addr); +		return -EINVAL; +	} + +	while (*start == ' ') +		start++; + +	if (strict_strtoul(start, 16, &val)) +		return -EINVAL; + +	ret = aat2870->write(aat2870, (u8)addr, (u8)val); +	if (ret) +		return ret; + +	return buf_size; +} + +static const struct file_operations aat2870_reg_fops = { +	.open = aat2870_reg_open_file, +	.read = aat2870_reg_read_file, +	.write = aat2870_reg_write_file, +}; + +static void aat2870_init_debugfs(struct aat2870_data *aat2870) +{ +	aat2870->dentry_root = debugfs_create_dir("aat2870", NULL); +	if (!aat2870->dentry_root) { +		dev_warn(aat2870->dev, +			 "Failed to create debugfs root directory\n"); +		return; +	} + +	aat2870->dentry_reg = debugfs_create_file("regs", 0644, +						  aat2870->dentry_root, +						  aat2870, &aat2870_reg_fops); +	if (!aat2870->dentry_reg) +		dev_warn(aat2870->dev, +			 "Failed to create debugfs register file\n"); +} + +static void aat2870_uninit_debugfs(struct aat2870_data *aat2870) +{ +	debugfs_remove_recursive(aat2870->dentry_root); +} +#else +static inline void aat2870_init_debugfs(struct aat2870_data *aat2870) +{ +} + +static inline void aat2870_uninit_debugfs(struct aat2870_data *aat2870) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static int aat2870_i2c_probe(struct i2c_client *client, +			     const struct i2c_device_id *id) +{ +	struct aat2870_platform_data *pdata = client->dev.platform_data; +	struct aat2870_data *aat2870; +	int i, j; +	int ret = 0; + +	aat2870 = kzalloc(sizeof(struct aat2870_data), GFP_KERNEL); +	if (!aat2870) { +		dev_err(&client->dev, +			"Failed to allocate memory for aat2870\n"); +		ret = -ENOMEM; +		goto out; +	} + +	aat2870->dev = &client->dev; +	dev_set_drvdata(aat2870->dev, aat2870); + +	aat2870->client = client; +	i2c_set_clientdata(client, aat2870); + +	aat2870->reg_cache = aat2870_regs; + +	if (pdata->en_pin < 0) +		aat2870->en_pin = -1; +	else +		aat2870->en_pin = pdata->en_pin; + +	aat2870->init = pdata->init; +	aat2870->uninit = pdata->uninit; +	aat2870->read = aat2870_read; +	aat2870->write = aat2870_write; +	aat2870->update = aat2870_update; + +	mutex_init(&aat2870->io_lock); + +	if (aat2870->init) +		aat2870->init(aat2870); + +	if (aat2870->en_pin >= 0) { +		ret = gpio_request(aat2870->en_pin, "aat2870-en"); +		if (ret < 0) { +			dev_err(&client->dev, +				"Failed to request GPIO %d\n", aat2870->en_pin); +			goto out_kfree; +		} +		gpio_direction_output(aat2870->en_pin, 1); +	} + +	aat2870_enable(aat2870); + +	for (i = 0; i < pdata->num_subdevs; i++) { +		for (j = 0; j < ARRAY_SIZE(aat2870_devs); j++) { +			if ((pdata->subdevs[i].id == aat2870_devs[j].id) && +					!strcmp(pdata->subdevs[i].name, +						aat2870_devs[j].name)) { +				aat2870_devs[j].platform_data = +					pdata->subdevs[i].platform_data; +				break; +			} +		} +	} + +	ret = mfd_add_devices(aat2870->dev, 0, aat2870_devs, +			      ARRAY_SIZE(aat2870_devs), NULL, 0); +	if (ret != 0) { +		dev_err(aat2870->dev, "Failed to add subdev: %d\n", ret); +		goto out_disable; +	} + +	aat2870_init_debugfs(aat2870); + +	return 0; + +out_disable: +	aat2870_disable(aat2870); +	if (aat2870->en_pin >= 0) +		gpio_free(aat2870->en_pin); +out_kfree: +	kfree(aat2870); +out: +	return ret; +} + +static int aat2870_i2c_remove(struct i2c_client *client) +{ +	struct aat2870_data *aat2870 = i2c_get_clientdata(client); + +	aat2870_uninit_debugfs(aat2870); + +	mfd_remove_devices(aat2870->dev); +	aat2870_disable(aat2870); +	if (aat2870->en_pin >= 0) +		gpio_free(aat2870->en_pin); +	if (aat2870->uninit) +		aat2870->uninit(aat2870); +	kfree(aat2870); + +	return 0; +} + +#ifdef CONFIG_PM +static int aat2870_i2c_suspend(struct i2c_client *client, pm_message_t state) +{ +	struct aat2870_data *aat2870 = i2c_get_clientdata(client); + +	aat2870_disable(aat2870); + +	return 0; +} + +static int aat2870_i2c_resume(struct i2c_client *client) +{ +	struct aat2870_data *aat2870 = i2c_get_clientdata(client); +	struct aat2870_register *reg = NULL; +	int i; + +	aat2870_enable(aat2870); + +	/* restore registers */ +	for (i = 0; i < AAT2870_REG_NUM; i++) { +		reg = &aat2870->reg_cache[i]; +		if (reg->writeable) +			aat2870->write(aat2870, i, reg->value); +	} + +	return 0; +} +#else +#define aat2870_i2c_suspend	NULL +#define aat2870_i2c_resume	NULL +#endif /* CONFIG_PM */ + +static struct i2c_device_id aat2870_i2c_id_table[] = { +	{ "aat2870", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, aat2870_i2c_id_table); + +static struct i2c_driver aat2870_i2c_driver = { +	.driver = { +		.name	= "aat2870", +		.owner	= THIS_MODULE, +	}, +	.probe		= aat2870_i2c_probe, +	.remove		= aat2870_i2c_remove, +	.suspend	= aat2870_i2c_suspend, +	.resume		= aat2870_i2c_resume, +	.id_table	= aat2870_i2c_id_table, +}; + +static int __init aat2870_init(void) +{ +	return i2c_add_driver(&aat2870_i2c_driver); +} +subsys_initcall(aat2870_init); + +static void __exit aat2870_exit(void) +{ +	i2c_del_driver(&aat2870_i2c_driver); +} +module_exit(aat2870_exit); + +MODULE_DESCRIPTION("Core support for the AnalogicTech AAT2870"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jin Park <jinyoungp@nvidia.com>"); diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c index 3d7dce671b93..56ba1943c91d 100644 --- a/drivers/mfd/ab3550-core.c +++ b/drivers/mfd/ab3550-core.c @@ -879,20 +879,13 @@ static ssize_t ab3550_bank_write(struct file *file,  	size_t count, loff_t *ppos)  {  	struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private; -	char buf[32]; -	int buf_size;  	unsigned long user_bank;  	int err;  	/* Get userspace string and assure termination */ -	buf_size = min(count, (sizeof(buf) - 1)); -	if (copy_from_user(buf, user_buf, buf_size)) -		return -EFAULT; -	buf[buf_size] = 0; - -	err = strict_strtoul(buf, 0, &user_bank); +	err = kstrtoul_from_user(user_buf, count, 0, &user_bank);  	if (err) -		return -EINVAL; +		return err;  	if (user_bank >= AB3550_NUM_BANKS) {  		dev_err(&ab->i2c_client[0]->dev, @@ -902,7 +895,7 @@ static ssize_t ab3550_bank_write(struct file *file,  	ab->debug_bank = user_bank; -	return buf_size; +	return count;  }  static int ab3550_address_print(struct seq_file *s, void *p) @@ -923,27 +916,21 @@ static ssize_t ab3550_address_write(struct file *file,  	size_t count, loff_t *ppos)  {  	struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private; -	char buf[32]; -	int buf_size;  	unsigned long user_address;  	int err;  	/* Get userspace string and assure termination */ -	buf_size = min(count, (sizeof(buf) - 1)); -	if (copy_from_user(buf, user_buf, buf_size)) -		return -EFAULT; -	buf[buf_size] = 0; - -	err = strict_strtoul(buf, 0, &user_address); +	err = kstrtoul_from_user(user_buf, count, 0, &user_address);  	if (err) -		return -EINVAL; +		return err; +  	if (user_address > 0xff) {  		dev_err(&ab->i2c_client[0]->dev,  			"debugfs error input > 0xff\n");  		return -EINVAL;  	}  	ab->debug_address = user_address; -	return buf_size; +	return count;  }  static int ab3550_val_print(struct seq_file *s, void *p) @@ -971,21 +958,15 @@ static ssize_t ab3550_val_write(struct file *file,  	size_t count, loff_t *ppos)  {  	struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private; -	char buf[32]; -	int buf_size;  	unsigned long user_val;  	int err;  	u8 regvalue;  	/* Get userspace string and assure termination */ -	buf_size = min(count, (sizeof(buf)-1)); -	if (copy_from_user(buf, user_buf, buf_size)) -		return -EFAULT; -	buf[buf_size] = 0; - -	err = strict_strtoul(buf, 0, &user_val); +	err = kstrtoul_from_user(user_buf, count, 0, &user_val);  	if (err) -		return -EINVAL; +		return err; +  	if (user_val > 0xff) {  		dev_err(&ab->i2c_client[0]->dev,  			"debugfs error input > 0xff\n"); @@ -1002,7 +983,7 @@ static ssize_t ab3550_val_write(struct file *file,  	if (err)  		return -EINVAL; -	return buf_size; +	return count;  }  static const struct file_operations ab3550_bank_fops = { diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index fc0c1af1566e..387705e494b9 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -363,7 +363,7 @@ static void ab8500_irq_remove(struct ab8500 *ab8500)  	}  } -static struct resource ab8500_gpio_resources[] = { +static struct resource __devinitdata ab8500_gpio_resources[] = {  	{  		.name	= "GPIO_INT6",  		.start	= AB8500_INT_GPIO6R, @@ -372,7 +372,7 @@ static struct resource ab8500_gpio_resources[] = {  	}  }; -static struct resource ab8500_gpadc_resources[] = { +static struct resource __devinitdata ab8500_gpadc_resources[] = {  	{  		.name	= "HW_CONV_END",  		.start	= AB8500_INT_GP_HW_ADC_CONV_END, @@ -387,7 +387,7 @@ static struct resource ab8500_gpadc_resources[] = {  	},  }; -static struct resource ab8500_rtc_resources[] = { +static struct resource __devinitdata ab8500_rtc_resources[] = {  	{  		.name	= "60S",  		.start	= AB8500_INT_RTC_60S, @@ -402,7 +402,7 @@ static struct resource ab8500_rtc_resources[] = {  	},  }; -static struct resource ab8500_poweronkey_db_resources[] = { +static struct resource __devinitdata ab8500_poweronkey_db_resources[] = {  	{  		.name	= "ONKEY_DBF",  		.start	= AB8500_INT_PON_KEY1DB_F, @@ -417,20 +417,47 @@ static struct resource ab8500_poweronkey_db_resources[] = {  	},  }; -static struct resource ab8500_bm_resources[] = { +static struct resource __devinitdata ab8500_av_acc_detect_resources[] = {  	{ -		.name = "MAIN_EXT_CH_NOT_OK", -		.start = AB8500_INT_MAIN_EXT_CH_NOT_OK, -		.end = AB8500_INT_MAIN_EXT_CH_NOT_OK, -		.flags = IORESOURCE_IRQ, +	       .name = "ACC_DETECT_1DB_F", +	       .start = AB8500_INT_ACC_DETECT_1DB_F, +	       .end = AB8500_INT_ACC_DETECT_1DB_F, +	       .flags = IORESOURCE_IRQ,  	},  	{ -		.name = "BATT_OVV", -		.start = AB8500_INT_BATT_OVV, -		.end = AB8500_INT_BATT_OVV, -		.flags = IORESOURCE_IRQ, +	       .name = "ACC_DETECT_1DB_R", +	       .start = AB8500_INT_ACC_DETECT_1DB_R, +	       .end = AB8500_INT_ACC_DETECT_1DB_R, +	       .flags = IORESOURCE_IRQ, +	}, +	{ +	       .name = "ACC_DETECT_21DB_F", +	       .start = AB8500_INT_ACC_DETECT_21DB_F, +	       .end = AB8500_INT_ACC_DETECT_21DB_F, +	       .flags = IORESOURCE_IRQ, +	}, +	{ +	       .name = "ACC_DETECT_21DB_R", +	       .start = AB8500_INT_ACC_DETECT_21DB_R, +	       .end = AB8500_INT_ACC_DETECT_21DB_R, +	       .flags = IORESOURCE_IRQ, +	}, +	{ +	       .name = "ACC_DETECT_22DB_F", +	       .start = AB8500_INT_ACC_DETECT_22DB_F, +	       .end = AB8500_INT_ACC_DETECT_22DB_F, +	       .flags = IORESOURCE_IRQ,  	},  	{ +	       .name = "ACC_DETECT_22DB_R", +	       .start = AB8500_INT_ACC_DETECT_22DB_R, +	       .end = AB8500_INT_ACC_DETECT_22DB_R, +	       .flags = IORESOURCE_IRQ, +	}, +}; + +static struct resource __devinitdata ab8500_charger_resources[] = { +	{  		.name = "MAIN_CH_UNPLUG_DET",  		.start = AB8500_INT_MAIN_CH_UNPLUG_DET,  		.end = AB8500_INT_MAIN_CH_UNPLUG_DET, @@ -443,27 +470,27 @@ static struct resource ab8500_bm_resources[] = {  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "VBUS_DET_F", -		.start = AB8500_INT_VBUS_DET_F, -		.end = AB8500_INT_VBUS_DET_F, -		.flags = IORESOURCE_IRQ, -	}, -	{  		.name = "VBUS_DET_R",  		.start = AB8500_INT_VBUS_DET_R,  		.end = AB8500_INT_VBUS_DET_R,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "BAT_CTRL_INDB", -		.start = AB8500_INT_BAT_CTRL_INDB, -		.end = AB8500_INT_BAT_CTRL_INDB, +		.name = "VBUS_DET_F", +		.start = AB8500_INT_VBUS_DET_F, +		.end = AB8500_INT_VBUS_DET_F,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "CH_WD_EXP", -		.start = AB8500_INT_CH_WD_EXP, -		.end = AB8500_INT_CH_WD_EXP, +		.name = "USB_LINK_STATUS", +		.start = AB8500_INT_USB_LINK_STATUS, +		.end = AB8500_INT_USB_LINK_STATUS, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "USB_CHARGE_DET_DONE", +		.start = AB8500_INT_USB_CHG_DET_DONE, +		.end = AB8500_INT_USB_CHG_DET_DONE,  		.flags = IORESOURCE_IRQ,  	},  	{ @@ -473,21 +500,60 @@ static struct resource ab8500_bm_resources[] = {  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "NCONV_ACCU", -		.start = AB8500_INT_CCN_CONV_ACC, -		.end = AB8500_INT_CCN_CONV_ACC, +		.name = "USB_CH_TH_PROT_R", +		.start = AB8500_INT_USB_CH_TH_PROT_R, +		.end = AB8500_INT_USB_CH_TH_PROT_R,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "LOW_BAT_F", -		.start = AB8500_INT_LOW_BAT_F, -		.end = AB8500_INT_LOW_BAT_F, +		.name = "USB_CH_TH_PROT_F", +		.start = AB8500_INT_USB_CH_TH_PROT_F, +		.end = AB8500_INT_USB_CH_TH_PROT_F,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "LOW_BAT_R", -		.start = AB8500_INT_LOW_BAT_R, -		.end = AB8500_INT_LOW_BAT_R, +		.name = "MAIN_EXT_CH_NOT_OK", +		.start = AB8500_INT_MAIN_EXT_CH_NOT_OK, +		.end = AB8500_INT_MAIN_EXT_CH_NOT_OK, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "MAIN_CH_TH_PROT_R", +		.start = AB8500_INT_MAIN_CH_TH_PROT_R, +		.end = AB8500_INT_MAIN_CH_TH_PROT_R, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "MAIN_CH_TH_PROT_F", +		.start = AB8500_INT_MAIN_CH_TH_PROT_F, +		.end = AB8500_INT_MAIN_CH_TH_PROT_F, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "USB_CHARGER_NOT_OKR", +		.start = AB8500_INT_USB_CHARGER_NOT_OK, +		.end = AB8500_INT_USB_CHARGER_NOT_OK, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "USB_CHARGER_NOT_OKF", +		.start = AB8500_INT_USB_CHARGER_NOT_OKF, +		.end = AB8500_INT_USB_CHARGER_NOT_OKF, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "CH_WD_EXP", +		.start = AB8500_INT_CH_WD_EXP, +		.end = AB8500_INT_CH_WD_EXP, +		.flags = IORESOURCE_IRQ, +	}, +}; + +static struct resource __devinitdata ab8500_btemp_resources[] = { +	{ +		.name = "BAT_CTRL_INDB", +		.start = AB8500_INT_BAT_CTRL_INDB, +		.end = AB8500_INT_BAT_CTRL_INDB,  		.flags = IORESOURCE_IRQ,  	},  	{ @@ -503,38 +569,55 @@ static struct resource ab8500_bm_resources[] = {  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "USB_CHARGER_NOT_OKR", -		.start = AB8500_INT_USB_CHARGER_NOT_OK, -		.end = AB8500_INT_USB_CHARGER_NOT_OK, +		.name = "BTEMP_LOW_MEDIUM", +		.start = AB8500_INT_BTEMP_LOW_MEDIUM, +		.end = AB8500_INT_BTEMP_LOW_MEDIUM,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "USB_CHARGE_DET_DONE", -		.start = AB8500_INT_USB_CHG_DET_DONE, -		.end = AB8500_INT_USB_CHG_DET_DONE, +		.name = "BTEMP_MEDIUM_HIGH", +		.start = AB8500_INT_BTEMP_MEDIUM_HIGH, +		.end = AB8500_INT_BTEMP_MEDIUM_HIGH,  		.flags = IORESOURCE_IRQ,  	}, +}; + +static struct resource __devinitdata ab8500_fg_resources[] = {  	{ -		.name = "USB_CH_TH_PROT_R", -		.start = AB8500_INT_USB_CH_TH_PROT_R, -		.end = AB8500_INT_USB_CH_TH_PROT_R, +		.name = "NCONV_ACCU", +		.start = AB8500_INT_CCN_CONV_ACC, +		.end = AB8500_INT_CCN_CONV_ACC,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "MAIN_CH_TH_PROT_R", -		.start = AB8500_INT_MAIN_CH_TH_PROT_R, -		.end = AB8500_INT_MAIN_CH_TH_PROT_R, +		.name = "BATT_OVV", +		.start = AB8500_INT_BATT_OVV, +		.end = AB8500_INT_BATT_OVV,  		.flags = IORESOURCE_IRQ,  	},  	{ -		.name = "USB_CHARGER_NOT_OKF", -		.start = AB8500_INT_USB_CHARGER_NOT_OKF, -		.end = AB8500_INT_USB_CHARGER_NOT_OKF, +		.name = "LOW_BAT_F", +		.start = AB8500_INT_LOW_BAT_F, +		.end = AB8500_INT_LOW_BAT_F, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "LOW_BAT_R", +		.start = AB8500_INT_LOW_BAT_R, +		.end = AB8500_INT_LOW_BAT_R, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "CC_INT_CALIB", +		.start = AB8500_INT_CC_INT_CALIB, +		.end = AB8500_INT_CC_INT_CALIB,  		.flags = IORESOURCE_IRQ,  	},  }; -static struct resource ab8500_debug_resources[] = { +static struct resource __devinitdata ab8500_chargalg_resources[] = {}; + +static struct resource __devinitdata ab8500_debug_resources[] = {  	{  		.name	= "IRQ_FIRST",  		.start	= AB8500_INT_MAIN_EXT_CH_NOT_OK, @@ -549,7 +632,7 @@ static struct resource ab8500_debug_resources[] = {  	},  }; -static struct resource ab8500_usb_resources[] = { +static struct resource __devinitdata ab8500_usb_resources[] = {  	{  		.name = "ID_WAKEUP_R",  		.start = AB8500_INT_ID_WAKEUP_R, @@ -580,9 +663,21 @@ static struct resource ab8500_usb_resources[] = {  		.end = AB8500_INT_USB_LINK_STATUS,  		.flags = IORESOURCE_IRQ,  	}, +	{ +		.name = "USB_ADP_PROBE_PLUG", +		.start = AB8500_INT_ADP_PROBE_PLUG, +		.end = AB8500_INT_ADP_PROBE_PLUG, +		.flags = IORESOURCE_IRQ, +	}, +	{ +		.name = "USB_ADP_PROBE_UNPLUG", +		.start = AB8500_INT_ADP_PROBE_UNPLUG, +		.end = AB8500_INT_ADP_PROBE_UNPLUG, +		.flags = IORESOURCE_IRQ, +	},  }; -static struct resource ab8500_temp_resources[] = { +static struct resource __devinitdata ab8500_temp_resources[] = {  	{  		.name  = "AB8500_TEMP_WARM",  		.start = AB8500_INT_TEMP_WARM, @@ -591,7 +686,7 @@ static struct resource ab8500_temp_resources[] = {  	},  }; -static struct mfd_cell ab8500_devs[] = { +static struct mfd_cell __devinitdata ab8500_devs[] = {  #ifdef CONFIG_DEBUG_FS  	{  		.name = "ab8500-debug", @@ -621,11 +716,33 @@ static struct mfd_cell ab8500_devs[] = {  		.resources = ab8500_rtc_resources,  	},  	{ -		.name = "ab8500-bm", -		.num_resources = ARRAY_SIZE(ab8500_bm_resources), -		.resources = ab8500_bm_resources, +		.name = "ab8500-charger", +		.num_resources = ARRAY_SIZE(ab8500_charger_resources), +		.resources = ab8500_charger_resources, +	}, +	{ +		.name = "ab8500-btemp", +		.num_resources = ARRAY_SIZE(ab8500_btemp_resources), +		.resources = ab8500_btemp_resources, +	}, +	{ +		.name = "ab8500-fg", +		.num_resources = ARRAY_SIZE(ab8500_fg_resources), +		.resources = ab8500_fg_resources, +	}, +	{ +		.name = "ab8500-chargalg", +		.num_resources = ARRAY_SIZE(ab8500_chargalg_resources), +		.resources = ab8500_chargalg_resources, +	}, +	{ +		.name = "ab8500-acc-det", +		.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), +		.resources = ab8500_av_acc_detect_resources, +	}, +	{ +		.name = "ab8500-codec",  	}, -	{ .name = "ab8500-codec", },  	{  		.name = "ab8500-usb",  		.num_resources = ARRAY_SIZE(ab8500_usb_resources), diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 64748e42ac03..64bdeeb1c11a 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -419,20 +419,13 @@ static ssize_t ab8500_bank_write(struct file *file,  	size_t count, loff_t *ppos)  {  	struct device *dev = ((struct seq_file *)(file->private_data))->private; -	char buf[32]; -	int buf_size;  	unsigned long user_bank;  	int err;  	/* Get userspace string and assure termination */ -	buf_size = min(count, (sizeof(buf) - 1)); -	if (copy_from_user(buf, user_buf, buf_size)) -		return -EFAULT; -	buf[buf_size] = 0; - -	err = strict_strtoul(buf, 0, &user_bank); +	err = kstrtoul_from_user(user_buf, count, 0, &user_bank);  	if (err) -		return -EINVAL; +		return err;  	if (user_bank >= AB8500_NUM_BANKS) {  		dev_err(dev, "debugfs error input > number of banks\n"); @@ -441,7 +434,7 @@ static ssize_t ab8500_bank_write(struct file *file,  	debug_bank = user_bank; -	return buf_size; +	return count;  }  static int ab8500_address_print(struct seq_file *s, void *p) @@ -459,26 +452,20 @@ static ssize_t ab8500_address_write(struct file *file,  	size_t count, loff_t *ppos)  {  	struct device *dev = ((struct seq_file *)(file->private_data))->private; -	char buf[32]; -	int buf_size;  	unsigned long user_address;  	int err;  	/* Get userspace string and assure termination */ -	buf_size = min(count, (sizeof(buf) - 1)); -	if (copy_from_user(buf, user_buf, buf_size)) -		return -EFAULT; -	buf[buf_size] = 0; - -	err = strict_strtoul(buf, 0, &user_address); +	err = kstrtoul_from_user(user_buf, count, 0, &user_address);  	if (err) -		return -EINVAL; +		return err; +  	if (user_address > 0xff) {  		dev_err(dev, "debugfs error input > 0xff\n");  		return -EINVAL;  	}  	debug_address = user_address; -	return buf_size; +	return count;  }  static int ab8500_val_print(struct seq_file *s, void *p) @@ -509,20 +496,14 @@ static ssize_t ab8500_val_write(struct file *file,  	size_t count, loff_t *ppos)  {  	struct device *dev = ((struct seq_file *)(file->private_data))->private; -	char buf[32]; -	int buf_size;  	unsigned long user_val;  	int err;  	/* Get userspace string and assure termination */ -	buf_size = min(count, (sizeof(buf)-1)); -	if (copy_from_user(buf, user_buf, buf_size)) -		return -EFAULT; -	buf[buf_size] = 0; - -	err = strict_strtoul(buf, 0, &user_val); +	err = kstrtoul_from_user(user_buf, count, 0, &user_val);  	if (err) -		return -EINVAL; +		return err; +  	if (user_val > 0xff) {  		dev_err(dev, "debugfs error input > 0xff\n");  		return -EINVAL; @@ -534,7 +515,7 @@ static ssize_t ab8500_val_write(struct file *file,  		return -EINVAL;  	} -	return buf_size; +	return count;  }  static const struct file_operations ab8500_bank_fops = { diff --git a/drivers/mfd/asic3.c b/drivers/mfd/asic3.c index c27fd1fc3b86..c71ae09430c5 100644 --- a/drivers/mfd/asic3.c +++ b/drivers/mfd/asic3.c @@ -619,6 +619,7 @@ static void asic3_clk_disable(struct asic3 *asic, struct asic3_clk *clk)  /* MFD cells (SPI, PWM, LED, DS1WM, MMC) */  static struct ds1wm_driver_data ds1wm_pdata = {  	.active_high = 1, +	.reset_recover_delay = 1,  };  static struct resource ds1wm_resources[] = { diff --git a/drivers/mfd/htc-pasic3.c b/drivers/mfd/htc-pasic3.c index 2808bd125d13..04c7093d6499 100644 --- a/drivers/mfd/htc-pasic3.c +++ b/drivers/mfd/htc-pasic3.c @@ -99,6 +99,7 @@ static int ds1wm_disable(struct platform_device *pdev)  static struct ds1wm_driver_data ds1wm_pdata = {  	.active_high = 0, +	.reset_recover_delay = 1,  };  static struct resource ds1wm_resources[] __initdata = { diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c index a0bd0cf05af3..21131c7b0f1e 100644 --- a/drivers/mfd/jz4740-adc.c +++ b/drivers/mfd/jz4740-adc.c @@ -56,7 +56,7 @@ struct jz4740_adc {  	void __iomem *base;  	int irq; -	int irq_base; +	struct irq_chip_generic *gc;  	struct clk *clk;  	atomic_t clk_ref; @@ -64,63 +64,17 @@ struct jz4740_adc {  	spinlock_t lock;  }; -static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq, -	bool masked) -{ -	unsigned long flags; -	uint8_t val; - -	irq -= adc->irq_base; - -	spin_lock_irqsave(&adc->lock, flags); - -	val = readb(adc->base + JZ_REG_ADC_CTRL); -	if (masked) -		val |= BIT(irq); -	else -		val &= ~BIT(irq); -	writeb(val, adc->base + JZ_REG_ADC_CTRL); - -	spin_unlock_irqrestore(&adc->lock, flags); -} - -static void jz4740_adc_irq_mask(struct irq_data *data) -{ -	struct jz4740_adc *adc = irq_data_get_irq_chip_data(data); -	jz4740_adc_irq_set_masked(adc, data->irq, true); -} - -static void jz4740_adc_irq_unmask(struct irq_data *data) -{ -	struct jz4740_adc *adc = irq_data_get_irq_chip_data(data); -	jz4740_adc_irq_set_masked(adc, data->irq, false); -} - -static void jz4740_adc_irq_ack(struct irq_data *data) -{ -	struct jz4740_adc *adc = irq_data_get_irq_chip_data(data); -	unsigned int irq = data->irq - adc->irq_base; -	writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS); -} - -static struct irq_chip jz4740_adc_irq_chip = { -	.name = "jz4740-adc", -	.irq_mask = jz4740_adc_irq_mask, -	.irq_unmask = jz4740_adc_irq_unmask, -	.irq_ack = jz4740_adc_irq_ack, -}; -  static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)  { -	struct jz4740_adc *adc = irq_desc_get_handler_data(desc); +	struct irq_chip_generic *gc = irq_desc_get_handler_data(desc);  	uint8_t status;  	unsigned int i; -	status = readb(adc->base + JZ_REG_ADC_STATUS); +	status = readb(gc->reg_base + JZ_REG_ADC_STATUS);  	for (i = 0; i < 5; ++i) {  		if (status & BIT(i)) -			generic_handle_irq(adc->irq_base + i); +			generic_handle_irq(gc->irq_base + i);  	}  } @@ -249,10 +203,12 @@ const struct mfd_cell jz4740_adc_cells[] = {  static int __devinit jz4740_adc_probe(struct platform_device *pdev)  { -	int ret; +	struct irq_chip_generic *gc; +	struct irq_chip_type *ct;  	struct jz4740_adc *adc;  	struct resource *mem_base; -	int irq; +	int ret; +	int irq_base;  	adc = kmalloc(sizeof(*adc), GFP_KERNEL);  	if (!adc) { @@ -267,9 +223,9 @@ static int __devinit jz4740_adc_probe(struct platform_device *pdev)  		goto err_free;  	} -	adc->irq_base = platform_get_irq(pdev, 1); -	if (adc->irq_base < 0) { -		ret = adc->irq_base; +	irq_base = platform_get_irq(pdev, 1); +	if (irq_base < 0) { +		ret = irq_base;  		dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);  		goto err_free;  	} @@ -309,20 +265,28 @@ static int __devinit jz4740_adc_probe(struct platform_device *pdev)  	platform_set_drvdata(pdev, adc); -	for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) { -		irq_set_chip_data(irq, adc); -		irq_set_chip_and_handler(irq, &jz4740_adc_irq_chip, -					 handle_level_irq); -	} +	gc = irq_alloc_generic_chip("INTC", 1, irq_base, adc->base, +		handle_level_irq); + +	ct = gc->chip_types; +	ct->regs.mask = JZ_REG_ADC_CTRL; +	ct->regs.ack = JZ_REG_ADC_STATUS; +	ct->chip.irq_mask = irq_gc_mask_set_bit; +	ct->chip.irq_unmask = irq_gc_mask_clr_bit; +	ct->chip.irq_ack = irq_gc_ack; + +	irq_setup_generic_chip(gc, IRQ_MSK(5), 0, 0, IRQ_NOPROBE | IRQ_LEVEL); + +	adc->gc = gc; -	irq_set_handler_data(adc->irq, adc); +	irq_set_handler_data(adc->irq, gc);  	irq_set_chained_handler(adc->irq, jz4740_adc_irq_demux);  	writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);  	writeb(0xff, adc->base + JZ_REG_ADC_CTRL);  	ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells, -		ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base); +		ARRAY_SIZE(jz4740_adc_cells), mem_base, irq_base);  	if (ret < 0)  		goto err_clk_put; @@ -347,6 +311,8 @@ static int __devexit jz4740_adc_remove(struct platform_device *pdev)  	mfd_remove_devices(&pdev->dev); +	irq_remove_generic_chip(adc->gc, IRQ_MSK(5), IRQ_NOPROBE | IRQ_LEVEL, 0); +	kfree(adc->gc);  	irq_set_handler_data(adc->irq, NULL);  	irq_set_chained_handler(adc->irq, NULL); diff --git a/drivers/mfd/lpc_sch.c b/drivers/mfd/lpc_sch.c index ea3f52c07ef7..ea1169b04779 100644 --- a/drivers/mfd/lpc_sch.c +++ b/drivers/mfd/lpc_sch.c @@ -37,6 +37,9 @@  #define GPIOBASE	0x44  #define GPIO_IO_SIZE	64 +#define WDTBASE		0x84 +#define WDT_IO_SIZE	64 +  static struct resource smbus_sch_resource = {  		.flags = IORESOURCE_IO,  }; @@ -59,6 +62,18 @@ static struct mfd_cell lpc_sch_cells[] = {  	},  }; +static struct resource wdt_sch_resource = { +		.flags = IORESOURCE_IO, +}; + +static struct mfd_cell tunnelcreek_cells[] = { +	{ +		.name = "tunnelcreek_wdt", +		.num_resources = 1, +		.resources = &wdt_sch_resource, +	}, +}; +  static struct pci_device_id lpc_sch_ids[] = {  	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) },  	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC) }, @@ -72,6 +87,7 @@ static int __devinit lpc_sch_probe(struct pci_dev *dev,  	unsigned int base_addr_cfg;  	unsigned short base_addr;  	int i; +	int ret;  	pci_read_config_dword(dev, SMBASE, &base_addr_cfg);  	if (!(base_addr_cfg & (1 << 31))) { @@ -104,8 +120,39 @@ static int __devinit lpc_sch_probe(struct pci_dev *dev,  	for (i=0; i < ARRAY_SIZE(lpc_sch_cells); i++)  		lpc_sch_cells[i].id = id->device; -	return mfd_add_devices(&dev->dev, 0, +	ret = mfd_add_devices(&dev->dev, 0,  			lpc_sch_cells, ARRAY_SIZE(lpc_sch_cells), NULL, 0); +	if (ret) +		goto out_dev; + +	if (id->device == PCI_DEVICE_ID_INTEL_ITC_LPC) { +		pci_read_config_dword(dev, WDTBASE, &base_addr_cfg); +		if (!(base_addr_cfg & (1 << 31))) { +			dev_err(&dev->dev, "Decode of the WDT I/O range disabled\n"); +			ret = -ENODEV; +			goto out_dev; +		} +		base_addr = (unsigned short)base_addr_cfg; +		if (base_addr == 0) { +			dev_err(&dev->dev, "I/O space for WDT uninitialized\n"); +			ret = -ENODEV; +			goto out_dev; +		} + +		wdt_sch_resource.start = base_addr; +		wdt_sch_resource.end = base_addr + WDT_IO_SIZE - 1; + +		for (i = 0; i < ARRAY_SIZE(tunnelcreek_cells); i++) +			tunnelcreek_cells[i].id = id->device; + +		ret = mfd_add_devices(&dev->dev, 0, tunnelcreek_cells, +			ARRAY_SIZE(tunnelcreek_cells), NULL, 0); +	} + +	return ret; +out_dev: +	mfd_remove_devices(&dev->dev); +	return ret;  }  static void __devexit lpc_sch_remove(struct pci_dev *dev) diff --git a/drivers/mfd/max8997-irq.c b/drivers/mfd/max8997-irq.c index 638bf7e4d3b3..09274cf7c33b 100644 --- a/drivers/mfd/max8997-irq.c +++ b/drivers/mfd/max8997-irq.c @@ -58,8 +58,6 @@ static struct i2c_client *get_i2c(struct max8997_dev *max8997,  	default:  		return ERR_PTR(-EINVAL);  	} - -	return ERR_PTR(-EINVAL);  }  struct max8997_irq_data { diff --git a/drivers/mfd/max8998.c b/drivers/mfd/max8998.c index 9ec7570f5b81..de4096aee248 100644 --- a/drivers/mfd/max8998.c +++ b/drivers/mfd/max8998.c @@ -39,6 +39,8 @@ static struct mfd_cell max8998_devs[] = {  		.name = "max8998-pmic",  	}, {  		.name = "max8998-rtc", +	}, { +		.name = "max8998-battery",  	},  }; diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c index 855219526ccb..29601e7d606d 100644 --- a/drivers/mfd/omap-usb-host.c +++ b/drivers/mfd/omap-usb-host.c @@ -26,7 +26,6 @@  #include <linux/spinlock.h>  #include <linux/gpio.h>  #include <plat/usb.h> -#include <linux/pm_runtime.h>  #define USBHS_DRIVER_NAME	"usbhs-omap"  #define OMAP_EHCI_DEVICE	"ehci-omap" @@ -147,6 +146,9 @@  struct usbhs_hcd_omap { +	struct clk			*usbhost_ick; +	struct clk			*usbhost_hs_fck; +	struct clk			*usbhost_fs_fck;  	struct clk			*xclk60mhsp1_ck;  	struct clk			*xclk60mhsp2_ck;  	struct clk			*utmi_p1_fck; @@ -156,6 +158,8 @@ struct usbhs_hcd_omap {  	struct clk			*usbhost_p2_fck;  	struct clk			*usbtll_p2_fck;  	struct clk			*init_60m_fclk; +	struct clk			*usbtll_fck; +	struct clk			*usbtll_ick;  	void __iomem			*uhh_base;  	void __iomem			*tll_base; @@ -349,13 +353,46 @@ static int __devinit usbhs_omap_probe(struct platform_device *pdev)  	omap->platdata.ehci_data = pdata->ehci_data;  	omap->platdata.ohci_data = pdata->ohci_data; -	pm_runtime_enable(&pdev->dev); +	omap->usbhost_ick = clk_get(dev, "usbhost_ick"); +	if (IS_ERR(omap->usbhost_ick)) { +		ret =  PTR_ERR(omap->usbhost_ick); +		dev_err(dev, "usbhost_ick failed error:%d\n", ret); +		goto err_end; +	} + +	omap->usbhost_hs_fck = clk_get(dev, "hs_fck"); +	if (IS_ERR(omap->usbhost_hs_fck)) { +		ret = PTR_ERR(omap->usbhost_hs_fck); +		dev_err(dev, "usbhost_hs_fck failed error:%d\n", ret); +		goto err_usbhost_ick; +	} + +	omap->usbhost_fs_fck = clk_get(dev, "fs_fck"); +	if (IS_ERR(omap->usbhost_fs_fck)) { +		ret = PTR_ERR(omap->usbhost_fs_fck); +		dev_err(dev, "usbhost_fs_fck failed error:%d\n", ret); +		goto err_usbhost_hs_fck; +	} + +	omap->usbtll_fck = clk_get(dev, "usbtll_fck"); +	if (IS_ERR(omap->usbtll_fck)) { +		ret = PTR_ERR(omap->usbtll_fck); +		dev_err(dev, "usbtll_fck failed error:%d\n", ret); +		goto err_usbhost_fs_fck; +	} + +	omap->usbtll_ick = clk_get(dev, "usbtll_ick"); +	if (IS_ERR(omap->usbtll_ick)) { +		ret = PTR_ERR(omap->usbtll_ick); +		dev_err(dev, "usbtll_ick failed error:%d\n", ret); +		goto err_usbtll_fck; +	}  	omap->utmi_p1_fck = clk_get(dev, "utmi_p1_gfclk");  	if (IS_ERR(omap->utmi_p1_fck)) {  		ret = PTR_ERR(omap->utmi_p1_fck);  		dev_err(dev, "utmi_p1_gfclk failed error:%d\n",	ret); -		goto err_end; +		goto err_usbtll_ick;  	}  	omap->xclk60mhsp1_ck = clk_get(dev, "xclk60mhsp1_ck"); @@ -485,8 +522,22 @@ err_xclk60mhsp1_ck:  err_utmi_p1_fck:  	clk_put(omap->utmi_p1_fck); +err_usbtll_ick: +	clk_put(omap->usbtll_ick); + +err_usbtll_fck: +	clk_put(omap->usbtll_fck); + +err_usbhost_fs_fck: +	clk_put(omap->usbhost_fs_fck); + +err_usbhost_hs_fck: +	clk_put(omap->usbhost_hs_fck); + +err_usbhost_ick: +	clk_put(omap->usbhost_ick); +  err_end: -	pm_runtime_disable(&pdev->dev);  	kfree(omap);  end_probe: @@ -520,7 +571,11 @@ static int __devexit usbhs_omap_remove(struct platform_device *pdev)  	clk_put(omap->utmi_p2_fck);  	clk_put(omap->xclk60mhsp1_ck);  	clk_put(omap->utmi_p1_fck); -	pm_runtime_disable(&pdev->dev); +	clk_put(omap->usbtll_ick); +	clk_put(omap->usbtll_fck); +	clk_put(omap->usbhost_fs_fck); +	clk_put(omap->usbhost_hs_fck); +	clk_put(omap->usbhost_ick);  	kfree(omap);  	return 0; @@ -640,6 +695,7 @@ static int usbhs_enable(struct device *dev)  	struct usbhs_omap_platform_data	*pdata = &omap->platdata;  	unsigned long			flags = 0;  	int				ret = 0; +	unsigned long			timeout;  	unsigned			reg;  	dev_dbg(dev, "starting TI HSUSB Controller\n"); @@ -652,7 +708,11 @@ static int usbhs_enable(struct device *dev)  	if (omap->count > 0)  		goto end_count; -	pm_runtime_get_sync(dev); +	clk_enable(omap->usbhost_ick); +	clk_enable(omap->usbhost_hs_fck); +	clk_enable(omap->usbhost_fs_fck); +	clk_enable(omap->usbtll_fck); +	clk_enable(omap->usbtll_ick);  	if (pdata->ehci_data->phy_reset) {  		if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0])) { @@ -676,6 +736,50 @@ static int usbhs_enable(struct device *dev)  	omap->usbhs_rev = usbhs_read(omap->uhh_base, OMAP_UHH_REVISION);  	dev_dbg(dev, "OMAP UHH_REVISION 0x%x\n", omap->usbhs_rev); +	/* perform TLL soft reset, and wait until reset is complete */ +	usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG, +			OMAP_USBTLL_SYSCONFIG_SOFTRESET); + +	/* Wait for TLL reset to complete */ +	timeout = jiffies + msecs_to_jiffies(1000); +	while (!(usbhs_read(omap->tll_base, OMAP_USBTLL_SYSSTATUS) +			& OMAP_USBTLL_SYSSTATUS_RESETDONE)) { +		cpu_relax(); + +		if (time_after(jiffies, timeout)) { +			dev_dbg(dev, "operation timed out\n"); +			ret = -EINVAL; +			goto err_tll; +		} +	} + +	dev_dbg(dev, "TLL RESET DONE\n"); + +	/* (1<<3) = no idle mode only for initial debugging */ +	usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG, +			OMAP_USBTLL_SYSCONFIG_ENAWAKEUP | +			OMAP_USBTLL_SYSCONFIG_SIDLEMODE | +			OMAP_USBTLL_SYSCONFIG_AUTOIDLE); + +	/* Put UHH in NoIdle/NoStandby mode */ +	reg = usbhs_read(omap->uhh_base, OMAP_UHH_SYSCONFIG); +	if (is_omap_usbhs_rev1(omap)) { +		reg |= (OMAP_UHH_SYSCONFIG_ENAWAKEUP +				| OMAP_UHH_SYSCONFIG_SIDLEMODE +				| OMAP_UHH_SYSCONFIG_CACTIVITY +				| OMAP_UHH_SYSCONFIG_MIDLEMODE); +		reg &= ~OMAP_UHH_SYSCONFIG_AUTOIDLE; + + +	} else if (is_omap_usbhs_rev2(omap)) { +		reg &= ~OMAP4_UHH_SYSCONFIG_IDLEMODE_CLEAR; +		reg |= OMAP4_UHH_SYSCONFIG_NOIDLE; +		reg &= ~OMAP4_UHH_SYSCONFIG_STDBYMODE_CLEAR; +		reg |= OMAP4_UHH_SYSCONFIG_NOSTDBY; +	} + +	usbhs_write(omap->uhh_base, OMAP_UHH_SYSCONFIG, reg); +  	reg = usbhs_read(omap->uhh_base, OMAP_UHH_HOSTCONFIG);  	/* setup ULPI bypass and burst configurations */  	reg |= (OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN @@ -815,8 +919,6 @@ end_count:  	return 0;  err_tll: -	pm_runtime_put_sync(dev); -	spin_unlock_irqrestore(&omap->lock, flags);  	if (pdata->ehci_data->phy_reset) {  		if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0]))  			gpio_free(pdata->ehci_data->reset_gpio_port[0]); @@ -824,6 +926,13 @@ err_tll:  		if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[1]))  			gpio_free(pdata->ehci_data->reset_gpio_port[1]);  	} + +	clk_disable(omap->usbtll_ick); +	clk_disable(omap->usbtll_fck); +	clk_disable(omap->usbhost_fs_fck); +	clk_disable(omap->usbhost_hs_fck); +	clk_disable(omap->usbhost_ick); +	spin_unlock_irqrestore(&omap->lock, flags);  	return ret;  } @@ -889,14 +998,18 @@ static void usbhs_disable(struct device *dev)  	if (is_omap_usbhs_rev2(omap)) {  		if (is_ehci_tll_mode(pdata->port_mode[0])) -			clk_enable(omap->usbtll_p1_fck); +			clk_disable(omap->usbtll_p1_fck);  		if (is_ehci_tll_mode(pdata->port_mode[1])) -			clk_enable(omap->usbtll_p2_fck); +			clk_disable(omap->usbtll_p2_fck);  		clk_disable(omap->utmi_p2_fck);  		clk_disable(omap->utmi_p1_fck);  	} -	pm_runtime_put_sync(dev); +	clk_disable(omap->usbtll_ick); +	clk_disable(omap->usbtll_fck); +	clk_disable(omap->usbhost_fs_fck); +	clk_disable(omap->usbhost_hs_fck); +	clk_disable(omap->usbhost_ick);  	/* The gpio_free migh sleep; so unlock the spinlock */  	spin_unlock_irqrestore(&omap->lock, flags); diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 7ab7746631d4..2963689cf45c 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -228,7 +228,7 @@ int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,  EXPORT_SYMBOL_GPL(stmpe_block_write);  /** - * stmpe_set_altfunc: set the alternate function for STMPE pins + * stmpe_set_altfunc()- set the alternate function for STMPE pins   * @stmpe:	Device to configure   * @pins:	Bitmask of pins to affect   * @block:	block to enable alternate functions for diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h index 0dbdc4e8cd77..e4ee38956583 100644 --- a/drivers/mfd/stmpe.h +++ b/drivers/mfd/stmpe.h @@ -42,6 +42,7 @@ struct stmpe_variant_block {   * @id_mask:	bits valid in CHIPID register for comparison with id_val   * @num_gpios:	number of GPIOS   * @af_bits:	number of bits used to specify the alternate function + * @regs: variant specific registers.   * @blocks:	list of blocks present on this device   * @num_blocks:	number of blocks present on this device   * @num_irqs:	number of internal IRQs available on this device diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c index ad715bf49cac..71bc835324d8 100644 --- a/drivers/mfd/tc6387xb.c +++ b/drivers/mfd/tc6387xb.c @@ -177,7 +177,7 @@ static int __devinit tc6387xb_probe(struct platform_device *dev)  	if (ret)  		goto err_resource; -	tc6387xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); +	tc6387xb->scr = ioremap(rscr->start, resource_size(rscr));  	if (!tc6387xb->scr) {  		ret = -ENOMEM;  		goto err_ioremap; diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index 69272e4e3459..696879e2eef7 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -287,12 +287,8 @@ static __devinitdata struct i2c_board_info timberdale_saa7706_i2c_board_info = {  static __devinitdata struct timb_radio_platform_data  	timberdale_radio_platform_data = {  	.i2c_adapter = 0, -	.tuner = { -		.info = &timberdale_tef6868_i2c_board_info -	}, -	.dsp = { -		.info = &timberdale_saa7706_i2c_board_info -	} +	.tuner = &timberdale_tef6868_i2c_board_info, +	.dsp = &timberdale_saa7706_i2c_board_info  };  static const __devinitconst struct resource timberdale_video_resources[] = { diff --git a/drivers/mfd/tps65910.c b/drivers/mfd/tps65910.c index 2229e66d80db..6f5b8cf2f652 100644 --- a/drivers/mfd/tps65910.c +++ b/drivers/mfd/tps65910.c @@ -147,12 +147,11 @@ static int tps65910_i2c_probe(struct i2c_client *i2c,  	if (init_data == NULL)  		return -ENOMEM; -	init_data->irq = pmic_plat_data->irq; -	init_data->irq_base = pmic_plat_data->irq; -  	tps65910 = kzalloc(sizeof(struct tps65910), GFP_KERNEL); -	if (tps65910 == NULL) +	if (tps65910 == NULL) { +		kfree(init_data);  		return -ENOMEM; +	}  	i2c_set_clientdata(i2c, tps65910);  	tps65910->dev = &i2c->dev; @@ -168,17 +167,22 @@ static int tps65910_i2c_probe(struct i2c_client *i2c,  	if (ret < 0)  		goto err; +	init_data->irq = pmic_plat_data->irq; +	init_data->irq_base = pmic_plat_data->irq; +  	tps65910_gpio_init(tps65910, pmic_plat_data->gpio_base);  	ret = tps65910_irq_init(tps65910, init_data->irq, init_data);  	if (ret < 0)  		goto err; +	kfree(init_data);  	return ret;  err:  	mfd_remove_devices(tps65910->dev);  	kfree(tps65910); +	kfree(init_data);  	return ret;  } @@ -187,6 +191,7 @@ static int tps65910_i2c_remove(struct i2c_client *i2c)  	struct tps65910 *tps65910 = i2c_get_clientdata(i2c);  	mfd_remove_devices(tps65910->dev); +	tps65910_irq_exit(tps65910);  	kfree(tps65910);  	return 0; diff --git a/drivers/mfd/tps65911-comparator.c b/drivers/mfd/tps65911-comparator.c index 3d2dc56a3d40..e7ff783aa31e 100644 --- a/drivers/mfd/tps65911-comparator.c +++ b/drivers/mfd/tps65911-comparator.c @@ -125,7 +125,7 @@ static DEVICE_ATTR(comp2_threshold, S_IRUGO, comp_threshold_show, NULL);  static __devinit int tps65911_comparator_probe(struct platform_device *pdev)  {  	struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); -	struct tps65910_platform_data *pdata = dev_get_platdata(tps65910->dev); +	struct tps65910_board *pdata = dev_get_platdata(tps65910->dev);  	int ret;  	ret = comp_threshold_set(tps65910, COMP1,  pdata->vmbch_threshold); @@ -157,6 +157,8 @@ static __devexit int tps65911_comparator_remove(struct platform_device *pdev)  	struct tps65910 *tps65910;  	tps65910 = dev_get_drvdata(pdev->dev.parent); +	device_remove_file(&pdev->dev, &dev_attr_comp2_threshold); +	device_remove_file(&pdev->dev, &dev_attr_comp1_threshold);  	return 0;  } diff --git a/drivers/mfd/tps65912-core.c b/drivers/mfd/tps65912-core.c new file mode 100644 index 000000000000..955bc00e4b20 --- /dev/null +++ b/drivers/mfd/tps65912-core.c @@ -0,0 +1,177 @@ +/* + * tps65912-core.c  --  TI TPS65912x + * + * Copyright 2011 Texas Instruments Inc. + * + * Author: Margarita Olaya Cabrera <magi@slimlogic.co.uk> + * + *  This program is free software; you can redistribute it and/or modify it + *  under  the terms of the GNU General  Public License as published by the + *  Free Software Foundation;  either version 2 of the License, or (at your + *  option) any later version. + * + *  This driver is based on wm8350 implementation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65912.h> + +static struct mfd_cell tps65912s[] = { +	{ +		.name = "tps65912-pmic", +	}, +}; + +int tps65912_set_bits(struct tps65912 *tps65912, u8 reg, u8 mask) +{ +	u8 data; +	int err; + +	mutex_lock(&tps65912->io_mutex); + +	err = tps65912->read(tps65912, reg, 1, &data); +	if (err) { +		dev_err(tps65912->dev, "Read from reg 0x%x failed\n", reg); +		goto out; +	} + +	data |= mask; +	err = tps65912->write(tps65912, reg, 1, &data); +	if (err) +		dev_err(tps65912->dev, "Write to reg 0x%x failed\n", reg); + +out: +	mutex_unlock(&tps65912->io_mutex); +	return err; +} +EXPORT_SYMBOL_GPL(tps65912_set_bits); + +int tps65912_clear_bits(struct tps65912 *tps65912, u8 reg, u8 mask) +{ +	u8 data; +	int err; + +	mutex_lock(&tps65912->io_mutex); +	err = tps65912->read(tps65912, reg, 1, &data); +	if (err) { +		dev_err(tps65912->dev, "Read from reg 0x%x failed\n", reg); +		goto out; +	} + +	data &= ~mask; +	err = tps65912->write(tps65912, reg, 1, &data); +	if (err) +		dev_err(tps65912->dev, "Write to reg 0x%x failed\n", reg); + +out: +	mutex_unlock(&tps65912->io_mutex); +	return err; +} +EXPORT_SYMBOL_GPL(tps65912_clear_bits); + +static inline int tps65912_read(struct tps65912 *tps65912, u8 reg) +{ +	u8 val; +	int err; + +	err = tps65912->read(tps65912, reg, 1, &val); +	if (err < 0) +		return err; + +	return val; +} + +static inline int tps65912_write(struct tps65912 *tps65912, u8 reg, u8 val) +{ +	return tps65912->write(tps65912, reg, 1, &val); +} + +int tps65912_reg_read(struct tps65912 *tps65912, u8 reg) +{ +	int data; + +	mutex_lock(&tps65912->io_mutex); + +	data = tps65912_read(tps65912, reg); +	if (data < 0) +		dev_err(tps65912->dev, "Read from reg 0x%x failed\n", reg); + +	mutex_unlock(&tps65912->io_mutex); +	return data; +} +EXPORT_SYMBOL_GPL(tps65912_reg_read); + +int tps65912_reg_write(struct tps65912 *tps65912, u8 reg, u8 val) +{ +	int err; + +	mutex_lock(&tps65912->io_mutex); + +	err = tps65912_write(tps65912, reg, val); +	if (err < 0) +		dev_err(tps65912->dev, "Write for reg 0x%x failed\n", reg); + +	mutex_unlock(&tps65912->io_mutex); +	return err; +} +EXPORT_SYMBOL_GPL(tps65912_reg_write); + +int tps65912_device_init(struct tps65912 *tps65912) +{ +	struct tps65912_board *pmic_plat_data = tps65912->dev->platform_data; +	struct tps65912_platform_data *init_data; +	int ret, dcdc_avs, value; + +	init_data = kzalloc(sizeof(struct tps65912_platform_data), GFP_KERNEL); +	if (init_data == NULL) +		return -ENOMEM; + +	init_data->irq = pmic_plat_data->irq; +	init_data->irq_base = pmic_plat_data->irq; + +	mutex_init(&tps65912->io_mutex); +	dev_set_drvdata(tps65912->dev, tps65912); + +	dcdc_avs = (pmic_plat_data->is_dcdc1_avs << 0 | +			pmic_plat_data->is_dcdc2_avs  << 1 | +				pmic_plat_data->is_dcdc3_avs << 2 | +					pmic_plat_data->is_dcdc4_avs << 3); +	if (dcdc_avs) { +		tps65912->read(tps65912, TPS65912_I2C_SPI_CFG, 1, &value); +		dcdc_avs |= value; +		tps65912->write(tps65912, TPS65912_I2C_SPI_CFG, 1, &dcdc_avs); +	} + +	ret = mfd_add_devices(tps65912->dev, -1, +			      tps65912s, ARRAY_SIZE(tps65912s), +			      NULL, 0); +	if (ret < 0) +		goto err; + +	ret = tps65912_irq_init(tps65912, init_data->irq, init_data); +	if (ret < 0) +		goto err; + +	return ret; + +err: +	kfree(init_data); +	mfd_remove_devices(tps65912->dev); +	kfree(tps65912); +	return ret; +} + +void tps65912_device_exit(struct tps65912 *tps65912) +{ +	mfd_remove_devices(tps65912->dev); +	kfree(tps65912); +} + +MODULE_AUTHOR("Margarita Olaya	<magi@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS65912x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/tps65912-i2c.c b/drivers/mfd/tps65912-i2c.c new file mode 100644 index 000000000000..c041f2c3d2bd --- /dev/null +++ b/drivers/mfd/tps65912-i2c.c @@ -0,0 +1,139 @@ +/* + * tps65912-i2c.c  --  I2C access for TI TPS65912x PMIC + * + * Copyright 2011 Texas Instruments Inc. + * + * Author: Margarita Olaya Cabrera <magi@slimlogic.co.uk> + * + *  This program is free software; you can redistribute it and/or modify it + *  under  the terms of the GNU General  Public License as published by the + *  Free Software Foundation;  either version 2 of the License, or (at your + *  option) any later version. + * + *  This driver is based on wm8350 implementation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65912.h> + +static int tps65912_i2c_read(struct tps65912 *tps65912, u8 reg, +				  int bytes, void *dest) +{ +	struct i2c_client *i2c = tps65912->control_data; +	struct i2c_msg xfer[2]; +	int ret; + +	/* Write register */ +	xfer[0].addr = i2c->addr; +	xfer[0].flags = 0; +	xfer[0].len = 1; +	xfer[0].buf = ® + +	/* Read data */ +	xfer[1].addr = i2c->addr; +	xfer[1].flags = I2C_M_RD; +	xfer[1].len = bytes; +	xfer[1].buf = dest; + +	ret = i2c_transfer(i2c->adapter, xfer, 2); +	if (ret == 2) +		ret = 0; +	else if (ret >= 0) +		ret = -EIO; +	return ret; +} + +static int tps65912_i2c_write(struct tps65912 *tps65912, u8 reg, +				   int bytes, void *src) +{ +	struct i2c_client *i2c = tps65912->control_data; +	/* we add 1 byte for device register */ +	u8 msg[TPS6591X_MAX_REGISTER + 1]; +	int ret; + +	if (bytes > TPS6591X_MAX_REGISTER) +		return -EINVAL; + +	msg[0] = reg; +	memcpy(&msg[1], src, bytes); + +	ret = i2c_master_send(i2c, msg, bytes + 1); +	if (ret < 0) +		return ret; +	if (ret != bytes + 1) +		return -EIO; + +	return 0; +} + +static int tps65912_i2c_probe(struct i2c_client *i2c, +			    const struct i2c_device_id *id) +{ +	struct tps65912 *tps65912; + +	tps65912 = kzalloc(sizeof(struct tps65912), GFP_KERNEL); +	if (tps65912 == NULL) +		return -ENOMEM; + +	i2c_set_clientdata(i2c, tps65912); +	tps65912->dev = &i2c->dev; +	tps65912->control_data = i2c; +	tps65912->read = tps65912_i2c_read; +	tps65912->write = tps65912_i2c_write; + +	return tps65912_device_init(tps65912); +} + +static int tps65912_i2c_remove(struct i2c_client *i2c) +{ +	struct tps65912 *tps65912 = i2c_get_clientdata(i2c); + +	tps65912_device_exit(tps65912); + +	return 0; +} + +static const struct i2c_device_id tps65912_i2c_id[] = { +	{"tps65912", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tps65912_i2c_id); + +static struct i2c_driver tps65912_i2c_driver = { +	.driver = { +		   .name = "tps65912", +		   .owner = THIS_MODULE, +	}, +	.probe = tps65912_i2c_probe, +	.remove = tps65912_i2c_remove, +	.id_table = tps65912_i2c_id, +}; + +static int __init tps65912_i2c_init(void) +{ +	int ret; + +	ret = i2c_add_driver(&tps65912_i2c_driver); +	if (ret != 0) +		pr_err("Failed to register TPS65912 I2C driver: %d\n", ret); + +	return ret; +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps65912_i2c_init); + +static void __exit tps65912_i2c_exit(void) +{ +	i2c_del_driver(&tps65912_i2c_driver); +} +module_exit(tps65912_i2c_exit); + +MODULE_AUTHOR("Margarita Olaya	<magi@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS6591x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/tps65912-irq.c b/drivers/mfd/tps65912-irq.c new file mode 100644 index 000000000000..d360a83a2738 --- /dev/null +++ b/drivers/mfd/tps65912-irq.c @@ -0,0 +1,224 @@ +/* + * tps65912-irq.c  --  TI TPS6591x + * + * Copyright 2011 Texas Instruments Inc. + * + * Author: Margarita Olaya <magi@slimlogic.co.uk> + * + *  This program is free software; you can redistribute it and/or modify it + *  under  the terms of the GNU General  Public License as published by the + *  Free Software Foundation;  either version 2 of the License, or (at your + *  option) any later version. + * + * This driver is based on wm8350 implementation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65912.h> + +static inline int irq_to_tps65912_irq(struct tps65912 *tps65912, +							int irq) +{ +	return irq - tps65912->irq_base; +} + +/* + * This is a threaded IRQ handler so can access I2C/SPI.  Since the + * IRQ handler explicitly clears the IRQ it handles the IRQ line + * will be reasserted and the physical IRQ will be handled again if + * another interrupt is asserted while we run - in the normal course + * of events this is a rare occurrence so we save I2C/SPI reads. We're + * also assuming that it's rare to get lots of interrupts firing + * simultaneously so try to minimise I/O. + */ +static irqreturn_t tps65912_irq(int irq, void *irq_data) +{ +	struct tps65912 *tps65912 = irq_data; +	u32 irq_sts; +	u32 irq_mask; +	u8 reg; +	int i; + + +	tps65912->read(tps65912, TPS65912_INT_STS, 1, ®); +	irq_sts = reg; +	tps65912->read(tps65912, TPS65912_INT_STS2, 1, ®); +	irq_sts |= reg << 8; +	tps65912->read(tps65912, TPS65912_INT_STS3, 1, ®); +	irq_sts |= reg << 16; +	tps65912->read(tps65912, TPS65912_INT_STS4, 1, ®); +	irq_sts |= reg << 24; + +	tps65912->read(tps65912, TPS65912_INT_MSK, 1, ®); +	irq_mask = reg; +	tps65912->read(tps65912, TPS65912_INT_MSK2, 1, ®); +	irq_mask |= reg << 8; +	tps65912->read(tps65912, TPS65912_INT_MSK3, 1, ®); +	irq_mask |= reg << 16; +	tps65912->read(tps65912, TPS65912_INT_MSK4, 1, ®); +	irq_mask |= reg << 24; + +	irq_sts &= ~irq_mask; +	if (!irq_sts) +		return IRQ_NONE; + +	for (i = 0; i < tps65912->irq_num; i++) { +		if (!(irq_sts & (1 << i))) +			continue; + +		handle_nested_irq(tps65912->irq_base + i); +	} + +	/* Write the STS register back to clear IRQs we handled */ +	reg = irq_sts & 0xFF; +	irq_sts >>= 8; +	if (reg) +		tps65912->write(tps65912, TPS65912_INT_STS, 1, ®); +	reg = irq_sts & 0xFF; +	irq_sts >>= 8; +	if (reg) +		tps65912->write(tps65912, TPS65912_INT_STS2, 1, ®); +	reg = irq_sts & 0xFF; +	irq_sts >>= 8; +	if (reg) +		tps65912->write(tps65912, TPS65912_INT_STS3, 1, ®); +	reg = irq_sts & 0xFF; +	if (reg) +		tps65912->write(tps65912, TPS65912_INT_STS4, 1, ®); + +	return IRQ_HANDLED; +} + +static void tps65912_irq_lock(struct irq_data *data) +{ +	struct tps65912 *tps65912 = irq_data_get_irq_chip_data(data); + +	mutex_lock(&tps65912->irq_lock); +} + +static void tps65912_irq_sync_unlock(struct irq_data *data) +{ +	struct tps65912 *tps65912 = irq_data_get_irq_chip_data(data); +	u32 reg_mask; +	u8 reg; + +	tps65912->read(tps65912, TPS65912_INT_MSK, 1, ®); +	reg_mask = reg; +	tps65912->read(tps65912, TPS65912_INT_MSK2, 1, ®); +	reg_mask |= reg << 8; +	tps65912->read(tps65912, TPS65912_INT_MSK3, 1, ®); +	reg_mask |= reg << 16; +	tps65912->read(tps65912, TPS65912_INT_MSK4, 1, ®); +	reg_mask |= reg << 24; + +	if (tps65912->irq_mask != reg_mask) { +		reg = tps65912->irq_mask & 0xFF; +		tps65912->write(tps65912, TPS65912_INT_MSK, 1, ®); +		reg = tps65912->irq_mask >> 8 & 0xFF; +		tps65912->write(tps65912, TPS65912_INT_MSK2, 1, ®); +		reg = tps65912->irq_mask >> 16 & 0xFF; +		tps65912->write(tps65912, TPS65912_INT_MSK3, 1, ®); +		reg = tps65912->irq_mask >> 24 & 0xFF; +		tps65912->write(tps65912, TPS65912_INT_MSK4, 1, ®); +	} + +	mutex_unlock(&tps65912->irq_lock); +} + +static void tps65912_irq_enable(struct irq_data *data) +{ +	struct tps65912 *tps65912 = irq_data_get_irq_chip_data(data); + +	tps65912->irq_mask &= ~(1 << irq_to_tps65912_irq(tps65912, data->irq)); +} + +static void tps65912_irq_disable(struct irq_data *data) +{ +	struct tps65912 *tps65912 = irq_data_get_irq_chip_data(data); + +	tps65912->irq_mask |= (1 << irq_to_tps65912_irq(tps65912, data->irq)); +} + +static struct irq_chip tps65912_irq_chip = { +	.name = "tps65912", +	.irq_bus_lock = tps65912_irq_lock, +	.irq_bus_sync_unlock = tps65912_irq_sync_unlock, +	.irq_disable = tps65912_irq_disable, +	.irq_enable = tps65912_irq_enable, +}; + +int tps65912_irq_init(struct tps65912 *tps65912, int irq, +			    struct tps65912_platform_data *pdata) +{ +	int ret, cur_irq; +	int flags = IRQF_ONESHOT; +	u8 reg; + +	if (!irq) { +		dev_warn(tps65912->dev, "No interrupt support, no core IRQ\n"); +		return 0; +	} + +	if (!pdata || !pdata->irq_base) { +		dev_warn(tps65912->dev, "No interrupt support, no IRQ base\n"); +		return 0; +	} + +	/* Clear unattended interrupts */ +	tps65912->read(tps65912, TPS65912_INT_STS, 1, ®); +	tps65912->write(tps65912, TPS65912_INT_STS, 1, ®); +	tps65912->read(tps65912, TPS65912_INT_STS2, 1, ®); +	tps65912->write(tps65912, TPS65912_INT_STS2, 1, ®); +	tps65912->read(tps65912, TPS65912_INT_STS3, 1, ®); +	tps65912->write(tps65912, TPS65912_INT_STS3, 1, ®); +	tps65912->read(tps65912, TPS65912_INT_STS4, 1, ®); +	tps65912->write(tps65912, TPS65912_INT_STS4, 1, ®); + +	/* Mask top level interrupts */ +	tps65912->irq_mask = 0xFFFFFFFF; + +	mutex_init(&tps65912->irq_lock); +	tps65912->chip_irq = irq; +	tps65912->irq_base = pdata->irq_base; + +	tps65912->irq_num = TPS65912_NUM_IRQ; + +	/* Register with genirq */ +	for (cur_irq = tps65912->irq_base; +	     cur_irq < tps65912->irq_num + tps65912->irq_base; +	     cur_irq++) { +		irq_set_chip_data(cur_irq, tps65912); +		irq_set_chip_and_handler(cur_irq, &tps65912_irq_chip, +					 handle_edge_irq); +		irq_set_nested_thread(cur_irq, 1); +		/* ARM needs us to explicitly flag the IRQ as valid +		 * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM +		set_irq_flags(cur_irq, IRQF_VALID); +#else +		irq_set_noprobe(cur_irq); +#endif +	} + +	ret = request_threaded_irq(irq, NULL, tps65912_irq, flags, +				   "tps65912", tps65912); + +	irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); +	if (ret != 0) +		dev_err(tps65912->dev, "Failed to request IRQ: %d\n", ret); + +	return ret; +} + +int tps65912_irq_exit(struct tps65912 *tps65912) +{ +	free_irq(tps65912->chip_irq, tps65912); +	return 0; +} diff --git a/drivers/mfd/tps65912-spi.c b/drivers/mfd/tps65912-spi.c new file mode 100644 index 000000000000..6d71e0d25744 --- /dev/null +++ b/drivers/mfd/tps65912-spi.c @@ -0,0 +1,142 @@ +/* + * tps65912-spi.c  --  SPI access for TI TPS65912x PMIC + * + * Copyright 2011 Texas Instruments Inc. + * + * Author: Margarita Olaya Cabrera <magi@slimlogic.co.uk> + * + *  This program is free software; you can redistribute it and/or modify it + *  under  the terms of the GNU General  Public License as published by the + *  Free Software Foundation;  either version 2 of the License, or (at your + *  option) any later version. + * + *  This driver is based on wm8350 implementation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65912.h> + +static int tps65912_spi_write(struct tps65912 *tps65912, u8 addr, +							int bytes, void *src) +{ +	struct spi_device *spi = tps65912->control_data; +	u8 *data = (u8 *) src; +	int ret; +	/* bit 23 is the read/write bit */ +	unsigned long spi_data = 1 << 23 | addr << 15 | *data; +	struct spi_transfer xfer; +	struct spi_message msg; +	u32 tx_buf, rx_buf; + +	tx_buf = spi_data; +	rx_buf = 0; + +	xfer.tx_buf	= &tx_buf; +	xfer.rx_buf	= NULL; +	xfer.len	= sizeof(unsigned long); +	xfer.bits_per_word = 24; + +	spi_message_init(&msg); +	spi_message_add_tail(&xfer, &msg); + +	ret = spi_sync(spi, &msg); +	return ret; +} + +static int tps65912_spi_read(struct tps65912 *tps65912, u8 addr, +							int bytes, void *dest) +{ +	struct spi_device *spi = tps65912->control_data; +	/* bit 23 is the read/write bit */ +	unsigned long spi_data = 0 << 23 | addr << 15; +	struct spi_transfer xfer; +	struct spi_message msg; +	int ret; +	u8 *data = (u8 *) dest; +	u32 tx_buf, rx_buf; + +	tx_buf = spi_data; +	rx_buf = 0; + +	xfer.tx_buf	= &tx_buf; +	xfer.rx_buf	= &rx_buf; +	xfer.len	= sizeof(unsigned long); +	xfer.bits_per_word = 24; + +	spi_message_init(&msg); +	spi_message_add_tail(&xfer, &msg); + +	if (spi == NULL) +		return 0; + +	ret = spi_sync(spi, &msg); +	if (ret == 0) +		*data = (u8) (rx_buf & 0xFF); +	return ret; +} + +static int __devinit tps65912_spi_probe(struct spi_device *spi) +{ +	struct tps65912 *tps65912; + +	tps65912 = kzalloc(sizeof(struct tps65912), GFP_KERNEL); +	if (tps65912 == NULL) +		return -ENOMEM; + +	tps65912->dev = &spi->dev; +	tps65912->control_data = spi; +	tps65912->read = tps65912_spi_read; +	tps65912->write = tps65912_spi_write; + +	spi_set_drvdata(spi, tps65912); + +	return tps65912_device_init(tps65912); +} + +static int __devexit tps65912_spi_remove(struct spi_device *spi) +{ +	struct tps65912 *tps65912 = spi_get_drvdata(spi); + +	tps65912_device_exit(tps65912); + +	return 0; +} + +static struct spi_driver tps65912_spi_driver = { +	.driver = { +		.name = "tps65912", +		.bus = &spi_bus_type, +		.owner = THIS_MODULE, +	}, +	.probe	= tps65912_spi_probe, +	.remove = __devexit_p(tps65912_spi_remove), +}; + +static int __init tps65912_spi_init(void) +{ +	int ret; + +	ret = spi_register_driver(&tps65912_spi_driver); +	if (ret != 0) +		pr_err("Failed to register TPS65912 SPI driver: %d\n", ret); + +	return 0; +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps65912_spi_init); + +static void __exit tps65912_spi_exit(void) +{ +	spi_unregister_driver(&tps65912_spi_driver); +} +module_exit(tps65912_spi_exit); + +MODULE_AUTHOR("Margarita Olaya	<magi@slimlogic.co.uk>"); +MODULE_DESCRIPTION("SPI support for TPS65912 chip family mfd"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index b8f2a4e7f6e7..01ecfeee6524 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -110,7 +110,7 @@  #endif  #if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\ -	defined(CONFIG_SND_SOC_TWL6040) || defined(CONFIG_SND_SOC_TWL6040_MODULE) +	defined(CONFIG_TWL6040_CORE) || defined(CONFIG_TWL6040_CORE_MODULE)  #define twl_has_codec()	true  #else  #define twl_has_codec()	false @@ -815,20 +815,19 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)  			return PTR_ERR(child);  	} -	if (twl_has_codec() && pdata->codec && twl_class_is_4030()) { +	if (twl_has_codec() && pdata->audio && twl_class_is_4030()) {  		sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid;  		child = add_child(sub_chip_id, "twl4030-audio", -				pdata->codec, sizeof(*pdata->codec), +				pdata->audio, sizeof(*pdata->audio),  				false, 0, 0);  		if (IS_ERR(child))  			return PTR_ERR(child);  	} -	/* Phoenix codec driver is probed directly atm */ -	if (twl_has_codec() && pdata->codec && twl_class_is_6030()) { +	if (twl_has_codec() && pdata->audio && twl_class_is_6030()) {  		sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; -		child = add_child(sub_chip_id, "twl6040-codec", -				pdata->codec, sizeof(*pdata->codec), +		child = add_child(sub_chip_id, "twl6040", +				pdata->audio, sizeof(*pdata->audio),  				false, 0, 0);  		if (IS_ERR(child))  			return PTR_ERR(child); @@ -1284,6 +1283,8 @@ static const struct i2c_device_id twl_ids[] = {  	{ "tps65950", 0 },		/* catalog version of twl5030 */  	{ "tps65930", TPS_SUBSET },	/* fewer LDOs and DACs; no charger */  	{ "tps65920", TPS_SUBSET },	/* fewer LDOs; no codec or charger */ +	{ "tps65921", TPS_SUBSET },	/* fewer LDOs; no codec, no LED +					   and vibrator. Charger in USB module*/  	{ "twl6030", TWL6030_CLASS },	/* "Phoenix power chip" */  	{ "twl6025", TWL6030_CLASS | TWL6025_SUBCLASS }, /* "Phoenix lite" */  	{ /* end of list */ }, diff --git a/drivers/mfd/twl4030-audio.c b/drivers/mfd/twl4030-audio.c new file mode 100644 index 000000000000..ae51ab5d0e5d --- /dev/null +++ b/drivers/mfd/twl4030-audio.c @@ -0,0 +1,277 @@ +/* + * MFD driver for twl4030 audio submodule, which contains an audio codec, and + * the vibra control. + * + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * Copyright:   (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/platform_device.h> +#include <linux/i2c/twl.h> +#include <linux/mfd/core.h> +#include <linux/mfd/twl4030-audio.h> + +#define TWL4030_AUDIO_CELLS	2 + +static struct platform_device *twl4030_audio_dev; + +struct twl4030_audio_resource { +	int request_count; +	u8 reg; +	u8 mask; +}; + +struct twl4030_audio { +	unsigned int audio_mclk; +	struct mutex mutex; +	struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; +	struct mfd_cell cells[TWL4030_AUDIO_CELLS]; +}; + +/* + * Modify the resource, the function returns the content of the register + * after the modification. + */ +static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) +{ +	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); +	u8 val; + +	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, +			audio->resource[id].reg); + +	if (enable) +		val |= audio->resource[id].mask; +	else +		val &= ~audio->resource[id].mask; + +	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, +					val, audio->resource[id].reg); + +	return val; +} + +static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) +{ +	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); +	u8 val; + +	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, +			audio->resource[id].reg); + +	return val; +} + +/* + * Enable the resource. + * The function returns with error or the content of the register + */ +int twl4030_audio_enable_resource(enum twl4030_audio_res id) +{ +	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); +	int val; + +	if (id >= TWL4030_AUDIO_RES_MAX) { +		dev_err(&twl4030_audio_dev->dev, +				"Invalid resource ID (%u)\n", id); +		return -EINVAL; +	} + +	mutex_lock(&audio->mutex); +	if (!audio->resource[id].request_count) +		/* Resource was disabled, enable it */ +		val = twl4030_audio_set_resource(id, 1); +	else +		val = twl4030_audio_get_resource(id); + +	audio->resource[id].request_count++; +	mutex_unlock(&audio->mutex); + +	return val; +} +EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); + +/* + * Disable the resource. + * The function returns with error or the content of the register + */ +int twl4030_audio_disable_resource(unsigned id) +{ +	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); +	int val; + +	if (id >= TWL4030_AUDIO_RES_MAX) { +		dev_err(&twl4030_audio_dev->dev, +				"Invalid resource ID (%u)\n", id); +		return -EINVAL; +	} + +	mutex_lock(&audio->mutex); +	if (!audio->resource[id].request_count) { +		dev_err(&twl4030_audio_dev->dev, +			"Resource has been disabled already (%u)\n", id); +		mutex_unlock(&audio->mutex); +		return -EPERM; +	} +	audio->resource[id].request_count--; + +	if (!audio->resource[id].request_count) +		/* Resource can be disabled now */ +		val = twl4030_audio_set_resource(id, 0); +	else +		val = twl4030_audio_get_resource(id); + +	mutex_unlock(&audio->mutex); + +	return val; +} +EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); + +unsigned int twl4030_audio_get_mclk(void) +{ +	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); + +	return audio->audio_mclk; +} +EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); + +static int __devinit twl4030_audio_probe(struct platform_device *pdev) +{ +	struct twl4030_audio *audio; +	struct twl4030_audio_data *pdata = pdev->dev.platform_data; +	struct mfd_cell *cell = NULL; +	int ret, childs = 0; +	u8 val; + +	if (!pdata) { +		dev_err(&pdev->dev, "Platform data is missing\n"); +		return -EINVAL; +	} + +	/* Configure APLL_INFREQ and disable APLL if enabled */ +	val = 0; +	switch (pdata->audio_mclk) { +	case 19200000: +		val |= TWL4030_APLL_INFREQ_19200KHZ; +		break; +	case 26000000: +		val |= TWL4030_APLL_INFREQ_26000KHZ; +		break; +	case 38400000: +		val |= TWL4030_APLL_INFREQ_38400KHZ; +		break; +	default: +		dev_err(&pdev->dev, "Invalid audio_mclk\n"); +		return -EINVAL; +	} +	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, +					val, TWL4030_REG_APLL_CTL); + +	audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL); +	if (!audio) +		return -ENOMEM; + +	platform_set_drvdata(pdev, audio); + +	twl4030_audio_dev = pdev; +	mutex_init(&audio->mutex); +	audio->audio_mclk = pdata->audio_mclk; + +	/* Codec power */ +	audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; +	audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; + +	/* PLL */ +	audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; +	audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; + +	if (pdata->codec) { +		cell = &audio->cells[childs]; +		cell->name = "twl4030-codec"; +		cell->platform_data = pdata->codec; +		cell->pdata_size = sizeof(*pdata->codec); +		childs++; +	} +	if (pdata->vibra) { +		cell = &audio->cells[childs]; +		cell->name = "twl4030-vibra"; +		cell->platform_data = pdata->vibra; +		cell->pdata_size = sizeof(*pdata->vibra); +		childs++; +	} + +	if (childs) +		ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, +				      childs, NULL, 0); +	else { +		dev_err(&pdev->dev, "No platform data found for childs\n"); +		ret = -ENODEV; +	} + +	if (!ret) +		return 0; + +	platform_set_drvdata(pdev, NULL); +	kfree(audio); +	twl4030_audio_dev = NULL; +	return ret; +} + +static int __devexit twl4030_audio_remove(struct platform_device *pdev) +{ +	struct twl4030_audio *audio = platform_get_drvdata(pdev); + +	mfd_remove_devices(&pdev->dev); +	platform_set_drvdata(pdev, NULL); +	kfree(audio); +	twl4030_audio_dev = NULL; + +	return 0; +} + +MODULE_ALIAS("platform:twl4030-audio"); + +static struct platform_driver twl4030_audio_driver = { +	.probe		= twl4030_audio_probe, +	.remove		= __devexit_p(twl4030_audio_remove), +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= "twl4030-audio", +	}, +}; + +static int __devinit twl4030_audio_init(void) +{ +	return platform_driver_register(&twl4030_audio_driver); +} +module_init(twl4030_audio_init); + +static void __devexit twl4030_audio_exit(void) +{ +	platform_driver_unregister(&twl4030_audio_driver); +} +module_exit(twl4030_audio_exit); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c deleted file mode 100644 index 2bf4136464c1..000000000000 --- a/drivers/mfd/twl4030-codec.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * MFD driver for twl4030 codec submodule - * - * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> - * - * Copyright:   (C) 2009 Nokia Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/slab.h> -#include <linux/kernel.h> -#include <linux/fs.h> -#include <linux/platform_device.h> -#include <linux/i2c/twl.h> -#include <linux/mfd/core.h> -#include <linux/mfd/twl4030-codec.h> - -#define TWL4030_CODEC_CELLS	2 - -static struct platform_device *twl4030_codec_dev; - -struct twl4030_codec_resource { -	int request_count; -	u8 reg; -	u8 mask; -}; - -struct twl4030_codec { -	unsigned int audio_mclk; -	struct mutex mutex; -	struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX]; -	struct mfd_cell cells[TWL4030_CODEC_CELLS]; -}; - -/* - * Modify the resource, the function returns the content of the register - * after the modification. - */ -static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable) -{ -	struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); -	u8 val; - -	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, -			codec->resource[id].reg); - -	if (enable) -		val |= codec->resource[id].mask; -	else -		val &= ~codec->resource[id].mask; - -	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, -					val, codec->resource[id].reg); - -	return val; -} - -static inline int twl4030_codec_get_resource(enum twl4030_codec_res id) -{ -	struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); -	u8 val; - -	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, -			codec->resource[id].reg); - -	return val; -} - -/* - * Enable the resource. - * The function returns with error or the content of the register - */ -int twl4030_codec_enable_resource(enum twl4030_codec_res id) -{ -	struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); -	int val; - -	if (id >= TWL4030_CODEC_RES_MAX) { -		dev_err(&twl4030_codec_dev->dev, -				"Invalid resource ID (%u)\n", id); -		return -EINVAL; -	} - -	mutex_lock(&codec->mutex); -	if (!codec->resource[id].request_count) -		/* Resource was disabled, enable it */ -		val = twl4030_codec_set_resource(id, 1); -	else -		val = twl4030_codec_get_resource(id); - -	codec->resource[id].request_count++; -	mutex_unlock(&codec->mutex); - -	return val; -} -EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource); - -/* - * Disable the resource. - * The function returns with error or the content of the register - */ -int twl4030_codec_disable_resource(unsigned id) -{ -	struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); -	int val; - -	if (id >= TWL4030_CODEC_RES_MAX) { -		dev_err(&twl4030_codec_dev->dev, -				"Invalid resource ID (%u)\n", id); -		return -EINVAL; -	} - -	mutex_lock(&codec->mutex); -	if (!codec->resource[id].request_count) { -		dev_err(&twl4030_codec_dev->dev, -			"Resource has been disabled already (%u)\n", id); -		mutex_unlock(&codec->mutex); -		return -EPERM; -	} -	codec->resource[id].request_count--; - -	if (!codec->resource[id].request_count) -		/* Resource can be disabled now */ -		val = twl4030_codec_set_resource(id, 0); -	else -		val = twl4030_codec_get_resource(id); - -	mutex_unlock(&codec->mutex); - -	return val; -} -EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource); - -unsigned int twl4030_codec_get_mclk(void) -{ -	struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); - -	return codec->audio_mclk; -} -EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk); - -static int __devinit twl4030_codec_probe(struct platform_device *pdev) -{ -	struct twl4030_codec *codec; -	struct twl4030_codec_data *pdata = pdev->dev.platform_data; -	struct mfd_cell *cell = NULL; -	int ret, childs = 0; -	u8 val; - -	if (!pdata) { -		dev_err(&pdev->dev, "Platform data is missing\n"); -		return -EINVAL; -	} - -	/* Configure APLL_INFREQ and disable APLL if enabled */ -	val = 0; -	switch (pdata->audio_mclk) { -	case 19200000: -		val |= TWL4030_APLL_INFREQ_19200KHZ; -		break; -	case 26000000: -		val |= TWL4030_APLL_INFREQ_26000KHZ; -		break; -	case 38400000: -		val |= TWL4030_APLL_INFREQ_38400KHZ; -		break; -	default: -		dev_err(&pdev->dev, "Invalid audio_mclk\n"); -		return -EINVAL; -	} -	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, -					val, TWL4030_REG_APLL_CTL); - -	codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL); -	if (!codec) -		return -ENOMEM; - -	platform_set_drvdata(pdev, codec); - -	twl4030_codec_dev = pdev; -	mutex_init(&codec->mutex); -	codec->audio_mclk = pdata->audio_mclk; - -	/* Codec power */ -	codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE; -	codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ; - -	/* PLL */ -	codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL; -	codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN; - -	if (pdata->audio) { -		cell = &codec->cells[childs]; -		cell->name = "twl4030-codec"; -		cell->platform_data = pdata->audio; -		cell->pdata_size = sizeof(*pdata->audio); -		childs++; -	} -	if (pdata->vibra) { -		cell = &codec->cells[childs]; -		cell->name = "twl4030-vibra"; -		cell->platform_data = pdata->vibra; -		cell->pdata_size = sizeof(*pdata->vibra); -		childs++; -	} - -	if (childs) -		ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells, -				      childs, NULL, 0); -	else { -		dev_err(&pdev->dev, "No platform data found for childs\n"); -		ret = -ENODEV; -	} - -	if (!ret) -		return 0; - -	platform_set_drvdata(pdev, NULL); -	kfree(codec); -	twl4030_codec_dev = NULL; -	return ret; -} - -static int __devexit twl4030_codec_remove(struct platform_device *pdev) -{ -	struct twl4030_codec *codec = platform_get_drvdata(pdev); - -	mfd_remove_devices(&pdev->dev); -	platform_set_drvdata(pdev, NULL); -	kfree(codec); -	twl4030_codec_dev = NULL; - -	return 0; -} - -MODULE_ALIAS("platform:twl4030-audio"); - -static struct platform_driver twl4030_codec_driver = { -	.probe		= twl4030_codec_probe, -	.remove		= __devexit_p(twl4030_codec_remove), -	.driver		= { -		.owner	= THIS_MODULE, -		.name	= "twl4030-audio", -	}, -}; - -static int __devinit twl4030_codec_init(void) -{ -	return platform_driver_register(&twl4030_codec_driver); -} -module_init(twl4030_codec_init); - -static void __devexit twl4030_codec_exit(void) -{ -	platform_driver_unregister(&twl4030_codec_driver); -} -module_exit(twl4030_codec_exit); - -MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); -MODULE_LICENSE("GPL"); - diff --git a/drivers/mfd/twl4030-madc.c b/drivers/mfd/twl4030-madc.c index 3941ddcf15fe..b5d598c3aa71 100644 --- a/drivers/mfd/twl4030-madc.c +++ b/drivers/mfd/twl4030-madc.c @@ -530,13 +530,13 @@ int twl4030_madc_conversion(struct twl4030_madc_request *req)  	if (ret) {  		dev_err(twl4030_madc->dev,  			"unable to write sel register 0x%X\n", method->sel + 1); -		return ret; +		goto out;  	}  	ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, ch_lsb, method->sel);  	if (ret) {  		dev_err(twl4030_madc->dev,  			"unable to write sel register 0x%X\n", method->sel + 1); -		return ret; +		goto out;  	}  	/* Select averaging for all channels if do_avg is set */  	if (req->do_avg) { @@ -546,7 +546,7 @@ int twl4030_madc_conversion(struct twl4030_madc_request *req)  			dev_err(twl4030_madc->dev,  				"unable to write avg register 0x%X\n",  				method->avg + 1); -			return ret; +			goto out;  		}  		ret = twl_i2c_write_u8(TWL4030_MODULE_MADC,  				       ch_lsb, method->avg); @@ -554,7 +554,7 @@ int twl4030_madc_conversion(struct twl4030_madc_request *req)  			dev_err(twl4030_madc->dev,  				"unable to write sel reg 0x%X\n",  				method->sel + 1); -			return ret; +			goto out;  		}  	}  	if (req->type == TWL4030_MADC_IRQ_ONESHOT && req->func_cb != NULL) { diff --git a/drivers/mfd/twl6030-pwm.c b/drivers/mfd/twl6030-pwm.c index 5d25bdc78424..e8fee147678d 100644 --- a/drivers/mfd/twl6030-pwm.c +++ b/drivers/mfd/twl6030-pwm.c @@ -161,3 +161,5 @@ void pwm_free(struct pwm_device *pwm)  	kfree(pwm);  }  EXPORT_SYMBOL(pwm_free); + +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c new file mode 100644 index 000000000000..24d436c2fe4a --- /dev/null +++ b/drivers/mfd/twl6040-core.c @@ -0,0 +1,620 @@ +/* + * MFD driver for TWL6040 audio device + * + * Authors:	Misael Lopez Cruz <misael.lopez@ti.com> + *		Jorge Eduardo Candelaria <jorge.candelaria@ti.com> + *		Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * Copyright:	(C) 2011 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/i2c/twl.h> +#include <linux/mfd/core.h> +#include <linux/mfd/twl6040.h> + +static struct platform_device *twl6040_dev; + +int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg) +{ +	int ret; +	u8 val = 0; + +	mutex_lock(&twl6040->io_mutex); +	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); +	if (ret < 0) { +		mutex_unlock(&twl6040->io_mutex); +		return ret; +	} +	mutex_unlock(&twl6040->io_mutex); + +	return val; +} +EXPORT_SYMBOL(twl6040_reg_read); + +int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val) +{ +	int ret; + +	mutex_lock(&twl6040->io_mutex); +	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg); +	mutex_unlock(&twl6040->io_mutex); + +	return ret; +} +EXPORT_SYMBOL(twl6040_reg_write); + +int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask) +{ +	int ret; +	u8 val; + +	mutex_lock(&twl6040->io_mutex); +	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); +	if (ret) +		goto out; + +	val |= mask; +	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg); +out: +	mutex_unlock(&twl6040->io_mutex); +	return ret; +} +EXPORT_SYMBOL(twl6040_set_bits); + +int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask) +{ +	int ret; +	u8 val; + +	mutex_lock(&twl6040->io_mutex); +	ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); +	if (ret) +		goto out; + +	val &= ~mask; +	ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg); +out: +	mutex_unlock(&twl6040->io_mutex); +	return ret; +} +EXPORT_SYMBOL(twl6040_clear_bits); + +/* twl6040 codec manual power-up sequence */ +static int twl6040_power_up(struct twl6040 *twl6040) +{ +	u8 ldoctl, ncpctl, lppllctl; +	int ret; + +	/* enable high-side LDO, reference system and internal oscillator */ +	ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA; +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); +	if (ret) +		return ret; +	usleep_range(10000, 10500); + +	/* enable negative charge pump */ +	ncpctl = TWL6040_NCPENA; +	ret = twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl); +	if (ret) +		goto ncp_err; +	usleep_range(1000, 1500); + +	/* enable low-side LDO */ +	ldoctl |= TWL6040_LSLDOENA; +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); +	if (ret) +		goto lsldo_err; +	usleep_range(1000, 1500); + +	/* enable low-power PLL */ +	lppllctl = TWL6040_LPLLENA; +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); +	if (ret) +		goto lppll_err; +	usleep_range(5000, 5500); + +	/* disable internal oscillator */ +	ldoctl &= ~TWL6040_OSCENA; +	ret = twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); +	if (ret) +		goto osc_err; + +	return 0; + +osc_err: +	lppllctl &= ~TWL6040_LPLLENA; +	twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); +lppll_err: +	ldoctl &= ~TWL6040_LSLDOENA; +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); +lsldo_err: +	ncpctl &= ~TWL6040_NCPENA; +	twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl); +ncp_err: +	ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA); +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); + +	return ret; +} + +/* twl6040 manual power-down sequence */ +static void twl6040_power_down(struct twl6040 *twl6040) +{ +	u8 ncpctl, ldoctl, lppllctl; + +	ncpctl = twl6040_reg_read(twl6040, TWL6040_REG_NCPCTL); +	ldoctl = twl6040_reg_read(twl6040, TWL6040_REG_LDOCTL); +	lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL); + +	/* enable internal oscillator */ +	ldoctl |= TWL6040_OSCENA; +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); +	usleep_range(1000, 1500); + +	/* disable low-power PLL */ +	lppllctl &= ~TWL6040_LPLLENA; +	twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); + +	/* disable low-side LDO */ +	ldoctl &= ~TWL6040_LSLDOENA; +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); + +	/* disable negative charge pump */ +	ncpctl &= ~TWL6040_NCPENA; +	twl6040_reg_write(twl6040, TWL6040_REG_NCPCTL, ncpctl); + +	/* disable high-side LDO, reference system and internal oscillator */ +	ldoctl &= ~(TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA); +	twl6040_reg_write(twl6040, TWL6040_REG_LDOCTL, ldoctl); +} + +static irqreturn_t twl6040_naudint_handler(int irq, void *data) +{ +	struct twl6040 *twl6040 = data; +	u8 intid, status; + +	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID); + +	if (intid & TWL6040_READYINT) +		complete(&twl6040->ready); + +	if (intid & TWL6040_THINT) { +		status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS); +		if (status & TWL6040_TSHUTDET) { +			dev_warn(&twl6040_dev->dev, +				 "Thermal shutdown, powering-off"); +			twl6040_power(twl6040, 0); +		} else { +			dev_warn(&twl6040_dev->dev, +				 "Leaving thermal shutdown, powering-on"); +			twl6040_power(twl6040, 1); +		} +	} + +	return IRQ_HANDLED; +} + +static int twl6040_power_up_completion(struct twl6040 *twl6040, +				       int naudint) +{ +	int time_left; +	u8 intid; + +	time_left = wait_for_completion_timeout(&twl6040->ready, +						msecs_to_jiffies(144)); +	if (!time_left) { +		intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID); +		if (!(intid & TWL6040_READYINT)) { +			dev_err(&twl6040_dev->dev, +				"timeout waiting for READYINT\n"); +			return -ETIMEDOUT; +		} +	} + +	return 0; +} + +int twl6040_power(struct twl6040 *twl6040, int on) +{ +	int audpwron = twl6040->audpwron; +	int naudint = twl6040->irq; +	int ret = 0; + +	mutex_lock(&twl6040->mutex); + +	if (on) { +		/* already powered-up */ +		if (twl6040->power_count++) +			goto out; + +		if (gpio_is_valid(audpwron)) { +			/* use AUDPWRON line */ +			gpio_set_value(audpwron, 1); +			/* wait for power-up completion */ +			ret = twl6040_power_up_completion(twl6040, naudint); +			if (ret) { +				dev_err(&twl6040_dev->dev, +					"automatic power-down failed\n"); +				twl6040->power_count = 0; +				goto out; +			} +		} else { +			/* use manual power-up sequence */ +			ret = twl6040_power_up(twl6040); +			if (ret) { +				dev_err(&twl6040_dev->dev, +					"manual power-up failed\n"); +				twl6040->power_count = 0; +				goto out; +			} +		} +		/* Default PLL configuration after power up */ +		twl6040->pll = TWL6040_SYSCLK_SEL_LPPLL; +		twl6040->sysclk = 19200000; +	} else { +		/* already powered-down */ +		if (!twl6040->power_count) { +			dev_err(&twl6040_dev->dev, +				"device is already powered-off\n"); +			ret = -EPERM; +			goto out; +		} + +		if (--twl6040->power_count) +			goto out; + +		if (gpio_is_valid(audpwron)) { +			/* use AUDPWRON line */ +			gpio_set_value(audpwron, 0); + +			/* power-down sequence latency */ +			usleep_range(500, 700); +		} else { +			/* use manual power-down sequence */ +			twl6040_power_down(twl6040); +		} +		twl6040->sysclk = 0; +	} + +out: +	mutex_unlock(&twl6040->mutex); +	return ret; +} +EXPORT_SYMBOL(twl6040_power); + +int twl6040_set_pll(struct twl6040 *twl6040, int pll_id, +		    unsigned int freq_in, unsigned int freq_out) +{ +	u8 hppllctl, lppllctl; +	int ret = 0; + +	mutex_lock(&twl6040->mutex); + +	hppllctl = twl6040_reg_read(twl6040, TWL6040_REG_HPPLLCTL); +	lppllctl = twl6040_reg_read(twl6040, TWL6040_REG_LPPLLCTL); + +	switch (pll_id) { +	case TWL6040_SYSCLK_SEL_LPPLL: +		/* low-power PLL divider */ +		switch (freq_out) { +		case 17640000: +			lppllctl |= TWL6040_LPLLFIN; +			break; +		case 19200000: +			lppllctl &= ~TWL6040_LPLLFIN; +			break; +		default: +			dev_err(&twl6040_dev->dev, +				"freq_out %d not supported\n", freq_out); +			ret = -EINVAL; +			goto pll_out; +		} +		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); + +		switch (freq_in) { +		case 32768: +			lppllctl |= TWL6040_LPLLENA; +			twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, +					  lppllctl); +			mdelay(5); +			lppllctl &= ~TWL6040_HPLLSEL; +			twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, +					  lppllctl); +			hppllctl &= ~TWL6040_HPLLENA; +			twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, +					  hppllctl); +			break; +		default: +			dev_err(&twl6040_dev->dev, +				"freq_in %d not supported\n", freq_in); +			ret = -EINVAL; +			goto pll_out; +		} +		break; +	case TWL6040_SYSCLK_SEL_HPPLL: +		/* high-performance PLL can provide only 19.2 MHz */ +		if (freq_out != 19200000) { +			dev_err(&twl6040_dev->dev, +				"freq_out %d not supported\n", freq_out); +			ret = -EINVAL; +			goto pll_out; +		} + +		hppllctl &= ~TWL6040_MCLK_MSK; + +		switch (freq_in) { +		case 12000000: +			/* PLL enabled, active mode */ +			hppllctl |= TWL6040_MCLK_12000KHZ | +				    TWL6040_HPLLENA; +			break; +		case 19200000: +			/* +			 * PLL disabled +			 * (enable PLL if MCLK jitter quality +			 *  doesn't meet specification) +			 */ +			hppllctl |= TWL6040_MCLK_19200KHZ; +			break; +		case 26000000: +			/* PLL enabled, active mode */ +			hppllctl |= TWL6040_MCLK_26000KHZ | +				    TWL6040_HPLLENA; +			break; +		case 38400000: +			/* PLL enabled, active mode */ +			hppllctl |= TWL6040_MCLK_38400KHZ | +				    TWL6040_HPLLENA; +			break; +		default: +			dev_err(&twl6040_dev->dev, +				"freq_in %d not supported\n", freq_in); +			ret = -EINVAL; +			goto pll_out; +		} + +		/* enable clock slicer to ensure input waveform is square */ +		hppllctl |= TWL6040_HPLLSQRENA; + +		twl6040_reg_write(twl6040, TWL6040_REG_HPPLLCTL, hppllctl); +		usleep_range(500, 700); +		lppllctl |= TWL6040_HPLLSEL; +		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); +		lppllctl &= ~TWL6040_LPLLENA; +		twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, lppllctl); +		break; +	default: +		dev_err(&twl6040_dev->dev, "unknown pll id %d\n", pll_id); +		ret = -EINVAL; +		goto pll_out; +	} + +	twl6040->sysclk = freq_out; +	twl6040->pll = pll_id; + +pll_out: +	mutex_unlock(&twl6040->mutex); +	return ret; +} +EXPORT_SYMBOL(twl6040_set_pll); + +int twl6040_get_pll(struct twl6040 *twl6040) +{ +	if (twl6040->power_count) +		return twl6040->pll; +	else +		return -ENODEV; +} +EXPORT_SYMBOL(twl6040_get_pll); + +unsigned int twl6040_get_sysclk(struct twl6040 *twl6040) +{ +	return twl6040->sysclk; +} +EXPORT_SYMBOL(twl6040_get_sysclk); + +static struct resource twl6040_vibra_rsrc[] = { +	{ +		.flags = IORESOURCE_IRQ, +	}, +}; + +static struct resource twl6040_codec_rsrc[] = { +	{ +		.flags = IORESOURCE_IRQ, +	}, +}; + +static int __devinit twl6040_probe(struct platform_device *pdev) +{ +	struct twl4030_audio_data *pdata = pdev->dev.platform_data; +	struct twl6040 *twl6040; +	struct mfd_cell *cell = NULL; +	int ret, children = 0; + +	if (!pdata) { +		dev_err(&pdev->dev, "Platform data is missing\n"); +		return -EINVAL; +	} + +	/* In order to operate correctly we need valid interrupt config */ +	if (!pdata->naudint_irq || !pdata->irq_base) { +		dev_err(&pdev->dev, "Invalid IRQ configuration\n"); +		return -EINVAL; +	} + +	twl6040 = kzalloc(sizeof(struct twl6040), GFP_KERNEL); +	if (!twl6040) +		return -ENOMEM; + +	platform_set_drvdata(pdev, twl6040); + +	twl6040_dev = pdev; +	twl6040->dev = &pdev->dev; +	twl6040->audpwron = pdata->audpwron_gpio; +	twl6040->irq = pdata->naudint_irq; +	twl6040->irq_base = pdata->irq_base; + +	mutex_init(&twl6040->mutex); +	mutex_init(&twl6040->io_mutex); +	init_completion(&twl6040->ready); + +	twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV); + +	if (gpio_is_valid(twl6040->audpwron)) { +		ret = gpio_request(twl6040->audpwron, "audpwron"); +		if (ret) +			goto gpio1_err; + +		ret = gpio_direction_output(twl6040->audpwron, 0); +		if (ret) +			goto gpio2_err; +	} + +	/* ERRATA: Automatic power-up is not possible in ES1.0 */ +	if (twl6040->rev == TWL6040_REV_ES1_0) +		twl6040->audpwron = -EINVAL; + +	/* codec interrupt */ +	ret = twl6040_irq_init(twl6040); +	if (ret) +		goto gpio2_err; + +	ret = request_threaded_irq(twl6040->irq_base + TWL6040_IRQ_READY, +				   NULL, twl6040_naudint_handler, 0, +				   "twl6040_irq_ready", twl6040); +	if (ret) { +		dev_err(twl6040->dev, "READY IRQ request failed: %d\n", +			ret); +		goto irq_err; +	} + +	/* dual-access registers controlled by I2C only */ +	twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL); + +	if (pdata->codec) { +		int irq = twl6040->irq_base + TWL6040_IRQ_PLUG; + +		cell = &twl6040->cells[children]; +		cell->name = "twl6040-codec"; +		twl6040_codec_rsrc[0].start = irq; +		twl6040_codec_rsrc[0].end = irq; +		cell->resources = twl6040_codec_rsrc; +		cell->num_resources = ARRAY_SIZE(twl6040_codec_rsrc); +		cell->platform_data = pdata->codec; +		cell->pdata_size = sizeof(*pdata->codec); +		children++; +	} + +	if (pdata->vibra) { +		int irq = twl6040->irq_base + TWL6040_IRQ_VIB; + +		cell = &twl6040->cells[children]; +		cell->name = "twl6040-vibra"; +		twl6040_vibra_rsrc[0].start = irq; +		twl6040_vibra_rsrc[0].end = irq; +		cell->resources = twl6040_vibra_rsrc; +		cell->num_resources = ARRAY_SIZE(twl6040_vibra_rsrc); + +		cell->platform_data = pdata->vibra; +		cell->pdata_size = sizeof(*pdata->vibra); +		children++; +	} + +	if (children) { +		ret = mfd_add_devices(&pdev->dev, pdev->id, twl6040->cells, +				      children, NULL, 0); +		if (ret) +			goto mfd_err; +	} else { +		dev_err(&pdev->dev, "No platform data found for children\n"); +		ret = -ENODEV; +		goto mfd_err; +	} + +	return 0; + +mfd_err: +	free_irq(twl6040->irq_base + TWL6040_IRQ_READY, twl6040); +irq_err: +	twl6040_irq_exit(twl6040); +gpio2_err: +	if (gpio_is_valid(twl6040->audpwron)) +		gpio_free(twl6040->audpwron); +gpio1_err: +	platform_set_drvdata(pdev, NULL); +	kfree(twl6040); +	twl6040_dev = NULL; +	return ret; +} + +static int __devexit twl6040_remove(struct platform_device *pdev) +{ +	struct twl6040 *twl6040 = platform_get_drvdata(pdev); + +	if (twl6040->power_count) +		twl6040_power(twl6040, 0); + +	if (gpio_is_valid(twl6040->audpwron)) +		gpio_free(twl6040->audpwron); + +	free_irq(twl6040->irq_base + TWL6040_IRQ_READY, twl6040); +	twl6040_irq_exit(twl6040); + +	mfd_remove_devices(&pdev->dev); +	platform_set_drvdata(pdev, NULL); +	kfree(twl6040); +	twl6040_dev = NULL; + +	return 0; +} + +static struct platform_driver twl6040_driver = { +	.probe		= twl6040_probe, +	.remove		= __devexit_p(twl6040_remove), +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= "twl6040", +	}, +}; + +static int __devinit twl6040_init(void) +{ +	return platform_driver_register(&twl6040_driver); +} +module_init(twl6040_init); + +static void __devexit twl6040_exit(void) +{ +	platform_driver_unregister(&twl6040_driver); +} + +module_exit(twl6040_exit); + +MODULE_DESCRIPTION("TWL6040 MFD"); +MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>"); +MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl6040"); diff --git a/drivers/mfd/twl6040-irq.c b/drivers/mfd/twl6040-irq.c new file mode 100644 index 000000000000..b3f8ddaa28a8 --- /dev/null +++ b/drivers/mfd/twl6040-irq.c @@ -0,0 +1,191 @@ +/* + * Interrupt controller support for TWL6040 + * + * Author:     Misael Lopez Cruz <misael.lopez@ti.com> + * + * Copyright:   (C) 2011 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/mfd/twl6040.h> + +struct twl6040_irq_data { +	int mask; +	int status; +}; + +static struct twl6040_irq_data twl6040_irqs[] = { +	{ +		.mask = TWL6040_THMSK, +		.status = TWL6040_THINT, +	}, +	{ +		.mask = TWL6040_PLUGMSK, +		.status = TWL6040_PLUGINT | TWL6040_UNPLUGINT, +	}, +	{ +		.mask = TWL6040_HOOKMSK, +		.status = TWL6040_HOOKINT, +	}, +	{ +		.mask = TWL6040_HFMSK, +		.status = TWL6040_HFINT, +	}, +	{ +		.mask = TWL6040_VIBMSK, +		.status = TWL6040_VIBINT, +	}, +	{ +		.mask = TWL6040_READYMSK, +		.status = TWL6040_READYINT, +	}, +}; + +static inline +struct twl6040_irq_data *irq_to_twl6040_irq(struct twl6040 *twl6040, +					    int irq) +{ +	return &twl6040_irqs[irq - twl6040->irq_base]; +} + +static void twl6040_irq_lock(struct irq_data *data) +{ +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data); + +	mutex_lock(&twl6040->irq_mutex); +} + +static void twl6040_irq_sync_unlock(struct irq_data *data) +{ +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data); + +	/* write back to hardware any change in irq mask */ +	if (twl6040->irq_masks_cur != twl6040->irq_masks_cache) { +		twl6040->irq_masks_cache = twl6040->irq_masks_cur; +		twl6040_reg_write(twl6040, TWL6040_REG_INTMR, +				  twl6040->irq_masks_cur); +	} + +	mutex_unlock(&twl6040->irq_mutex); +} + +static void twl6040_irq_enable(struct irq_data *data) +{ +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data); +	struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040, +							       data->irq); + +	twl6040->irq_masks_cur &= ~irq_data->mask; +} + +static void twl6040_irq_disable(struct irq_data *data) +{ +	struct twl6040 *twl6040 = irq_data_get_irq_chip_data(data); +	struct twl6040_irq_data *irq_data = irq_to_twl6040_irq(twl6040, +							       data->irq); + +	twl6040->irq_masks_cur |= irq_data->mask; +} + +static struct irq_chip twl6040_irq_chip = { +	.name			= "twl6040", +	.irq_bus_lock		= twl6040_irq_lock, +	.irq_bus_sync_unlock	= twl6040_irq_sync_unlock, +	.irq_enable		= twl6040_irq_enable, +	.irq_disable		= twl6040_irq_disable, +}; + +static irqreturn_t twl6040_irq_thread(int irq, void *data) +{ +	struct twl6040 *twl6040 = data; +	u8 intid; +	int i; + +	intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID); + +	/* apply masking and report (backwards to handle READYINT first) */ +	for (i = ARRAY_SIZE(twl6040_irqs) - 1; i >= 0; i--) { +		if (twl6040->irq_masks_cur & twl6040_irqs[i].mask) +			intid &= ~twl6040_irqs[i].status; +		if (intid & twl6040_irqs[i].status) +			handle_nested_irq(twl6040->irq_base + i); +	} + +	/* ack unmasked irqs */ +	twl6040_reg_write(twl6040, TWL6040_REG_INTID, intid); + +	return IRQ_HANDLED; +} + +int twl6040_irq_init(struct twl6040 *twl6040) +{ +	int cur_irq, ret; +	u8 val; + +	mutex_init(&twl6040->irq_mutex); + +	/* mask the individual interrupt sources */ +	twl6040->irq_masks_cur = TWL6040_ALLINT_MSK; +	twl6040->irq_masks_cache = TWL6040_ALLINT_MSK; +	twl6040_reg_write(twl6040, TWL6040_REG_INTMR, TWL6040_ALLINT_MSK); + +	/* Register them with genirq */ +	for (cur_irq = twl6040->irq_base; +	     cur_irq < twl6040->irq_base + ARRAY_SIZE(twl6040_irqs); +	     cur_irq++) { +		irq_set_chip_data(cur_irq, twl6040); +		irq_set_chip_and_handler(cur_irq, &twl6040_irq_chip, +					 handle_level_irq); +		irq_set_nested_thread(cur_irq, 1); + +		/* ARM needs us to explicitly flag the IRQ as valid +		 * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM +		set_irq_flags(cur_irq, IRQF_VALID); +#else +		irq_set_noprobe(cur_irq); +#endif +	} + +	ret = request_threaded_irq(twl6040->irq, NULL, twl6040_irq_thread, +				   IRQF_ONESHOT, "twl6040", twl6040); +	if (ret) { +		dev_err(twl6040->dev, "failed to request IRQ %d: %d\n", +			twl6040->irq, ret); +		return ret; +	} + +	/* reset interrupts */ +	val = twl6040_reg_read(twl6040, TWL6040_REG_INTID); + +	/* interrupts cleared on write */ +	twl6040_clear_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_INTCLRMODE); + +	return 0; +} +EXPORT_SYMBOL(twl6040_irq_init); + +void twl6040_irq_exit(struct twl6040 *twl6040) +{ +	free_irq(twl6040->irq, twl6040); +} +EXPORT_SYMBOL(twl6040_irq_exit); diff --git a/drivers/mfd/wm831x-auxadc.c b/drivers/mfd/wm831x-auxadc.c new file mode 100644 index 000000000000..87210954a066 --- /dev/null +++ b/drivers/mfd/wm831x-auxadc.c @@ -0,0 +1,299 @@ +/* + * wm831x-auxadc.c  --  AUXADC for Wolfson WM831x PMICs + * + * Copyright 2009-2011 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + *  This program is free software; you can redistribute  it and/or modify it + *  under  the terms of  the GNU General  Public License as published by the + *  Free Software Foundation;  either version 2 of the  License, or (at your + *  option) any later version. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/slab.h> +#include <linux/list.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/irq.h> +#include <linux/mfd/wm831x/auxadc.h> +#include <linux/mfd/wm831x/otp.h> +#include <linux/mfd/wm831x/regulator.h> + +struct wm831x_auxadc_req { +	struct list_head list; +	enum wm831x_auxadc input; +	int val; +	struct completion done; +}; + +static int wm831x_auxadc_read_irq(struct wm831x *wm831x, +				  enum wm831x_auxadc input) +{ +	struct wm831x_auxadc_req *req; +	int ret; +	bool ena = false; + +	req = kzalloc(sizeof(*req), GFP_KERNEL); +	if (!req) +		return -ENOMEM; + +	init_completion(&req->done); +	req->input = input; +	req->val = -ETIMEDOUT; + +	mutex_lock(&wm831x->auxadc_lock); + +	/* Enqueue the request */ +	list_add(&req->list, &wm831x->auxadc_pending); + +	ena = !wm831x->auxadc_active; + +	if (ena) { +		ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, +				      WM831X_AUX_ENA, WM831X_AUX_ENA); +		if (ret != 0) { +			dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", +				ret); +			goto out; +		} +	} + +	/* Enable the conversion if not already running */ +	if (!(wm831x->auxadc_active & (1 << input))) { +		ret = wm831x_set_bits(wm831x, WM831X_AUXADC_SOURCE, +				      1 << input, 1 << input); +		if (ret != 0) { +			dev_err(wm831x->dev, +				"Failed to set AUXADC source: %d\n", ret); +			goto out; +		} + +		wm831x->auxadc_active |= 1 << input; +	} + +	/* We convert at the fastest rate possible */ +	if (ena) { +		ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, +				      WM831X_AUX_CVT_ENA | +				      WM831X_AUX_RATE_MASK, +				      WM831X_AUX_CVT_ENA | +				      WM831X_AUX_RATE_MASK); +		if (ret != 0) { +			dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", +				ret); +			goto out; +		} +	} + +	mutex_unlock(&wm831x->auxadc_lock); + +	/* Wait for an interrupt */ +	wait_for_completion_timeout(&req->done, msecs_to_jiffies(500)); + +	mutex_lock(&wm831x->auxadc_lock); + +	list_del(&req->list); +	ret = req->val; + +out: +	mutex_unlock(&wm831x->auxadc_lock); + +	kfree(req); + +	return ret; +} + +static irqreturn_t wm831x_auxadc_irq(int irq, void *irq_data) +{ +	struct wm831x *wm831x = irq_data; +	struct wm831x_auxadc_req *req; +	int ret, input, val; + +	ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); +	if (ret < 0) { +		dev_err(wm831x->dev, +			"Failed to read AUXADC data: %d\n", ret); +		return IRQ_NONE; +	} + +	input = ((ret & WM831X_AUX_DATA_SRC_MASK) +		 >> WM831X_AUX_DATA_SRC_SHIFT) - 1; + +	if (input == 14) +		input = WM831X_AUX_CAL; + +	val = ret & WM831X_AUX_DATA_MASK; + +	mutex_lock(&wm831x->auxadc_lock); + +	/* Disable this conversion, we're about to complete all users */ +	wm831x_set_bits(wm831x, WM831X_AUXADC_SOURCE, +			1 << input, 0); +	wm831x->auxadc_active &= ~(1 << input); + +	/* Turn off the entire convertor if idle */ +	if (!wm831x->auxadc_active) +		wm831x_reg_write(wm831x, WM831X_AUXADC_CONTROL, 0); + +	/* Wake up any threads waiting for this request */ +	list_for_each_entry(req, &wm831x->auxadc_pending, list) { +		if (req->input == input) { +			req->val = val; +			complete(&req->done); +		} +	} + +	mutex_unlock(&wm831x->auxadc_lock); + +	return IRQ_HANDLED; +} + +static int wm831x_auxadc_read_polled(struct wm831x *wm831x, +				     enum wm831x_auxadc input) +{ +	int ret, src, timeout; + +	mutex_lock(&wm831x->auxadc_lock); + +	ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, +			      WM831X_AUX_ENA, WM831X_AUX_ENA); +	if (ret < 0) { +		dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); +		goto out; +	} + +	/* We force a single source at present */ +	src = input; +	ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE, +			       1 << src); +	if (ret < 0) { +		dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); +		goto out; +	} + +	ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, +			      WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); +	if (ret < 0) { +		dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); +		goto disable; +	} + +	/* If we're not using interrupts then poll the +	 * interrupt status register */ +	timeout = 5; +	while (timeout) { +		msleep(1); + +		ret = wm831x_reg_read(wm831x, +				      WM831X_INTERRUPT_STATUS_1); +		if (ret < 0) { +			dev_err(wm831x->dev, +				"ISR 1 read failed: %d\n", ret); +			goto disable; +		} + +		/* Did it complete? */ +		if (ret & WM831X_AUXADC_DATA_EINT) { +			wm831x_reg_write(wm831x, +					 WM831X_INTERRUPT_STATUS_1, +					 WM831X_AUXADC_DATA_EINT); +			break; +		} else { +			dev_err(wm831x->dev, +				"AUXADC conversion timeout\n"); +			ret = -EBUSY; +			goto disable; +		} +	} + +	ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); +	if (ret < 0) { +		dev_err(wm831x->dev, +			"Failed to read AUXADC data: %d\n", ret); +		goto disable; +	} + +	src = ((ret & WM831X_AUX_DATA_SRC_MASK) +	       >> WM831X_AUX_DATA_SRC_SHIFT) - 1; + +	if (src == 14) +		src = WM831X_AUX_CAL; + +	if (src != input) { +		dev_err(wm831x->dev, "Data from source %d not %d\n", +			src, input); +		ret = -EINVAL; +	} else { +		ret &= WM831X_AUX_DATA_MASK; +	} + +disable: +	wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0); +out: +	mutex_unlock(&wm831x->auxadc_lock); +	return ret; +} + +/** + * wm831x_auxadc_read: Read a value from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) +{ +	return wm831x->auxadc_read(wm831x, input); +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read); + +/** + * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC + * + * @wm831x: Device to read from. + * @input: AUXADC input to read. + */ +int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input) +{ +	int ret; + +	ret = wm831x_auxadc_read(wm831x, input); +	if (ret < 0) +		return ret; + +	ret *= 1465; + +	return ret; +} +EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv); + +void wm831x_auxadc_init(struct wm831x *wm831x) +{ +	int ret; + +	mutex_init(&wm831x->auxadc_lock); +	INIT_LIST_HEAD(&wm831x->auxadc_pending); + +	if (wm831x->irq && wm831x->irq_base) { +		wm831x->auxadc_read = wm831x_auxadc_read_irq; + +		ret = request_threaded_irq(wm831x->irq_base + +					   WM831X_IRQ_AUXADC_DATA, +					   NULL, wm831x_auxadc_irq, 0, +					   "auxadc", wm831x); +		if (ret < 0) { +			dev_err(wm831x->dev, "AUXADC IRQ request failed: %d\n", +				ret); +			wm831x->auxadc_read = NULL; +		} +	} + +	if (!wm831x->auxadc_read) +		wm831x->auxadc_read = wm831x_auxadc_read_polled; +} diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 265f75fc6a25..282e76ab678f 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -295,7 +295,7 @@ int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg,  		goto out;  	r &= ~mask; -	r |= val; +	r |= val & mask;  	ret = wm831x_write(wm831x, reg, 2, &r); @@ -306,146 +306,6 @@ out:  }  EXPORT_SYMBOL_GPL(wm831x_set_bits); -/** - * wm831x_auxadc_read: Read a value from the WM831x AUXADC - * - * @wm831x: Device to read from. - * @input: AUXADC input to read. - */ -int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) -{ -	int ret, src, irq_masked, timeout; - -	/* Are we using the interrupt? */ -	irq_masked = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_1_MASK); -	irq_masked &= WM831X_AUXADC_DATA_EINT; - -	mutex_lock(&wm831x->auxadc_lock); - -	ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, -			      WM831X_AUX_ENA, WM831X_AUX_ENA); -	if (ret < 0) { -		dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); -		goto out; -	} - -	/* We force a single source at present */ -	src = input; -	ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE, -			       1 << src); -	if (ret < 0) { -		dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); -		goto out; -	} - -	/* Clear any notification from a very late arriving interrupt */ -	try_wait_for_completion(&wm831x->auxadc_done); - -	ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, -			      WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); -	if (ret < 0) { -		dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); -		goto disable; -	} - -	if (irq_masked) { -		/* If we're not using interrupts then poll the -		 * interrupt status register */ -		timeout = 5; -		while (timeout) { -			msleep(1); - -			ret = wm831x_reg_read(wm831x, -					      WM831X_INTERRUPT_STATUS_1); -			if (ret < 0) { -				dev_err(wm831x->dev, -					"ISR 1 read failed: %d\n", ret); -				goto disable; -			} - -			/* Did it complete? */ -			if (ret & WM831X_AUXADC_DATA_EINT) { -				wm831x_reg_write(wm831x, -						 WM831X_INTERRUPT_STATUS_1, -						 WM831X_AUXADC_DATA_EINT); -				break; -			} else { -				dev_err(wm831x->dev, -					"AUXADC conversion timeout\n"); -				ret = -EBUSY; -				goto disable; -			} -		} -	} else { -		/* If we are using interrupts then wait for the -		 * interrupt to complete.  Use an extremely long -		 * timeout to handle situations with heavy load where -		 * the notification of the interrupt may be delayed by -		 * threaded IRQ handling. */ -		if (!wait_for_completion_timeout(&wm831x->auxadc_done, -						 msecs_to_jiffies(500))) { -			dev_err(wm831x->dev, "Timed out waiting for AUXADC\n"); -			ret = -EBUSY; -			goto disable; -		} -	} - -	ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); -	if (ret < 0) { -		dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret); -	} else { -		src = ((ret & WM831X_AUX_DATA_SRC_MASK) -		       >> WM831X_AUX_DATA_SRC_SHIFT) - 1; - -		if (src == 14) -			src = WM831X_AUX_CAL; - -		if (src != input) { -			dev_err(wm831x->dev, "Data from source %d not %d\n", -				src, input); -			ret = -EINVAL; -		} else { -			ret &= WM831X_AUX_DATA_MASK; -		} -	} - -disable: -	wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0); -out: -	mutex_unlock(&wm831x->auxadc_lock); -	return ret; -} -EXPORT_SYMBOL_GPL(wm831x_auxadc_read); - -static irqreturn_t wm831x_auxadc_irq(int irq, void *irq_data) -{ -	struct wm831x *wm831x = irq_data; - -	complete(&wm831x->auxadc_done); - -	return IRQ_HANDLED; -} - -/** - * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC - * - * @wm831x: Device to read from. - * @input: AUXADC input to read. - */ -int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input) -{ -	int ret; - -	ret = wm831x_auxadc_read(wm831x, input); -	if (ret < 0) -		return ret; - -	ret *= 1465; - -	return ret; -} -EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv); -  static struct resource wm831x_dcdc1_resources[] = {  	{  		.start = WM831X_DC1_CONTROL_1, @@ -872,6 +732,9 @@ static struct mfd_cell wm8310_devs[] = {  		.resources = wm831x_dcdc4_resources,  	},  	{ +		.name = "wm831x-clk", +	}, +	{  		.name = "wm831x-epe",  		.id = 1,  	}, @@ -976,11 +839,6 @@ static struct mfd_cell wm8310_devs[] = {  		.resources = wm831x_power_resources,  	},  	{ -		.name = "wm831x-rtc", -		.num_resources = ARRAY_SIZE(wm831x_rtc_resources), -		.resources = wm831x_rtc_resources, -	}, -	{  		.name = "wm831x-status",  		.id = 1,  		.num_resources = ARRAY_SIZE(wm831x_status1_resources), @@ -1028,6 +886,9 @@ static struct mfd_cell wm8311_devs[] = {  		.resources = wm831x_dcdc4_resources,  	},  	{ +		.name = "wm831x-clk", +	}, +	{  		.name = "wm831x-epe",  		.id = 1,  	}, @@ -1108,11 +969,6 @@ static struct mfd_cell wm8311_devs[] = {  		.resources = wm831x_power_resources,  	},  	{ -		.name = "wm831x-rtc", -		.num_resources = ARRAY_SIZE(wm831x_rtc_resources), -		.resources = wm831x_rtc_resources, -	}, -	{  		.name = "wm831x-status",  		.id = 1,  		.num_resources = ARRAY_SIZE(wm831x_status1_resources), @@ -1125,11 +981,6 @@ static struct mfd_cell wm8311_devs[] = {  		.resources = wm831x_status2_resources,  	},  	{ -		.name = "wm831x-touch", -		.num_resources = ARRAY_SIZE(wm831x_touch_resources), -		.resources = wm831x_touch_resources, -	}, -	{  		.name = "wm831x-watchdog",  		.num_resources = ARRAY_SIZE(wm831x_wdt_resources),  		.resources = wm831x_wdt_resources, @@ -1165,6 +1016,9 @@ static struct mfd_cell wm8312_devs[] = {  		.resources = wm831x_dcdc4_resources,  	},  	{ +		.name = "wm831x-clk", +	}, +	{  		.name = "wm831x-epe",  		.id = 1,  	}, @@ -1269,11 +1123,6 @@ static struct mfd_cell wm8312_devs[] = {  		.resources = wm831x_power_resources,  	},  	{ -		.name = "wm831x-rtc", -		.num_resources = ARRAY_SIZE(wm831x_rtc_resources), -		.resources = wm831x_rtc_resources, -	}, -	{  		.name = "wm831x-status",  		.id = 1,  		.num_resources = ARRAY_SIZE(wm831x_status1_resources), @@ -1286,11 +1135,6 @@ static struct mfd_cell wm8312_devs[] = {  		.resources = wm831x_status2_resources,  	},  	{ -		.name = "wm831x-touch", -		.num_resources = ARRAY_SIZE(wm831x_touch_resources), -		.resources = wm831x_touch_resources, -	}, -	{  		.name = "wm831x-watchdog",  		.num_resources = ARRAY_SIZE(wm831x_wdt_resources),  		.resources = wm831x_wdt_resources, @@ -1326,6 +1170,9 @@ static struct mfd_cell wm8320_devs[] = {  		.resources = wm8320_dcdc4_buck_resources,  	},  	{ +		.name = "wm831x-clk", +	}, +	{  		.name = "wm831x-gpio",  		.num_resources = ARRAY_SIZE(wm831x_gpio_resources),  		.resources = wm831x_gpio_resources, @@ -1405,11 +1252,6 @@ static struct mfd_cell wm8320_devs[] = {  		.resources = wm831x_on_resources,  	},  	{ -		.name = "wm831x-rtc", -		.num_resources = ARRAY_SIZE(wm831x_rtc_resources), -		.resources = wm831x_rtc_resources, -	}, -	{  		.name = "wm831x-status",  		.id = 1,  		.num_resources = ARRAY_SIZE(wm831x_status1_resources), @@ -1428,6 +1270,22 @@ static struct mfd_cell wm8320_devs[] = {  	},  }; +static struct mfd_cell touch_devs[] = { +	{ +		.name = "wm831x-touch", +		.num_resources = ARRAY_SIZE(wm831x_touch_resources), +		.resources = wm831x_touch_resources, +	}, +}; + +static struct mfd_cell rtc_devs[] = { +	{ +		.name = "wm831x-rtc", +		.num_resources = ARRAY_SIZE(wm831x_rtc_resources), +		.resources = wm831x_rtc_resources, +	}, +}; +  static struct mfd_cell backlight_devs[] = {  	{  		.name = "wm831x-backlight", @@ -1440,14 +1298,12 @@ static struct mfd_cell backlight_devs[] = {  int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)  {  	struct wm831x_pdata *pdata = wm831x->dev->platform_data; -	int rev; +	int rev, wm831x_num;  	enum wm831x_parent parent;  	int ret, i;  	mutex_init(&wm831x->io_lock);  	mutex_init(&wm831x->key_lock); -	mutex_init(&wm831x->auxadc_lock); -	init_completion(&wm831x->auxadc_done);  	dev_set_drvdata(wm831x->dev, wm831x);  	ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); @@ -1592,45 +1448,51 @@ int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)  		}  	} +	/* Multiply by 10 as we have many subdevices of the same type */ +	if (pdata && pdata->wm831x_num) +		wm831x_num = pdata->wm831x_num * 10; +	else +		wm831x_num = -1; +  	ret = wm831x_irq_init(wm831x, irq);  	if (ret != 0)  		goto err; -	if (wm831x->irq_base) { -		ret = request_threaded_irq(wm831x->irq_base + -					   WM831X_IRQ_AUXADC_DATA, -					   NULL, wm831x_auxadc_irq, 0, -					   "auxadc", wm831x); -		if (ret < 0) -			dev_err(wm831x->dev, "AUXADC IRQ request failed: %d\n", -				ret); -	} +	wm831x_auxadc_init(wm831x);  	/* The core device is up, instantiate the subdevices. */  	switch (parent) {  	case WM8310: -		ret = mfd_add_devices(wm831x->dev, -1, +		ret = mfd_add_devices(wm831x->dev, wm831x_num,  				      wm8310_devs, ARRAY_SIZE(wm8310_devs),  				      NULL, wm831x->irq_base);  		break;  	case WM8311: -		ret = mfd_add_devices(wm831x->dev, -1, +		ret = mfd_add_devices(wm831x->dev, wm831x_num,  				      wm8311_devs, ARRAY_SIZE(wm8311_devs),  				      NULL, wm831x->irq_base); +		if (!pdata || !pdata->disable_touch) +			mfd_add_devices(wm831x->dev, wm831x_num, +					touch_devs, ARRAY_SIZE(touch_devs), +					NULL, wm831x->irq_base);  		break;  	case WM8312: -		ret = mfd_add_devices(wm831x->dev, -1, +		ret = mfd_add_devices(wm831x->dev, wm831x_num,  				      wm8312_devs, ARRAY_SIZE(wm8312_devs),  				      NULL, wm831x->irq_base); +		if (!pdata || !pdata->disable_touch) +			mfd_add_devices(wm831x->dev, wm831x_num, +					touch_devs, ARRAY_SIZE(touch_devs), +					NULL, wm831x->irq_base);  		break;  	case WM8320:  	case WM8321:  	case WM8325:  	case WM8326: -		ret = mfd_add_devices(wm831x->dev, -1, +		ret = mfd_add_devices(wm831x->dev, wm831x_num,  				      wm8320_devs, ARRAY_SIZE(wm8320_devs),  				      NULL, wm831x->irq_base);  		break; @@ -1645,9 +1507,30 @@ int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)  		goto err_irq;  	} +	/* The RTC can only be used if the 32.768kHz crystal is +	 * enabled; this can't be controlled by software at runtime. +	 */ +	ret = wm831x_reg_read(wm831x, WM831X_CLOCK_CONTROL_2); +	if (ret < 0) { +		dev_err(wm831x->dev, "Failed to read clock status: %d\n", ret); +		goto err_irq; +	} + +	if (ret & WM831X_XTAL_ENA) { +		ret = mfd_add_devices(wm831x->dev, wm831x_num, +				      rtc_devs, ARRAY_SIZE(rtc_devs), +				      NULL, wm831x->irq_base); +		if (ret != 0) { +			dev_err(wm831x->dev, "Failed to add RTC: %d\n", ret); +			goto err_irq; +		} +	} else { +		dev_info(wm831x->dev, "32.768kHz clock disabled, no RTC\n"); +	} +  	if (pdata && pdata->backlight) {  		/* Treat errors as non-critical */ -		ret = mfd_add_devices(wm831x->dev, -1, backlight_devs, +		ret = mfd_add_devices(wm831x->dev, wm831x_num, backlight_devs,  				      ARRAY_SIZE(backlight_devs), NULL,  				      wm831x->irq_base);  		if (ret < 0) diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 42b928ec891e..ada1835a5455 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -348,6 +348,15 @@ static void wm831x_irq_sync_unlock(struct irq_data *data)  	struct wm831x *wm831x = irq_data_get_irq_chip_data(data);  	int i; +	for (i = 0; i < ARRAY_SIZE(wm831x->gpio_update); i++) { +		if (wm831x->gpio_update[i]) { +			wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + i, +					WM831X_GPN_INT_MODE | WM831X_GPN_POL, +					wm831x->gpio_update[i]); +			wm831x->gpio_update[i] = 0; +		} +	} +  	for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) {  		/* If there's been a change in the mask write it back  		 * to the hardware. */ @@ -387,7 +396,7 @@ static void wm831x_irq_disable(struct irq_data *data)  static int wm831x_irq_set_type(struct irq_data *data, unsigned int type)  {  	struct wm831x *wm831x = irq_data_get_irq_chip_data(data); -	int val, irq; +	int irq;  	irq = data->irq - wm831x->irq_base; @@ -399,22 +408,30 @@ static int wm831x_irq_set_type(struct irq_data *data, unsigned int type)  			return -EINVAL;  	} +	/* Rebase the IRQ into the GPIO range so we've got a sensible array +	 * index. +	 */ +	irq -= WM831X_IRQ_GPIO_1; + +	/* We set the high bit to flag that we need an update; don't +	 * do the update here as we can be called with the bus lock +	 * held. +	 */  	switch (type) {  	case IRQ_TYPE_EDGE_BOTH: -		val = WM831X_GPN_INT_MODE; +		wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_INT_MODE;  		break;  	case IRQ_TYPE_EDGE_RISING: -		val = WM831X_GPN_POL; +		wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL;  		break;  	case IRQ_TYPE_EDGE_FALLING: -		val = 0; +		wm831x->gpio_update[irq] = 0x10000;  		break;  	default:  		return -EINVAL;  	} -	return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + irq, -			       WM831X_GPN_INT_MODE | WM831X_GPN_POL, val); +	return 0;  }  static struct irq_chip wm831x_irq_chip = { @@ -432,7 +449,7 @@ static irqreturn_t wm831x_irq_thread(int irq, void *data)  {  	struct wm831x *wm831x = data;  	unsigned int i; -	int primary; +	int primary, status_addr;  	int status_regs[WM831X_NUM_IRQ_REGS] = { 0 };  	int read[WM831X_NUM_IRQ_REGS] = { 0 };  	int *status; @@ -467,8 +484,9 @@ static irqreturn_t wm831x_irq_thread(int irq, void *data)  		/* Hopefully there should only be one register to read  		 * each time otherwise we ought to do a block read. */  		if (!read[offset]) { -			*status = wm831x_reg_read(wm831x, -				     irq_data_to_status_reg(&wm831x_irqs[i])); +			status_addr = irq_data_to_status_reg(&wm831x_irqs[i]); + +			*status = wm831x_reg_read(wm831x, status_addr);  			if (*status < 0) {  				dev_err(wm831x->dev,  					"Failed to read IRQ status: %d\n", @@ -477,26 +495,21 @@ static irqreturn_t wm831x_irq_thread(int irq, void *data)  			}  			read[offset] = 1; + +			/* Ignore any bits that we don't think are masked */ +			*status &= ~wm831x->irq_masks_cur[offset]; + +			/* Acknowledge now so we don't miss +			 * notifications while we handle. +			 */ +			wm831x_reg_write(wm831x, status_addr, *status);  		} -		/* Report it if it isn't masked, or forget the status. */ -		if ((*status & ~wm831x->irq_masks_cur[offset]) -		    & wm831x_irqs[i].mask) +		if (*status & wm831x_irqs[i].mask)  			handle_nested_irq(wm831x->irq_base + i); -		else -			*status &= ~wm831x_irqs[i].mask;  	}  out: -	/* Touchscreen interrupts are handled specially in the driver */ -	status_regs[0] &= ~(WM831X_TCHDATA_EINT | WM831X_TCHPD_EINT); - -	for (i = 0; i < ARRAY_SIZE(status_regs); i++) { -		if (status_regs[i]) -			wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1 + i, -					 status_regs[i]); -	} -  	return IRQ_HANDLED;  } @@ -515,13 +528,22 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq)  				 0xffff);  	} -	if (!pdata || !pdata->irq_base) { -		dev_err(wm831x->dev, -			"No interrupt base specified, no interrupts\n"); +	/* Try to dynamically allocate IRQs if no base is specified */ +	if (!pdata || !pdata->irq_base) +		wm831x->irq_base = -1; +	else +		wm831x->irq_base = pdata->irq_base; + +	wm831x->irq_base = irq_alloc_descs(wm831x->irq_base, 0, +					   WM831X_NUM_IRQS, 0); +	if (wm831x->irq_base < 0) { +		dev_warn(wm831x->dev, "Failed to allocate IRQs: %d\n", +			 wm831x->irq_base); +		wm831x->irq_base = 0;  		return 0;  	} -	if (pdata->irq_cmos) +	if (pdata && pdata->irq_cmos)  		i = 0;  	else  		i = WM831X_IRQ_OD; @@ -541,7 +563,6 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq)  	}  	wm831x->irq = irq; -	wm831x->irq_base = pdata->irq_base;  	/* Register them with genirq */  	for (cur_irq = wm831x->irq_base; diff --git a/drivers/mfd/wm8350-irq.c b/drivers/mfd/wm8350-irq.c index ed4b22a167b3..8a1fafd0bf7d 100644 --- a/drivers/mfd/wm8350-irq.c +++ b/drivers/mfd/wm8350-irq.c @@ -473,17 +473,13 @@ int wm8350_irq_init(struct wm8350 *wm8350, int irq,  {  	int ret, cur_irq, i;  	int flags = IRQF_ONESHOT; +	int irq_base = -1;  	if (!irq) {  		dev_warn(wm8350->dev, "No interrupt support, no core IRQ\n");  		return 0;  	} -	if (!pdata || !pdata->irq_base) { -		dev_warn(wm8350->dev, "No interrupt support, no IRQ base\n"); -		return 0; -	} -  	/* Mask top level interrupts */  	wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0xFFFF); @@ -502,7 +498,17 @@ int wm8350_irq_init(struct wm8350 *wm8350, int irq,  	wm8350->chip_irq = irq;  	wm8350->irq_base = pdata->irq_base; -	if (pdata->irq_high) { +	if (pdata && pdata->irq_base > 0) +		irq_base = pdata->irq_base; + +	wm8350->irq_base = irq_alloc_descs(irq_base, 0, ARRAY_SIZE(wm8350_irqs), 0); +	if (wm8350->irq_base < 0) { +		dev_warn(wm8350->dev, "Allocating irqs failed with %d\n", +			wm8350->irq_base); +		return 0; +	} + +	if (pdata && pdata->irq_high) {  		flags |= IRQF_TRIGGER_HIGH;  		wm8350_set_bits(wm8350, WM8350_SYSTEM_CONTROL_1, diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index e198d40292e7..96479c9b1728 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -316,7 +316,7 @@ static int wm8994_suspend(struct device *dev)  static int wm8994_resume(struct device *dev)  {  	struct wm8994 *wm8994 = dev_get_drvdata(dev); -	int ret; +	int ret, i;  	/* We may have lied to the PM core about suspending */  	if (!wm8994->suspended) @@ -329,10 +329,16 @@ static int wm8994_resume(struct device *dev)  		return ret;  	} -	ret = wm8994_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK, -			   WM8994_NUM_IRQ_REGS * 2, &wm8994->irq_masks_cur); -	if (ret < 0) -		dev_err(dev, "Failed to restore interrupt masks: %d\n", ret); +	/* Write register at a time as we use the cache on the CPU so store +	 * it in native endian. +	 */ +	for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) { +		ret = wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK +				       + i, wm8994->irq_masks_cur[i]); +		if (ret < 0) +			dev_err(dev, "Failed to restore interrupt masks: %d\n", +				ret); +	}  	ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2,  			   &wm8994->ldo_regs); @@ -403,7 +409,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq)  		break;  	default:  		BUG(); -		return -EINVAL; +		goto err;  	}  	wm8994->supplies = kzalloc(sizeof(struct regulator_bulk_data) * @@ -425,7 +431,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq)  		break;  	default:  		BUG(); -		return -EINVAL; +		goto err;  	}  	ret = regulator_bulk_get(wm8994->dev, wm8994->num_supplies, @@ -476,13 +482,18 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq)  		goto err_enable;  	} -	switch (ret) { -	case 0: -	case 1: -		if (wm8994->type == WM8994) +	switch (wm8994->type) { +	case WM8994: +		switch (ret) { +		case 0: +		case 1:  			dev_warn(wm8994->dev,  				 "revision %c not fully supported\n",  				 'A' + ret); +			break; +		default: +			break; +		}  		break;  	default:  		break; diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c index 71c6e8f9aedb..d682f7bd112c 100644 --- a/drivers/mfd/wm8994-irq.c +++ b/drivers/mfd/wm8994-irq.c @@ -231,12 +231,6 @@ static irqreturn_t wm8994_irq_thread(int irq, void *data)  		status[i] &= ~wm8994->irq_masks_cur[i];  	} -	/* Report */ -	for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) { -		if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask) -			handle_nested_irq(wm8994->irq_base + i); -	} -  	/* Ack any unmasked IRQs */  	for (i = 0; i < ARRAY_SIZE(status); i++) {  		if (status[i]) @@ -244,6 +238,12 @@ static irqreturn_t wm8994_irq_thread(int irq, void *data)  					 status[i]);  	} +	/* Report */ +	for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) { +		if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask) +			handle_nested_irq(wm8994->irq_base + i); +	} +  	return IRQ_HANDLED;  } | 
