diff options
author | Yunhong Jiang <yunhong.jiang@intel.com> | 2015-08-04 12:17:53 -0700 |
---|---|---|
committer | Yunhong Jiang <yunhong.jiang@intel.com> | 2015-08-04 15:44:42 -0700 |
commit | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (patch) | |
tree | 1c9cafbcd35f783a87880a10f85d1a060db1a563 /kernel/drivers/mfd | |
parent | 98260f3884f4a202f9ca5eabed40b1354c489b29 (diff) |
Add the rt linux 4.1.3-rt3 as base
Import the rt linux 4.1.3-rt3 as OPNFV kvm base.
It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and
the base is:
commit 0917f823c59692d751951bf5ea699a2d1e2f26a2
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Date: Sat Jul 25 12:13:34 2015 +0200
Prepare v4.1.3-rt3
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
We lose all the git history this way and it's not good. We
should apply another opnfv project repo in future.
Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423
Signed-off-by: Yunhong Jiang <yunhong.jiang@intel.com>
Diffstat (limited to 'kernel/drivers/mfd')
188 files changed, 91500 insertions, 0 deletions
diff --git a/kernel/drivers/mfd/88pm800.c b/kernel/drivers/mfd/88pm800.c new file mode 100644 index 000000000..841717a28 --- /dev/null +++ b/kernel/drivers/mfd/88pm800.c @@ -0,0 +1,634 @@ +/* + * Base driver for Marvell 88PM800 + * + * Copyright (C) 2012 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * Joseph(Yossi) Hanin <yhanin@marvell.com> + * Qiao Zhou <zhouqiao@marvell.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/88pm80x.h> +#include <linux/slab.h> + +/* Interrupt Registers */ +#define PM800_INT_STATUS1 (0x05) +#define PM800_ONKEY_INT_STS1 (1 << 0) +#define PM800_EXTON_INT_STS1 (1 << 1) +#define PM800_CHG_INT_STS1 (1 << 2) +#define PM800_BAT_INT_STS1 (1 << 3) +#define PM800_RTC_INT_STS1 (1 << 4) +#define PM800_CLASSD_OC_INT_STS1 (1 << 5) + +#define PM800_INT_STATUS2 (0x06) +#define PM800_VBAT_INT_STS2 (1 << 0) +#define PM800_VSYS_INT_STS2 (1 << 1) +#define PM800_VCHG_INT_STS2 (1 << 2) +#define PM800_TINT_INT_STS2 (1 << 3) +#define PM800_GPADC0_INT_STS2 (1 << 4) +#define PM800_TBAT_INT_STS2 (1 << 5) +#define PM800_GPADC2_INT_STS2 (1 << 6) +#define PM800_GPADC3_INT_STS2 (1 << 7) + +#define PM800_INT_STATUS3 (0x07) + +#define PM800_INT_STATUS4 (0x08) +#define PM800_GPIO0_INT_STS4 (1 << 0) +#define PM800_GPIO1_INT_STS4 (1 << 1) +#define PM800_GPIO2_INT_STS4 (1 << 2) +#define PM800_GPIO3_INT_STS4 (1 << 3) +#define PM800_GPIO4_INT_STS4 (1 << 4) + +#define PM800_INT_ENA_1 (0x09) +#define PM800_ONKEY_INT_ENA1 (1 << 0) +#define PM800_EXTON_INT_ENA1 (1 << 1) +#define PM800_CHG_INT_ENA1 (1 << 2) +#define PM800_BAT_INT_ENA1 (1 << 3) +#define PM800_RTC_INT_ENA1 (1 << 4) +#define PM800_CLASSD_OC_INT_ENA1 (1 << 5) + +#define PM800_INT_ENA_2 (0x0A) +#define PM800_VBAT_INT_ENA2 (1 << 0) +#define PM800_VSYS_INT_ENA2 (1 << 1) +#define PM800_VCHG_INT_ENA2 (1 << 2) +#define PM800_TINT_INT_ENA2 (1 << 3) + +#define PM800_INT_ENA_3 (0x0B) +#define PM800_GPADC0_INT_ENA3 (1 << 0) +#define PM800_GPADC1_INT_ENA3 (1 << 1) +#define PM800_GPADC2_INT_ENA3 (1 << 2) +#define PM800_GPADC3_INT_ENA3 (1 << 3) +#define PM800_GPADC4_INT_ENA3 (1 << 4) + +#define PM800_INT_ENA_4 (0x0C) +#define PM800_GPIO0_INT_ENA4 (1 << 0) +#define PM800_GPIO1_INT_ENA4 (1 << 1) +#define PM800_GPIO2_INT_ENA4 (1 << 2) +#define PM800_GPIO3_INT_ENA4 (1 << 3) +#define PM800_GPIO4_INT_ENA4 (1 << 4) + +/* number of INT_ENA & INT_STATUS regs */ +#define PM800_INT_REG_NUM (4) + +/* Interrupt Number in 88PM800 */ +enum { + PM800_IRQ_ONKEY, /*EN1b0 *//*0 */ + PM800_IRQ_EXTON, /*EN1b1 */ + PM800_IRQ_CHG, /*EN1b2 */ + PM800_IRQ_BAT, /*EN1b3 */ + PM800_IRQ_RTC, /*EN1b4 */ + PM800_IRQ_CLASSD, /*EN1b5 *//*5 */ + PM800_IRQ_VBAT, /*EN2b0 */ + PM800_IRQ_VSYS, /*EN2b1 */ + PM800_IRQ_VCHG, /*EN2b2 */ + PM800_IRQ_TINT, /*EN2b3 */ + PM800_IRQ_GPADC0, /*EN3b0 *//*10 */ + PM800_IRQ_GPADC1, /*EN3b1 */ + PM800_IRQ_GPADC2, /*EN3b2 */ + PM800_IRQ_GPADC3, /*EN3b3 */ + PM800_IRQ_GPADC4, /*EN3b4 */ + PM800_IRQ_GPIO0, /*EN4b0 *//*15 */ + PM800_IRQ_GPIO1, /*EN4b1 */ + PM800_IRQ_GPIO2, /*EN4b2 */ + PM800_IRQ_GPIO3, /*EN4b3 */ + PM800_IRQ_GPIO4, /*EN4b4 *//*19 */ + PM800_MAX_IRQ, +}; + +/* PM800: generation identification number */ +#define PM800_CHIP_GEN_ID_NUM 0x3 + +static const struct i2c_device_id pm80x_id_table[] = { + {"88PM800", 0}, + {} /* NULL terminated */ +}; +MODULE_DEVICE_TABLE(i2c, pm80x_id_table); + +static struct resource rtc_resources[] = { + { + .name = "88pm80x-rtc", + .start = PM800_IRQ_RTC, + .end = PM800_IRQ_RTC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell rtc_devs[] = { + { + .name = "88pm80x-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), + .resources = &rtc_resources[0], + .id = -1, + }, +}; + +static struct resource onkey_resources[] = { + { + .name = "88pm80x-onkey", + .start = PM800_IRQ_ONKEY, + .end = PM800_IRQ_ONKEY, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell onkey_devs[] = { + { + .name = "88pm80x-onkey", + .num_resources = 1, + .resources = &onkey_resources[0], + .id = -1, + }, +}; + +static const struct mfd_cell regulator_devs[] = { + { + .name = "88pm80x-regulator", + .id = -1, + }, +}; + +static const struct regmap_irq pm800_irqs[] = { + /* INT0 */ + [PM800_IRQ_ONKEY] = { + .mask = PM800_ONKEY_INT_ENA1, + }, + [PM800_IRQ_EXTON] = { + .mask = PM800_EXTON_INT_ENA1, + }, + [PM800_IRQ_CHG] = { + .mask = PM800_CHG_INT_ENA1, + }, + [PM800_IRQ_BAT] = { + .mask = PM800_BAT_INT_ENA1, + }, + [PM800_IRQ_RTC] = { + .mask = PM800_RTC_INT_ENA1, + }, + [PM800_IRQ_CLASSD] = { + .mask = PM800_CLASSD_OC_INT_ENA1, + }, + /* INT1 */ + [PM800_IRQ_VBAT] = { + .reg_offset = 1, + .mask = PM800_VBAT_INT_ENA2, + }, + [PM800_IRQ_VSYS] = { + .reg_offset = 1, + .mask = PM800_VSYS_INT_ENA2, + }, + [PM800_IRQ_VCHG] = { + .reg_offset = 1, + .mask = PM800_VCHG_INT_ENA2, + }, + [PM800_IRQ_TINT] = { + .reg_offset = 1, + .mask = PM800_TINT_INT_ENA2, + }, + /* INT2 */ + [PM800_IRQ_GPADC0] = { + .reg_offset = 2, + .mask = PM800_GPADC0_INT_ENA3, + }, + [PM800_IRQ_GPADC1] = { + .reg_offset = 2, + .mask = PM800_GPADC1_INT_ENA3, + }, + [PM800_IRQ_GPADC2] = { + .reg_offset = 2, + .mask = PM800_GPADC2_INT_ENA3, + }, + [PM800_IRQ_GPADC3] = { + .reg_offset = 2, + .mask = PM800_GPADC3_INT_ENA3, + }, + [PM800_IRQ_GPADC4] = { + .reg_offset = 2, + .mask = PM800_GPADC4_INT_ENA3, + }, + /* INT3 */ + [PM800_IRQ_GPIO0] = { + .reg_offset = 3, + .mask = PM800_GPIO0_INT_ENA4, + }, + [PM800_IRQ_GPIO1] = { + .reg_offset = 3, + .mask = PM800_GPIO1_INT_ENA4, + }, + [PM800_IRQ_GPIO2] = { + .reg_offset = 3, + .mask = PM800_GPIO2_INT_ENA4, + }, + [PM800_IRQ_GPIO3] = { + .reg_offset = 3, + .mask = PM800_GPIO3_INT_ENA4, + }, + [PM800_IRQ_GPIO4] = { + .reg_offset = 3, + .mask = PM800_GPIO4_INT_ENA4, + }, +}; + +static int device_gpadc_init(struct pm80x_chip *chip, + struct pm80x_platform_data *pdata) +{ + struct pm80x_subchip *subchip = chip->subchip; + struct regmap *map = subchip->regmap_gpadc; + int data = 0, mask = 0, ret = 0; + + if (!map) { + dev_warn(chip->dev, + "Warning: gpadc regmap is not available!\n"); + return -EINVAL; + } + /* + * initialize GPADC without activating it turn on GPADC + * measurments + */ + ret = regmap_update_bits(map, + PM800_GPADC_MISC_CONFIG2, + PM800_GPADC_MISC_GPFSM_EN, + PM800_GPADC_MISC_GPFSM_EN); + if (ret < 0) + goto out; + /* + * This function configures the ADC as requires for + * CP implementation.CP does not "own" the ADC configuration + * registers and relies on AP. + * Reason: enable automatic ADC measurements needed + * for CP to get VBAT and RF temperature readings. + */ + ret = regmap_update_bits(map, PM800_GPADC_MEAS_EN1, + PM800_MEAS_EN1_VBAT, PM800_MEAS_EN1_VBAT); + if (ret < 0) + goto out; + ret = regmap_update_bits(map, PM800_GPADC_MEAS_EN2, + (PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN), + (PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN)); + if (ret < 0) + goto out; + + /* + * the defult of PM800 is GPADC operates at 100Ks/s rate + * and Number of GPADC slots with active current bias prior + * to GPADC sampling = 1 slot for all GPADCs set for + * Temprature mesurmants + */ + mask = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 | + PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3); + + if (pdata && (pdata->batt_det == 0)) + data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 | + PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3); + else + data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN2 | + PM800_GPADC_GP_BIAS_EN3); + + ret = regmap_update_bits(map, PM800_GP_BIAS_ENA1, mask, data); + if (ret < 0) + goto out; + + dev_info(chip->dev, "pm800 device_gpadc_init: Done\n"); + return 0; + +out: + dev_info(chip->dev, "pm800 device_gpadc_init: Failed!\n"); + return ret; +} + +static int device_onkey_init(struct pm80x_chip *chip, + struct pm80x_platform_data *pdata) +{ + int ret; + + ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], + ARRAY_SIZE(onkey_devs), &onkey_resources[0], 0, + NULL); + if (ret) { + dev_err(chip->dev, "Failed to add onkey subdev\n"); + return ret; + } + + return 0; +} + +static int device_rtc_init(struct pm80x_chip *chip, + struct pm80x_platform_data *pdata) +{ + int ret; + + if (pdata) { + rtc_devs[0].platform_data = pdata->rtc; + rtc_devs[0].pdata_size = + pdata->rtc ? sizeof(struct pm80x_rtc_pdata) : 0; + } + ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0], + ARRAY_SIZE(rtc_devs), NULL, 0, NULL); + if (ret) { + dev_err(chip->dev, "Failed to add rtc subdev\n"); + return ret; + } + + return 0; +} + +static int device_regulator_init(struct pm80x_chip *chip, + struct pm80x_platform_data *pdata) +{ + int ret; + + ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], + ARRAY_SIZE(regulator_devs), NULL, 0, NULL); + if (ret) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + return ret; + } + + return 0; +} + +static int device_irq_init_800(struct pm80x_chip *chip) +{ + struct regmap *map = chip->regmap; + unsigned long flags = IRQF_ONESHOT; + int data, mask, ret = -EINVAL; + + if (!map || !chip->irq) { + dev_err(chip->dev, "incorrect parameters\n"); + return -EINVAL; + } + + /* + * irq_mode defines the way of clearing interrupt. it's read-clear by + * default. + */ + mask = + PM800_WAKEUP2_INV_INT | PM800_WAKEUP2_INT_CLEAR | + PM800_WAKEUP2_INT_MASK; + + data = PM800_WAKEUP2_INT_CLEAR; + ret = regmap_update_bits(map, PM800_WAKEUP2, mask, data); + + if (ret < 0) + goto out; + + ret = + regmap_add_irq_chip(chip->regmap, chip->irq, flags, -1, + chip->regmap_irq_chip, &chip->irq_data); + +out: + return ret; +} + +static void device_irq_exit_800(struct pm80x_chip *chip) +{ + regmap_del_irq_chip(chip->irq, chip->irq_data); +} + +static struct regmap_irq_chip pm800_irq_chip = { + .name = "88pm800", + .irqs = pm800_irqs, + .num_irqs = ARRAY_SIZE(pm800_irqs), + + .num_regs = 4, + .status_base = PM800_INT_STATUS1, + .mask_base = PM800_INT_ENA_1, + .ack_base = PM800_INT_STATUS1, + .mask_invert = 1, +}; + +static int pm800_pages_init(struct pm80x_chip *chip) +{ + struct pm80x_subchip *subchip; + struct i2c_client *client = chip->client; + + int ret = 0; + + subchip = chip->subchip; + if (!subchip || !subchip->power_page_addr || !subchip->gpadc_page_addr) + return -ENODEV; + + /* PM800 block power page */ + subchip->power_page = i2c_new_dummy(client->adapter, + subchip->power_page_addr); + if (subchip->power_page == NULL) { + ret = -ENODEV; + goto out; + } + + subchip->regmap_power = devm_regmap_init_i2c(subchip->power_page, + &pm80x_regmap_config); + if (IS_ERR(subchip->regmap_power)) { + ret = PTR_ERR(subchip->regmap_power); + dev_err(chip->dev, + "Failed to allocate regmap_power: %d\n", ret); + goto out; + } + + i2c_set_clientdata(subchip->power_page, chip); + + /* PM800 block GPADC */ + subchip->gpadc_page = i2c_new_dummy(client->adapter, + subchip->gpadc_page_addr); + if (subchip->gpadc_page == NULL) { + ret = -ENODEV; + goto out; + } + + subchip->regmap_gpadc = devm_regmap_init_i2c(subchip->gpadc_page, + &pm80x_regmap_config); + if (IS_ERR(subchip->regmap_gpadc)) { + ret = PTR_ERR(subchip->regmap_gpadc); + dev_err(chip->dev, + "Failed to allocate regmap_gpadc: %d\n", ret); + goto out; + } + i2c_set_clientdata(subchip->gpadc_page, chip); + +out: + return ret; +} + +static void pm800_pages_exit(struct pm80x_chip *chip) +{ + struct pm80x_subchip *subchip; + + subchip = chip->subchip; + + if (subchip && subchip->power_page) + i2c_unregister_device(subchip->power_page); + + if (subchip && subchip->gpadc_page) + i2c_unregister_device(subchip->gpadc_page); +} + +static int device_800_init(struct pm80x_chip *chip, + struct pm80x_platform_data *pdata) +{ + int ret; + unsigned int val; + + /* + * alarm wake up bit will be clear in device_irq_init(), + * read before that + */ + ret = regmap_read(chip->regmap, PM800_RTC_CONTROL, &val); + if (ret < 0) { + dev_err(chip->dev, "Failed to read RTC register: %d\n", ret); + goto out; + } + if (val & PM800_ALARM_WAKEUP) { + if (pdata && pdata->rtc) + pdata->rtc->rtc_wakeup = 1; + } + + ret = device_gpadc_init(chip, pdata); + if (ret < 0) { + dev_err(chip->dev, "[%s]Failed to init gpadc\n", __func__); + goto out; + } + + chip->regmap_irq_chip = &pm800_irq_chip; + + ret = device_irq_init_800(chip); + if (ret < 0) { + dev_err(chip->dev, "[%s]Failed to init pm800 irq\n", __func__); + goto out; + } + + ret = device_onkey_init(chip, pdata); + if (ret) { + dev_err(chip->dev, "Failed to add onkey subdev\n"); + goto out_dev; + } + + ret = device_rtc_init(chip, pdata); + if (ret) { + dev_err(chip->dev, "Failed to add rtc subdev\n"); + goto out; + } + + ret = device_regulator_init(chip, pdata); + if (ret) { + dev_err(chip->dev, "Failed to add regulators subdev\n"); + goto out; + } + + return 0; +out_dev: + mfd_remove_devices(chip->dev); + device_irq_exit_800(chip); +out: + return ret; +} + +static int pm800_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct pm80x_chip *chip; + struct pm80x_platform_data *pdata = dev_get_platdata(&client->dev); + struct pm80x_subchip *subchip; + + ret = pm80x_init(client); + if (ret) { + dev_err(&client->dev, "pm800_init fail\n"); + goto out_init; + } + + chip = i2c_get_clientdata(client); + + /* init subchip for PM800 */ + subchip = + devm_kzalloc(&client->dev, sizeof(struct pm80x_subchip), + GFP_KERNEL); + if (!subchip) { + ret = -ENOMEM; + goto err_subchip_alloc; + } + + /* pm800 has 2 addtional pages to support power and gpadc. */ + subchip->power_page_addr = client->addr + 1; + subchip->gpadc_page_addr = client->addr + 2; + chip->subchip = subchip; + + ret = pm800_pages_init(chip); + if (ret) { + dev_err(&client->dev, "pm800_pages_init failed!\n"); + goto err_device_init; + } + + ret = device_800_init(chip, pdata); + if (ret) { + dev_err(chip->dev, "Failed to initialize 88pm800 devices\n"); + goto err_device_init; + } + + if (pdata && pdata->plat_config) + pdata->plat_config(chip, pdata); + + return 0; + +err_device_init: + pm800_pages_exit(chip); +err_subchip_alloc: + pm80x_deinit(); +out_init: + return ret; +} + +static int pm800_remove(struct i2c_client *client) +{ + struct pm80x_chip *chip = i2c_get_clientdata(client); + + mfd_remove_devices(chip->dev); + device_irq_exit_800(chip); + + pm800_pages_exit(chip); + pm80x_deinit(); + + return 0; +} + +static struct i2c_driver pm800_driver = { + .driver = { + .name = "88PM800", + .owner = THIS_MODULE, + .pm = &pm80x_pm_ops, + }, + .probe = pm800_probe, + .remove = pm800_remove, + .id_table = pm80x_id_table, +}; + +static int __init pm800_i2c_init(void) +{ + return i2c_add_driver(&pm800_driver); +} +subsys_initcall(pm800_i2c_init); + +static void __exit pm800_i2c_exit(void) +{ + i2c_del_driver(&pm800_driver); +} +module_exit(pm800_i2c_exit); + +MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM800"); +MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/88pm805.c b/kernel/drivers/mfd/88pm805.c new file mode 100644 index 000000000..e9d506446 --- /dev/null +++ b/kernel/drivers/mfd/88pm805.c @@ -0,0 +1,292 @@ +/* + * Base driver for Marvell 88PM805 + * + * Copyright (C) 2012 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * Joseph(Yossi) Hanin <yhanin@marvell.com> + * Qiao Zhou <zhouqiao@marvell.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/88pm80x.h> +#include <linux/slab.h> +#include <linux/delay.h> + +static const struct i2c_device_id pm80x_id_table[] = { + {"88PM805", 0}, + {} /* NULL terminated */ +}; +MODULE_DEVICE_TABLE(i2c, pm80x_id_table); + +/* Interrupt Number in 88PM805 */ +enum { + PM805_IRQ_LDO_OFF, /*0 */ + PM805_IRQ_SRC_DPLL_LOCK, /*1 */ + PM805_IRQ_CLIP_FAULT, + PM805_IRQ_MIC_CONFLICT, + PM805_IRQ_HP2_SHRT, + PM805_IRQ_HP1_SHRT, /*5 */ + PM805_IRQ_FINE_PLL_FAULT, + PM805_IRQ_RAW_PLL_FAULT, + PM805_IRQ_VOLP_BTN_DET, + PM805_IRQ_VOLM_BTN_DET, + PM805_IRQ_SHRT_BTN_DET, /*10 */ + PM805_IRQ_MIC_DET, /*11 */ + + PM805_MAX_IRQ, +}; + +static struct resource codec_resources[] = { + { + /* Headset microphone insertion or removal */ + .name = "micin", + .start = PM805_IRQ_MIC_DET, + .end = PM805_IRQ_MIC_DET, + .flags = IORESOURCE_IRQ, + }, + { + /* Audio short HP1 */ + .name = "audio-short1", + .start = PM805_IRQ_HP1_SHRT, + .end = PM805_IRQ_HP1_SHRT, + .flags = IORESOURCE_IRQ, + }, + { + /* Audio short HP2 */ + .name = "audio-short2", + .start = PM805_IRQ_HP2_SHRT, + .end = PM805_IRQ_HP2_SHRT, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell codec_devs[] = { + { + .name = "88pm80x-codec", + .num_resources = ARRAY_SIZE(codec_resources), + .resources = &codec_resources[0], + .id = -1, + }, +}; + +static struct regmap_irq pm805_irqs[] = { + /* INT0 */ + [PM805_IRQ_LDO_OFF] = { + .mask = PM805_INT1_HP1_SHRT, + }, + [PM805_IRQ_SRC_DPLL_LOCK] = { + .mask = PM805_INT1_HP2_SHRT, + }, + [PM805_IRQ_CLIP_FAULT] = { + .mask = PM805_INT1_MIC_CONFLICT, + }, + [PM805_IRQ_MIC_CONFLICT] = { + .mask = PM805_INT1_CLIP_FAULT, + }, + [PM805_IRQ_HP2_SHRT] = { + .mask = PM805_INT1_LDO_OFF, + }, + [PM805_IRQ_HP1_SHRT] = { + .mask = PM805_INT1_SRC_DPLL_LOCK, + }, + /* INT1 */ + [PM805_IRQ_FINE_PLL_FAULT] = { + .reg_offset = 1, + .mask = PM805_INT2_MIC_DET, + }, + [PM805_IRQ_RAW_PLL_FAULT] = { + .reg_offset = 1, + .mask = PM805_INT2_SHRT_BTN_DET, + }, + [PM805_IRQ_VOLP_BTN_DET] = { + .reg_offset = 1, + .mask = PM805_INT2_VOLM_BTN_DET, + }, + [PM805_IRQ_VOLM_BTN_DET] = { + .reg_offset = 1, + .mask = PM805_INT2_VOLP_BTN_DET, + }, + [PM805_IRQ_SHRT_BTN_DET] = { + .reg_offset = 1, + .mask = PM805_INT2_RAW_PLL_FAULT, + }, + [PM805_IRQ_MIC_DET] = { + .reg_offset = 1, + .mask = PM805_INT2_FINE_PLL_FAULT, + }, +}; + +static int device_irq_init_805(struct pm80x_chip *chip) +{ + struct regmap *map = chip->regmap; + unsigned long flags = IRQF_ONESHOT; + int data, mask, ret = -EINVAL; + + if (!map || !chip->irq) { + dev_err(chip->dev, "incorrect parameters\n"); + return -EINVAL; + } + + /* + * irq_mode defines the way of clearing interrupt. it's read-clear by + * default. + */ + mask = + PM805_STATUS0_INT_CLEAR | PM805_STATUS0_INV_INT | + PM800_STATUS0_INT_MASK; + + data = PM805_STATUS0_INT_CLEAR; + ret = regmap_update_bits(map, PM805_INT_STATUS0, mask, data); + /* + * PM805_INT_STATUS is under 32K clock domain, so need to + * add proper delay before the next I2C register access. + */ + usleep_range(1000, 3000); + + if (ret < 0) + goto out; + + ret = + regmap_add_irq_chip(chip->regmap, chip->irq, flags, -1, + chip->regmap_irq_chip, &chip->irq_data); + +out: + return ret; +} + +static void device_irq_exit_805(struct pm80x_chip *chip) +{ + regmap_del_irq_chip(chip->irq, chip->irq_data); +} + +static struct regmap_irq_chip pm805_irq_chip = { + .name = "88pm805", + .irqs = pm805_irqs, + .num_irqs = ARRAY_SIZE(pm805_irqs), + + .num_regs = 2, + .status_base = PM805_INT_STATUS1, + .mask_base = PM805_INT_MASK1, + .ack_base = PM805_INT_STATUS1, +}; + +static int device_805_init(struct pm80x_chip *chip) +{ + int ret = 0; + struct regmap *map = chip->regmap; + + if (!map) { + dev_err(chip->dev, "regmap is invalid\n"); + return -EINVAL; + } + + chip->regmap_irq_chip = &pm805_irq_chip; + + ret = device_irq_init_805(chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to init pm805 irq!\n"); + goto out_irq_init; + } + + ret = mfd_add_devices(chip->dev, 0, &codec_devs[0], + ARRAY_SIZE(codec_devs), &codec_resources[0], 0, + NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add codec subdev\n"); + goto out_codec; + } else + dev_info(chip->dev, "[%s]:Added mfd codec_devs\n", __func__); + + return 0; + +out_codec: + device_irq_exit_805(chip); +out_irq_init: + return ret; +} + +static int pm805_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct pm80x_chip *chip; + struct pm80x_platform_data *pdata = dev_get_platdata(&client->dev); + + ret = pm80x_init(client); + if (ret) { + dev_err(&client->dev, "pm805_init fail!\n"); + goto out_init; + } + + chip = i2c_get_clientdata(client); + + ret = device_805_init(chip); + if (ret) { + dev_err(chip->dev, "Failed to initialize 88pm805 devices\n"); + goto err_805_init; + } + + if (pdata && pdata->plat_config) + pdata->plat_config(chip, pdata); + +err_805_init: + pm80x_deinit(); +out_init: + return ret; +} + +static int pm805_remove(struct i2c_client *client) +{ + struct pm80x_chip *chip = i2c_get_clientdata(client); + + mfd_remove_devices(chip->dev); + device_irq_exit_805(chip); + + pm80x_deinit(); + + return 0; +} + +static struct i2c_driver pm805_driver = { + .driver = { + .name = "88PM805", + .owner = THIS_MODULE, + .pm = &pm80x_pm_ops, + }, + .probe = pm805_probe, + .remove = pm805_remove, + .id_table = pm80x_id_table, +}; + +static int __init pm805_i2c_init(void) +{ + return i2c_add_driver(&pm805_driver); +} +subsys_initcall(pm805_i2c_init); + +static void __exit pm805_i2c_exit(void) +{ + i2c_del_driver(&pm805_driver); +} +module_exit(pm805_i2c_exit); + +MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM805"); +MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/88pm80x.c b/kernel/drivers/mfd/88pm80x.c new file mode 100644 index 000000000..5e72f65ef --- /dev/null +++ b/kernel/drivers/mfd/88pm80x.c @@ -0,0 +1,164 @@ +/* + * I2C driver for Marvell 88PM80x + * + * Copyright (C) 2012 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * Joseph(Yossi) Hanin <yhanin@marvell.com> + * Qiao Zhou <zhouqiao@marvell.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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mfd/88pm80x.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/err.h> + +/* 88pm80x chips have same definition for chip id register. */ +#define PM80X_CHIP_ID (0x00) +#define PM80X_CHIP_ID_NUM(x) (((x) >> 5) & 0x7) +#define PM80X_CHIP_ID_REVISION(x) ((x) & 0x1F) + +struct pm80x_chip_mapping { + unsigned int id; + int type; +}; + +static struct pm80x_chip_mapping chip_mapping[] = { + /* 88PM800 chip id number */ + {0x3, CHIP_PM800}, + /* 88PM805 chip id number */ + {0x0, CHIP_PM805}, +}; + +/* + * workaround: some registers needed by pm805 are defined in pm800, so + * need to use this global variable to maintain the relation between + * pm800 and pm805. would remove it after HW chip fixes the issue. + */ +static struct pm80x_chip *g_pm80x_chip; + +const struct regmap_config pm80x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; +EXPORT_SYMBOL_GPL(pm80x_regmap_config); + + +int pm80x_init(struct i2c_client *client) +{ + struct pm80x_chip *chip; + struct regmap *map; + unsigned int val; + int i, ret = 0; + + chip = + devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + map = devm_regmap_init_i2c(client, &pm80x_regmap_config); + if (IS_ERR(map)) { + ret = PTR_ERR(map); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + chip->client = client; + chip->regmap = map; + + chip->irq = client->irq; + + chip->dev = &client->dev; + dev_set_drvdata(chip->dev, chip); + i2c_set_clientdata(chip->client, chip); + + ret = regmap_read(chip->regmap, PM80X_CHIP_ID, &val); + if (ret < 0) { + dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(chip_mapping); i++) { + if (chip_mapping[i].id == PM80X_CHIP_ID_NUM(val)) { + chip->type = chip_mapping[i].type; + break; + } + } + + if (i == ARRAY_SIZE(chip_mapping)) { + dev_err(chip->dev, + "Failed to detect Marvell 88PM800:ChipID[0x%x]\n", val); + return -EINVAL; + } + + device_init_wakeup(&client->dev, 1); + + /* + * workaround: set g_pm80x_chip to the first probed chip. if the + * second chip is probed, just point to the companion to each + * other so that pm805 can access those specific register. would + * remove it after HW chip fixes the issue. + */ + if (!g_pm80x_chip) + g_pm80x_chip = chip; + else { + chip->companion = g_pm80x_chip->client; + g_pm80x_chip->companion = chip->client; + } + + return 0; +} +EXPORT_SYMBOL_GPL(pm80x_init); + +int pm80x_deinit(void) +{ + /* + * workaround: clear the dependency between pm800 and pm805. + * would remove it after HW chip fixes the issue. + */ + if (g_pm80x_chip->companion) + g_pm80x_chip->companion = NULL; + else + g_pm80x_chip = NULL; + return 0; +} +EXPORT_SYMBOL_GPL(pm80x_deinit); + +#ifdef CONFIG_PM_SLEEP +static int pm80x_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct pm80x_chip *chip = i2c_get_clientdata(client); + + if (chip && chip->wu_flag) + if (device_may_wakeup(chip->dev)) + enable_irq_wake(chip->irq); + + return 0; +} + +static int pm80x_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct pm80x_chip *chip = i2c_get_clientdata(client); + + if (chip && chip->wu_flag) + if (device_may_wakeup(chip->dev)) + disable_irq_wake(chip->irq); + + return 0; +} +#endif + +SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume); +EXPORT_SYMBOL_GPL(pm80x_pm_ops); + +MODULE_DESCRIPTION("I2C Driver for Marvell 88PM80x"); +MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/88pm860x-core.c b/kernel/drivers/mfd/88pm860x-core.c new file mode 100644 index 000000000..d2a85cde6 --- /dev/null +++ b/kernel/drivers/mfd/88pm860x-core.c @@ -0,0 +1,1288 @@ +/* + * Base driver for Marvell 88PM8607 + * + * Copyright (C) 2009 Marvell International Ltd. + * + * Author: Haojian Zhuang <haojian.zhuang@marvell.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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/mfd/core.h> +#include <linux/mfd/88pm860x.h> +#include <linux/regulator/machine.h> +#include <linux/power/charger-manager.h> + +#define INT_STATUS_NUM 3 + +static struct resource bk0_resources[] = { + {2, 2, "duty cycle", IORESOURCE_REG, }, + {3, 3, "always on", IORESOURCE_REG, }, + {3, 3, "current", IORESOURCE_REG, }, +}; +static struct resource bk1_resources[] = { + {4, 4, "duty cycle", IORESOURCE_REG, }, + {5, 5, "always on", IORESOURCE_REG, }, + {5, 5, "current", IORESOURCE_REG, }, +}; +static struct resource bk2_resources[] = { + {6, 6, "duty cycle", IORESOURCE_REG, }, + {7, 7, "always on", IORESOURCE_REG, }, + {5, 5, "current", IORESOURCE_REG, }, +}; + +static struct resource led0_resources[] = { + /* RGB1 Red LED */ + {0xd, 0xd, "control", IORESOURCE_REG, }, + {0xc, 0xc, "blink", IORESOURCE_REG, }, +}; +static struct resource led1_resources[] = { + /* RGB1 Green LED */ + {0xe, 0xe, "control", IORESOURCE_REG, }, + {0xc, 0xc, "blink", IORESOURCE_REG, }, +}; +static struct resource led2_resources[] = { + /* RGB1 Blue LED */ + {0xf, 0xf, "control", IORESOURCE_REG, }, + {0xc, 0xc, "blink", IORESOURCE_REG, }, +}; +static struct resource led3_resources[] = { + /* RGB2 Red LED */ + {0x9, 0x9, "control", IORESOURCE_REG, }, + {0x8, 0x8, "blink", IORESOURCE_REG, }, +}; +static struct resource led4_resources[] = { + /* RGB2 Green LED */ + {0xa, 0xa, "control", IORESOURCE_REG, }, + {0x8, 0x8, "blink", IORESOURCE_REG, }, +}; +static struct resource led5_resources[] = { + /* RGB2 Blue LED */ + {0xb, 0xb, "control", IORESOURCE_REG, }, + {0x8, 0x8, "blink", IORESOURCE_REG, }, +}; + +static struct resource buck1_resources[] = { + {0x24, 0x24, "buck set", IORESOURCE_REG, }, +}; +static struct resource buck2_resources[] = { + {0x25, 0x25, "buck set", IORESOURCE_REG, }, +}; +static struct resource buck3_resources[] = { + {0x26, 0x26, "buck set", IORESOURCE_REG, }, +}; +static struct resource ldo1_resources[] = { + {0x10, 0x10, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo2_resources[] = { + {0x11, 0x11, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo3_resources[] = { + {0x12, 0x12, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo4_resources[] = { + {0x13, 0x13, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo5_resources[] = { + {0x14, 0x14, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo6_resources[] = { + {0x15, 0x15, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo7_resources[] = { + {0x16, 0x16, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo8_resources[] = { + {0x17, 0x17, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo9_resources[] = { + {0x18, 0x18, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo10_resources[] = { + {0x19, 0x19, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo12_resources[] = { + {0x1a, 0x1a, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo_vibrator_resources[] = { + {0x28, 0x28, "ldo set", IORESOURCE_REG, }, +}; +static struct resource ldo14_resources[] = { + {0x1b, 0x1b, "ldo set", IORESOURCE_REG, }, +}; + +static struct resource touch_resources[] = { + {PM8607_IRQ_PEN, PM8607_IRQ_PEN, "touch", IORESOURCE_IRQ,}, +}; + +static struct resource onkey_resources[] = { + {PM8607_IRQ_ONKEY, PM8607_IRQ_ONKEY, "onkey", IORESOURCE_IRQ,}, +}; + +static struct resource codec_resources[] = { + /* Headset microphone insertion or removal */ + {PM8607_IRQ_MICIN, PM8607_IRQ_MICIN, "micin", IORESOURCE_IRQ,}, + /* Hook-switch press or release */ + {PM8607_IRQ_HOOK, PM8607_IRQ_HOOK, "hook", IORESOURCE_IRQ,}, + /* Headset insertion or removal */ + {PM8607_IRQ_HEADSET, PM8607_IRQ_HEADSET, "headset", IORESOURCE_IRQ,}, + /* Audio short */ + {PM8607_IRQ_AUDIO_SHORT, PM8607_IRQ_AUDIO_SHORT, "audio-short", + IORESOURCE_IRQ,}, +}; + +static struct resource battery_resources[] = { + {PM8607_IRQ_CC, PM8607_IRQ_CC, "columb counter", IORESOURCE_IRQ,}, + {PM8607_IRQ_BAT, PM8607_IRQ_BAT, "battery", IORESOURCE_IRQ,}, +}; + +static struct resource charger_resources[] = { + {PM8607_IRQ_CHG, PM8607_IRQ_CHG, "charger detect", IORESOURCE_IRQ,}, + {PM8607_IRQ_CHG_DONE, PM8607_IRQ_CHG_DONE, "charging done", + IORESOURCE_IRQ,}, + {PM8607_IRQ_CHG_FAIL, PM8607_IRQ_CHG_FAIL, "charging timeout", + IORESOURCE_IRQ,}, + {PM8607_IRQ_CHG_FAULT, PM8607_IRQ_CHG_FAULT, "charging fault", + IORESOURCE_IRQ,}, + {PM8607_IRQ_GPADC1, PM8607_IRQ_GPADC1, "battery temperature", + IORESOURCE_IRQ,}, + {PM8607_IRQ_VBAT, PM8607_IRQ_VBAT, "battery voltage", IORESOURCE_IRQ,}, + {PM8607_IRQ_VCHG, PM8607_IRQ_VCHG, "vchg voltage", IORESOURCE_IRQ,}, +}; + +static struct resource rtc_resources[] = { + {PM8607_IRQ_RTC, PM8607_IRQ_RTC, "rtc", IORESOURCE_IRQ,}, +}; + +static struct mfd_cell bk_devs[] = { + { + .name = "88pm860x-backlight", + .id = 0, + .num_resources = ARRAY_SIZE(bk0_resources), + .resources = bk0_resources, + }, { + .name = "88pm860x-backlight", + .id = 1, + .num_resources = ARRAY_SIZE(bk1_resources), + .resources = bk1_resources, + }, { + .name = "88pm860x-backlight", + .id = 2, + .num_resources = ARRAY_SIZE(bk2_resources), + .resources = bk2_resources, + }, +}; + +static struct mfd_cell led_devs[] = { + { + .name = "88pm860x-led", + .id = 0, + .num_resources = ARRAY_SIZE(led0_resources), + .resources = led0_resources, + }, { + .name = "88pm860x-led", + .id = 1, + .num_resources = ARRAY_SIZE(led1_resources), + .resources = led1_resources, + }, { + .name = "88pm860x-led", + .id = 2, + .num_resources = ARRAY_SIZE(led2_resources), + .resources = led2_resources, + }, { + .name = "88pm860x-led", + .id = 3, + .num_resources = ARRAY_SIZE(led3_resources), + .resources = led3_resources, + }, { + .name = "88pm860x-led", + .id = 4, + .num_resources = ARRAY_SIZE(led4_resources), + .resources = led4_resources, + }, { + .name = "88pm860x-led", + .id = 5, + .num_resources = ARRAY_SIZE(led5_resources), + .resources = led5_resources, + }, +}; + +static struct mfd_cell reg_devs[] = { + { + .name = "88pm860x-regulator", + .id = 0, + .num_resources = ARRAY_SIZE(buck1_resources), + .resources = buck1_resources, + }, { + .name = "88pm860x-regulator", + .id = 1, + .num_resources = ARRAY_SIZE(buck2_resources), + .resources = buck2_resources, + }, { + .name = "88pm860x-regulator", + .id = 2, + .num_resources = ARRAY_SIZE(buck3_resources), + .resources = buck3_resources, + }, { + .name = "88pm860x-regulator", + .id = 3, + .num_resources = ARRAY_SIZE(ldo1_resources), + .resources = ldo1_resources, + }, { + .name = "88pm860x-regulator", + .id = 4, + .num_resources = ARRAY_SIZE(ldo2_resources), + .resources = ldo2_resources, + }, { + .name = "88pm860x-regulator", + .id = 5, + .num_resources = ARRAY_SIZE(ldo3_resources), + .resources = ldo3_resources, + }, { + .name = "88pm860x-regulator", + .id = 6, + .num_resources = ARRAY_SIZE(ldo4_resources), + .resources = ldo4_resources, + }, { + .name = "88pm860x-regulator", + .id = 7, + .num_resources = ARRAY_SIZE(ldo5_resources), + .resources = ldo5_resources, + }, { + .name = "88pm860x-regulator", + .id = 8, + .num_resources = ARRAY_SIZE(ldo6_resources), + .resources = ldo6_resources, + }, { + .name = "88pm860x-regulator", + .id = 9, + .num_resources = ARRAY_SIZE(ldo7_resources), + .resources = ldo7_resources, + }, { + .name = "88pm860x-regulator", + .id = 10, + .num_resources = ARRAY_SIZE(ldo8_resources), + .resources = ldo8_resources, + }, { + .name = "88pm860x-regulator", + .id = 11, + .num_resources = ARRAY_SIZE(ldo9_resources), + .resources = ldo9_resources, + }, { + .name = "88pm860x-regulator", + .id = 12, + .num_resources = ARRAY_SIZE(ldo10_resources), + .resources = ldo10_resources, + }, { + .name = "88pm860x-regulator", + .id = 13, + .num_resources = ARRAY_SIZE(ldo12_resources), + .resources = ldo12_resources, + }, { + .name = "88pm860x-regulator", + .id = 14, + .num_resources = ARRAY_SIZE(ldo_vibrator_resources), + .resources = ldo_vibrator_resources, + }, { + .name = "88pm860x-regulator", + .id = 15, + .num_resources = ARRAY_SIZE(ldo14_resources), + .resources = ldo14_resources, + }, +}; + +static struct mfd_cell touch_devs[] = { + {"88pm860x-touch", -1,}, +}; + +static struct mfd_cell onkey_devs[] = { + {"88pm860x-onkey", -1,}, +}; + +static struct mfd_cell codec_devs[] = { + {"88pm860x-codec", -1,}, +}; + +static struct regulator_consumer_supply preg_supply[] = { + REGULATOR_SUPPLY("preg", "charger-manager"), +}; + +static struct regulator_init_data preg_init_data = { + .num_consumer_supplies = ARRAY_SIZE(preg_supply), + .consumer_supplies = &preg_supply[0], +}; + +static struct charger_regulator chg_desc_regulator_data[] = { + { .regulator_name = "preg", }, +}; + +static struct mfd_cell power_devs[] = { + {"88pm860x-battery", -1,}, + {"88pm860x-charger", -1,}, + {"88pm860x-preg", -1,}, + {"charger-manager", -1,}, +}; + +static struct mfd_cell rtc_devs[] = { + {"88pm860x-rtc", -1,}, +}; + + +struct pm860x_irq_data { + int reg; + int mask_reg; + int enable; /* enable or not */ + int offs; /* bit offset in mask register */ +}; + +static struct pm860x_irq_data pm860x_irqs[] = { + [PM8607_IRQ_ONKEY] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 0, + }, + [PM8607_IRQ_EXTON] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 1, + }, + [PM8607_IRQ_CHG] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 2, + }, + [PM8607_IRQ_BAT] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 3, + }, + [PM8607_IRQ_RTC] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 4, + }, + [PM8607_IRQ_CC] = { + .reg = PM8607_INT_STATUS1, + .mask_reg = PM8607_INT_MASK_1, + .offs = 1 << 5, + }, + [PM8607_IRQ_VBAT] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 0, + }, + [PM8607_IRQ_VCHG] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 1, + }, + [PM8607_IRQ_VSYS] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 2, + }, + [PM8607_IRQ_TINT] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 3, + }, + [PM8607_IRQ_GPADC0] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 4, + }, + [PM8607_IRQ_GPADC1] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 5, + }, + [PM8607_IRQ_GPADC2] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 6, + }, + [PM8607_IRQ_GPADC3] = { + .reg = PM8607_INT_STATUS2, + .mask_reg = PM8607_INT_MASK_2, + .offs = 1 << 7, + }, + [PM8607_IRQ_AUDIO_SHORT] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 0, + }, + [PM8607_IRQ_PEN] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 1, + }, + [PM8607_IRQ_HEADSET] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 2, + }, + [PM8607_IRQ_HOOK] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 3, + }, + [PM8607_IRQ_MICIN] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 4, + }, + [PM8607_IRQ_CHG_FAIL] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 5, + }, + [PM8607_IRQ_CHG_DONE] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 6, + }, + [PM8607_IRQ_CHG_FAULT] = { + .reg = PM8607_INT_STATUS3, + .mask_reg = PM8607_INT_MASK_3, + .offs = 1 << 7, + }, +}; + +static irqreturn_t pm860x_irq(int irq, void *data) +{ + struct pm860x_chip *chip = data; + struct pm860x_irq_data *irq_data; + struct i2c_client *i2c; + int read_reg = -1, value = 0; + int i; + + i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { + irq_data = &pm860x_irqs[i]; + if (read_reg != irq_data->reg) { + read_reg = irq_data->reg; + value = pm860x_reg_read(i2c, irq_data->reg); + } + if (value & irq_data->enable) + handle_nested_irq(chip->irq_base + i); + } + return IRQ_HANDLED; +} + +static void pm860x_irq_lock(struct irq_data *data) +{ + struct pm860x_chip *chip = irq_data_get_irq_chip_data(data); + + mutex_lock(&chip->irq_lock); +} + +static void pm860x_irq_sync_unlock(struct irq_data *data) +{ + struct pm860x_chip *chip = irq_data_get_irq_chip_data(data); + struct pm860x_irq_data *irq_data; + struct i2c_client *i2c; + static unsigned char cached[3] = {0x0, 0x0, 0x0}; + unsigned char mask[3]; + int i; + + i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + /* Load cached value. In initial, all IRQs are masked */ + for (i = 0; i < 3; i++) + mask[i] = cached[i]; + for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { + irq_data = &pm860x_irqs[i]; + switch (irq_data->mask_reg) { + case PM8607_INT_MASK_1: + mask[0] &= ~irq_data->offs; + mask[0] |= irq_data->enable; + break; + case PM8607_INT_MASK_2: + mask[1] &= ~irq_data->offs; + mask[1] |= irq_data->enable; + break; + case PM8607_INT_MASK_3: + mask[2] &= ~irq_data->offs; + mask[2] |= irq_data->enable; + break; + default: + dev_err(chip->dev, "wrong IRQ\n"); + break; + } + } + /* update mask into registers */ + for (i = 0; i < 3; i++) { + if (mask[i] != cached[i]) { + cached[i] = mask[i]; + pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]); + } + } + + mutex_unlock(&chip->irq_lock); +} + +static void pm860x_irq_enable(struct irq_data *data) +{ + pm860x_irqs[data->hwirq].enable = pm860x_irqs[data->hwirq].offs; +} + +static void pm860x_irq_disable(struct irq_data *data) +{ + pm860x_irqs[data->hwirq].enable = 0; +} + +static struct irq_chip pm860x_irq_chip = { + .name = "88pm860x", + .irq_bus_lock = pm860x_irq_lock, + .irq_bus_sync_unlock = pm860x_irq_sync_unlock, + .irq_enable = pm860x_irq_enable, + .irq_disable = pm860x_irq_disable, +}; + +static int pm860x_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_data(virq, d->host_data); + irq_set_chip_and_handler(virq, &pm860x_irq_chip, handle_edge_irq); + irq_set_nested_thread(virq, 1); +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + return 0; +} + +static struct irq_domain_ops pm860x_irq_domain_ops = { + .map = pm860x_irq_domain_map, + .xlate = irq_domain_xlate_onetwocell, +}; + +static int device_irq_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? + chip->client : chip->companion; + unsigned char status_buf[INT_STATUS_NUM]; + unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + int data, mask, ret = -EINVAL; + int nr_irqs, irq_base = -1; + struct device_node *node = i2c->dev.of_node; + + mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR + | PM8607_B0_MISC1_INT_MASK; + data = 0; + chip->irq_mode = 0; + if (pdata && pdata->irq_mode) { + /* + * irq_mode defines the way of clearing interrupt. If it's 1, + * clear IRQ by write. Otherwise, clear it by read. + * This control bit is valid from 88PM8607 B0 steping. + */ + data |= PM8607_B0_MISC1_INT_CLEAR; + chip->irq_mode = 1; + } + ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data); + if (ret < 0) + goto out; + + /* mask all IRQs */ + memset(status_buf, 0, INT_STATUS_NUM); + ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1, + INT_STATUS_NUM, status_buf); + if (ret < 0) + goto out; + + if (chip->irq_mode) { + /* clear interrupt status by write */ + memset(status_buf, 0xFF, INT_STATUS_NUM); + ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, + INT_STATUS_NUM, status_buf); + } else { + /* clear interrupt status by read */ + ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, + INT_STATUS_NUM, status_buf); + } + if (ret < 0) + goto out; + + mutex_init(&chip->irq_lock); + + if (pdata && pdata->irq_base) + irq_base = pdata->irq_base; + nr_irqs = ARRAY_SIZE(pm860x_irqs); + chip->irq_base = irq_alloc_descs(irq_base, 0, nr_irqs, 0); + if (chip->irq_base < 0) { + dev_err(&i2c->dev, "Failed to allocate interrupts, ret:%d\n", + chip->irq_base); + ret = -EBUSY; + goto out; + } + irq_domain_add_legacy(node, nr_irqs, chip->irq_base, 0, + &pm860x_irq_domain_ops, chip); + chip->core_irq = i2c->irq; + if (!chip->core_irq) + goto out; + + ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, + flags | IRQF_ONESHOT, "88pm860x", chip); + if (ret) { + dev_err(chip->dev, "Failed to request IRQ: %d\n", ret); + chip->core_irq = 0; + } + + return 0; +out: + chip->core_irq = 0; + return ret; +} + +static void device_irq_exit(struct pm860x_chip *chip) +{ + if (chip->core_irq) + free_irq(chip->core_irq, chip); +} + +int pm8606_osc_enable(struct pm860x_chip *chip, unsigned short client) +{ + int ret = -EIO; + struct i2c_client *i2c = (chip->id == CHIP_PM8606) ? + chip->client : chip->companion; + + dev_dbg(chip->dev, "%s(B): client=0x%x\n", __func__, client); + dev_dbg(chip->dev, "%s(B): vote=0x%x status=%d\n", + __func__, chip->osc_vote, + chip->osc_status); + + mutex_lock(&chip->osc_lock); + /* Update voting status */ + chip->osc_vote |= client; + /* If reference group is off - turn on*/ + if (chip->osc_status != PM8606_REF_GP_OSC_ON) { + chip->osc_status = PM8606_REF_GP_OSC_UNKNOWN; + /* Enable Reference group Vsys */ + if (pm860x_set_bits(i2c, PM8606_VSYS, + PM8606_VSYS_EN, PM8606_VSYS_EN)) + goto out; + + /*Enable Internal Oscillator */ + if (pm860x_set_bits(i2c, PM8606_MISC, + PM8606_MISC_OSC_EN, PM8606_MISC_OSC_EN)) + goto out; + /* Update status (only if writes succeed) */ + chip->osc_status = PM8606_REF_GP_OSC_ON; + } + mutex_unlock(&chip->osc_lock); + + dev_dbg(chip->dev, "%s(A): vote=0x%x status=%d ret=%d\n", + __func__, chip->osc_vote, + chip->osc_status, ret); + return 0; +out: + mutex_unlock(&chip->osc_lock); + return ret; +} +EXPORT_SYMBOL(pm8606_osc_enable); + +int pm8606_osc_disable(struct pm860x_chip *chip, unsigned short client) +{ + int ret = -EIO; + struct i2c_client *i2c = (chip->id == CHIP_PM8606) ? + chip->client : chip->companion; + + dev_dbg(chip->dev, "%s(B): client=0x%x\n", __func__, client); + dev_dbg(chip->dev, "%s(B): vote=0x%x status=%d\n", + __func__, chip->osc_vote, + chip->osc_status); + + mutex_lock(&chip->osc_lock); + /*Update voting status */ + chip->osc_vote &= ~(client); + /* If reference group is off and this is the last client to release + * - turn off */ + if ((chip->osc_status != PM8606_REF_GP_OSC_OFF) && + (chip->osc_vote == REF_GP_NO_CLIENTS)) { + chip->osc_status = PM8606_REF_GP_OSC_UNKNOWN; + /* Disable Reference group Vsys */ + if (pm860x_set_bits(i2c, PM8606_VSYS, PM8606_VSYS_EN, 0)) + goto out; + /* Disable Internal Oscillator */ + if (pm860x_set_bits(i2c, PM8606_MISC, PM8606_MISC_OSC_EN, 0)) + goto out; + chip->osc_status = PM8606_REF_GP_OSC_OFF; + } + mutex_unlock(&chip->osc_lock); + + dev_dbg(chip->dev, "%s(A): vote=0x%x status=%d ret=%d\n", + __func__, chip->osc_vote, + chip->osc_status, ret); + return 0; +out: + mutex_unlock(&chip->osc_lock); + return ret; +} +EXPORT_SYMBOL(pm8606_osc_disable); + +static void device_osc_init(struct i2c_client *i2c) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + + mutex_init(&chip->osc_lock); + /* init portofino reference group voting and status */ + /* Disable Reference group Vsys */ + pm860x_set_bits(i2c, PM8606_VSYS, PM8606_VSYS_EN, 0); + /* Disable Internal Oscillator */ + pm860x_set_bits(i2c, PM8606_MISC, PM8606_MISC_OSC_EN, 0); + + chip->osc_vote = REF_GP_NO_CLIENTS; + chip->osc_status = PM8606_REF_GP_OSC_OFF; +} + +static void device_bk_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret, i; + + if (pdata && pdata->backlight) { + if (pdata->num_backlights > ARRAY_SIZE(bk_devs)) + pdata->num_backlights = ARRAY_SIZE(bk_devs); + for (i = 0; i < pdata->num_backlights; i++) { + bk_devs[i].platform_data = &pdata->backlight[i]; + bk_devs[i].pdata_size = + sizeof(struct pm860x_backlight_pdata); + } + } + ret = mfd_add_devices(chip->dev, 0, bk_devs, + ARRAY_SIZE(bk_devs), NULL, 0, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add backlight subdev\n"); +} + +static void device_led_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret, i; + + if (pdata && pdata->led) { + if (pdata->num_leds > ARRAY_SIZE(led_devs)) + pdata->num_leds = ARRAY_SIZE(led_devs); + for (i = 0; i < pdata->num_leds; i++) { + led_devs[i].platform_data = &pdata->led[i]; + led_devs[i].pdata_size = + sizeof(struct pm860x_led_pdata); + } + } + ret = mfd_add_devices(chip->dev, 0, led_devs, + ARRAY_SIZE(led_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add led subdev\n"); + return; + } +} + +static void device_regulator_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + if (pdata == NULL) + return; + if (pdata->buck1) { + reg_devs[0].platform_data = pdata->buck1; + reg_devs[0].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->buck2) { + reg_devs[1].platform_data = pdata->buck2; + reg_devs[1].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->buck3) { + reg_devs[2].platform_data = pdata->buck3; + reg_devs[2].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo1) { + reg_devs[3].platform_data = pdata->ldo1; + reg_devs[3].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo2) { + reg_devs[4].platform_data = pdata->ldo2; + reg_devs[4].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo3) { + reg_devs[5].platform_data = pdata->ldo3; + reg_devs[5].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo4) { + reg_devs[6].platform_data = pdata->ldo4; + reg_devs[6].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo5) { + reg_devs[7].platform_data = pdata->ldo5; + reg_devs[7].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo6) { + reg_devs[8].platform_data = pdata->ldo6; + reg_devs[8].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo7) { + reg_devs[9].platform_data = pdata->ldo7; + reg_devs[9].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo8) { + reg_devs[10].platform_data = pdata->ldo8; + reg_devs[10].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo9) { + reg_devs[11].platform_data = pdata->ldo9; + reg_devs[11].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo10) { + reg_devs[12].platform_data = pdata->ldo10; + reg_devs[12].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo12) { + reg_devs[13].platform_data = pdata->ldo12; + reg_devs[13].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo_vibrator) { + reg_devs[14].platform_data = pdata->ldo_vibrator; + reg_devs[14].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo14) { + reg_devs[15].platform_data = pdata->ldo14; + reg_devs[15].pdata_size = sizeof(struct regulator_init_data); + } + ret = mfd_add_devices(chip->dev, 0, reg_devs, + ARRAY_SIZE(reg_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + return; + } +} + +static void device_rtc_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + if (!pdata) + return; + + rtc_devs[0].platform_data = pdata->rtc; + rtc_devs[0].pdata_size = sizeof(struct pm860x_rtc_pdata); + rtc_devs[0].num_resources = ARRAY_SIZE(rtc_resources); + rtc_devs[0].resources = &rtc_resources[0]; + ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0], + ARRAY_SIZE(rtc_devs), &rtc_resources[0], + chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add rtc subdev\n"); +} + +static void device_touch_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + if (pdata == NULL) + return; + + touch_devs[0].platform_data = pdata->touch; + touch_devs[0].pdata_size = sizeof(struct pm860x_touch_pdata); + touch_devs[0].num_resources = ARRAY_SIZE(touch_resources); + touch_devs[0].resources = &touch_resources[0]; + ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], + ARRAY_SIZE(touch_devs), &touch_resources[0], + chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add touch subdev\n"); +} + +static void device_power_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + if (pdata == NULL) + return; + + power_devs[0].platform_data = pdata->power; + power_devs[0].pdata_size = sizeof(struct pm860x_power_pdata); + power_devs[0].num_resources = ARRAY_SIZE(battery_resources); + power_devs[0].resources = &battery_resources[0], + ret = mfd_add_devices(chip->dev, 0, &power_devs[0], 1, + &battery_resources[0], chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add battery subdev\n"); + + power_devs[1].platform_data = pdata->power; + power_devs[1].pdata_size = sizeof(struct pm860x_power_pdata); + power_devs[1].num_resources = ARRAY_SIZE(charger_resources); + power_devs[1].resources = &charger_resources[0], + ret = mfd_add_devices(chip->dev, 0, &power_devs[1], 1, + &charger_resources[0], chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add charger subdev\n"); + + power_devs[2].platform_data = &preg_init_data; + power_devs[2].pdata_size = sizeof(struct regulator_init_data); + ret = mfd_add_devices(chip->dev, 0, &power_devs[2], 1, + NULL, chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add preg subdev\n"); + + if (pdata->chg_desc) { + pdata->chg_desc->charger_regulators = + &chg_desc_regulator_data[0]; + pdata->chg_desc->num_charger_regulators = + ARRAY_SIZE(chg_desc_regulator_data), + power_devs[3].platform_data = pdata->chg_desc; + power_devs[3].pdata_size = sizeof(*pdata->chg_desc); + ret = mfd_add_devices(chip->dev, 0, &power_devs[3], 1, + NULL, chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add chg-manager subdev\n"); + } +} + +static void device_onkey_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + onkey_devs[0].num_resources = ARRAY_SIZE(onkey_resources); + onkey_devs[0].resources = &onkey_resources[0], + ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], + ARRAY_SIZE(onkey_devs), &onkey_resources[0], + chip->irq_base, NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add onkey subdev\n"); +} + +static void device_codec_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + codec_devs[0].num_resources = ARRAY_SIZE(codec_resources); + codec_devs[0].resources = &codec_resources[0], + ret = mfd_add_devices(chip->dev, 0, &codec_devs[0], + ARRAY_SIZE(codec_devs), &codec_resources[0], 0, + NULL); + if (ret < 0) + dev_err(chip->dev, "Failed to add codec subdev\n"); +} + +static void device_8607_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ + int data, ret; + + ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); + if (ret < 0) { + dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); + goto out; + } + switch (ret & PM8607_VERSION_MASK) { + case 0x40: + case 0x50: + dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", + ret); + break; + default: + dev_err(chip->dev, + "Failed to detect Marvell 88PM8607. Chip ID: %02x\n", + ret); + goto out; + } + + ret = pm860x_reg_read(i2c, PM8607_BUCK3); + if (ret < 0) { + dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); + goto out; + } + if (ret & PM8607_BUCK3_DOUBLE) + chip->buck3_double = 1; + + ret = pm860x_reg_read(i2c, PM8607_B0_MISC1); + if (ret < 0) { + dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); + goto out; + } + + if (pdata && (pdata->i2c_port == PI2C_PORT)) + data = PM8607_B0_MISC1_PI2C; + else + data = 0; + ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data); + if (ret < 0) { + dev_err(chip->dev, "Failed to access MISC1:%d\n", ret); + goto out; + } + + ret = device_irq_init(chip, pdata); + if (ret < 0) + goto out; + + device_regulator_init(chip, pdata); + device_rtc_init(chip, pdata); + device_onkey_init(chip, pdata); + device_touch_init(chip, pdata); + device_power_init(chip, pdata); + device_codec_init(chip, pdata); +out: + return; +} + +static void device_8606_init(struct pm860x_chip *chip, + struct i2c_client *i2c, + struct pm860x_platform_data *pdata) +{ + device_osc_init(i2c); + device_bk_init(chip, pdata); + device_led_init(chip, pdata); +} + +static int pm860x_device_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + chip->core_irq = 0; + + switch (chip->id) { + case CHIP_PM8606: + device_8606_init(chip, chip->client, pdata); + break; + case CHIP_PM8607: + device_8607_init(chip, chip->client, pdata); + break; + } + + if (chip->companion) { + switch (chip->id) { + case CHIP_PM8607: + device_8606_init(chip, chip->companion, pdata); + break; + case CHIP_PM8606: + device_8607_init(chip, chip->companion, pdata); + break; + } + } + + return 0; +} + +static void pm860x_device_exit(struct pm860x_chip *chip) +{ + device_irq_exit(chip); + mfd_remove_devices(chip->dev); +} + +static int verify_addr(struct i2c_client *i2c) +{ + unsigned short addr_8607[] = {0x30, 0x34}; + unsigned short addr_8606[] = {0x10, 0x11}; + int size, i; + + if (i2c == NULL) + return 0; + size = ARRAY_SIZE(addr_8606); + for (i = 0; i < size; i++) { + if (i2c->addr == *(addr_8606 + i)) + return CHIP_PM8606; + } + size = ARRAY_SIZE(addr_8607); + for (i = 0; i < size; i++) { + if (i2c->addr == *(addr_8607 + i)) + return CHIP_PM8607; + } + return 0; +} + +static const struct regmap_config pm860x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int pm860x_dt_init(struct device_node *np, + struct device *dev, + struct pm860x_platform_data *pdata) +{ + int ret; + + if (of_get_property(np, "marvell,88pm860x-irq-read-clr", NULL)) + pdata->irq_mode = 1; + ret = of_property_read_u32(np, "marvell,88pm860x-slave-addr", + &pdata->companion_addr); + if (ret) { + dev_err(dev, + "Not found \"marvell,88pm860x-slave-addr\" property\n"); + pdata->companion_addr = 0; + } + return 0; +} + +static int pm860x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pm860x_platform_data *pdata = dev_get_platdata(&client->dev); + struct device_node *node = client->dev.of_node; + struct pm860x_chip *chip; + int ret; + + if (node && !pdata) { + /* parse DT to get platform data */ + pdata = devm_kzalloc(&client->dev, + sizeof(struct pm860x_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + ret = pm860x_dt_init(node, &client->dev, pdata); + if (ret) + return ret; + } else if (!pdata) { + pr_info("No platform data in %s!\n", __func__); + return -EINVAL; + } + + chip = devm_kzalloc(&client->dev, + sizeof(struct pm860x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->id = verify_addr(client); + chip->regmap = devm_regmap_init_i2c(client, &pm860x_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + chip->client = client; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + dev_set_drvdata(chip->dev, chip); + + /* + * Both client and companion client shares same platform driver. + * Driver distinguishes them by pdata->companion_addr. + * pdata->companion_addr is only assigned if companion chip exists. + * At the same time, the companion_addr shouldn't equal to client + * address. + */ + if (pdata->companion_addr && (pdata->companion_addr != client->addr)) { + chip->companion_addr = pdata->companion_addr; + chip->companion = i2c_new_dummy(chip->client->adapter, + chip->companion_addr); + if (!chip->companion) { + dev_err(&client->dev, + "Failed to allocate I2C companion device\n"); + return -ENODEV; + } + chip->regmap_companion = regmap_init_i2c(chip->companion, + &pm860x_regmap_config); + if (IS_ERR(chip->regmap_companion)) { + ret = PTR_ERR(chip->regmap_companion); + dev_err(&chip->companion->dev, + "Failed to allocate register map: %d\n", ret); + i2c_unregister_device(chip->companion); + return ret; + } + i2c_set_clientdata(chip->companion, chip); + } + + pm860x_device_init(chip, pdata); + return 0; +} + +static int pm860x_remove(struct i2c_client *client) +{ + struct pm860x_chip *chip = i2c_get_clientdata(client); + + pm860x_device_exit(chip); + if (chip->companion) { + regmap_exit(chip->regmap_companion); + i2c_unregister_device(chip->companion); + } + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pm860x_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct pm860x_chip *chip = i2c_get_clientdata(client); + + if (device_may_wakeup(dev) && chip->wakeup_flag) + enable_irq_wake(chip->core_irq); + return 0; +} + +static int pm860x_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct pm860x_chip *chip = i2c_get_clientdata(client); + + if (device_may_wakeup(dev) && chip->wakeup_flag) + disable_irq_wake(chip->core_irq); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm860x_pm_ops, pm860x_suspend, pm860x_resume); + +static const struct i2c_device_id pm860x_id_table[] = { + { "88PM860x", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pm860x_id_table); + +static const struct of_device_id pm860x_dt_ids[] = { + { .compatible = "marvell,88pm860x", }, + {}, +}; +MODULE_DEVICE_TABLE(of, pm860x_dt_ids); + +static struct i2c_driver pm860x_driver = { + .driver = { + .name = "88PM860x", + .owner = THIS_MODULE, + .pm = &pm860x_pm_ops, + .of_match_table = pm860x_dt_ids, + }, + .probe = pm860x_probe, + .remove = pm860x_remove, + .id_table = pm860x_id_table, +}; + +static int __init pm860x_i2c_init(void) +{ + int ret; + ret = i2c_add_driver(&pm860x_driver); + if (ret != 0) + pr_err("Failed to register 88PM860x I2C driver: %d\n", ret); + return ret; +} +subsys_initcall(pm860x_i2c_init); + +static void __exit pm860x_i2c_exit(void) +{ + i2c_del_driver(&pm860x_driver); +} +module_exit(pm860x_i2c_exit); + +MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/88pm860x-i2c.c b/kernel/drivers/mfd/88pm860x-i2c.c new file mode 100644 index 000000000..84e313107 --- /dev/null +++ b/kernel/drivers/mfd/88pm860x-i2c.c @@ -0,0 +1,177 @@ +/* + * I2C driver for Marvell 88PM860x + * + * Copyright (C) 2009 Marvell International Ltd. + * + * Author: Haojian Zhuang <haojian.zhuang@marvell.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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/mfd/88pm860x.h> + +int pm860x_reg_read(struct i2c_client *i2c, int reg) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + struct regmap *map = (i2c == chip->client) ? chip->regmap + : chip->regmap_companion; + unsigned int data; + int ret; + + ret = regmap_read(map, reg, &data); + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(pm860x_reg_read); + +int pm860x_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + struct regmap *map = (i2c == chip->client) ? chip->regmap + : chip->regmap_companion; + int ret; + + ret = regmap_write(map, reg, data); + return ret; +} +EXPORT_SYMBOL(pm860x_reg_write); + +int pm860x_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + struct regmap *map = (i2c == chip->client) ? chip->regmap + : chip->regmap_companion; + int ret; + + ret = regmap_raw_read(map, reg, buf, count); + return ret; +} +EXPORT_SYMBOL(pm860x_bulk_read); + +int pm860x_bulk_write(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + struct regmap *map = (i2c == chip->client) ? chip->regmap + : chip->regmap_companion; + int ret; + + ret = regmap_raw_write(map, reg, buf, count); + return ret; +} +EXPORT_SYMBOL(pm860x_bulk_write); + +int pm860x_set_bits(struct i2c_client *i2c, int reg, + unsigned char mask, unsigned char data) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + struct regmap *map = (i2c == chip->client) ? chip->regmap + : chip->regmap_companion; + int ret; + + ret = regmap_update_bits(map, reg, mask, data); + return ret; +} +EXPORT_SYMBOL(pm860x_set_bits); + +static int read_device(struct i2c_client *i2c, int reg, + int bytes, void *dest) +{ + unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX + 3]; + unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX + 2]; + struct i2c_adapter *adap = i2c->adapter; + struct i2c_msg msg[2] = { + { + .addr = i2c->addr, + .flags = 0, + .len = 1, + .buf = msgbuf0 + }, + { .addr = i2c->addr, + .flags = I2C_M_RD, + .len = 0, + .buf = msgbuf1 + }, + }; + int num = 1, ret = 0; + + if (dest == NULL) + return -EINVAL; + msgbuf0[0] = (unsigned char)reg; /* command */ + msg[1].len = bytes; + + /* if data needs to read back, num should be 2 */ + if (bytes > 0) + num = 2; + ret = adap->algo->master_xfer(adap, msg, num); + memcpy(dest, msgbuf1, bytes); + if (ret < 0) + return ret; + return 0; +} + +static int write_device(struct i2c_client *i2c, int reg, + int bytes, void *src) +{ + unsigned char buf[2]; + struct i2c_adapter *adap = i2c->adapter; + struct i2c_msg msg; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + msg.addr = i2c->addr; + msg.flags = 0; + msg.len = bytes + 1; + msg.buf = buf; + + ret = adap->algo->master_xfer(adap, &msg, 1); + if (ret < 0) + return ret; + return 0; +} + +int pm860x_page_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + unsigned char zero; + int ret; + + i2c_lock_adapter(i2c->adapter); + read_device(i2c, 0xFA, 0, &zero); + read_device(i2c, 0xFB, 0, &zero); + read_device(i2c, 0xFF, 0, &zero); + ret = write_device(i2c, reg, 1, &data); + read_device(i2c, 0xFE, 0, &zero); + read_device(i2c, 0xFC, 0, &zero); + i2c_unlock_adapter(i2c->adapter); + return ret; +} +EXPORT_SYMBOL(pm860x_page_reg_write); + +int pm860x_page_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + unsigned char zero = 0; + int ret; + + i2c_lock_adapter(i2c->adapter); + read_device(i2c, 0xfa, 0, &zero); + read_device(i2c, 0xfb, 0, &zero); + read_device(i2c, 0xff, 0, &zero); + ret = read_device(i2c, reg, count, buf); + read_device(i2c, 0xFE, 0, &zero); + read_device(i2c, 0xFC, 0, &zero); + i2c_unlock_adapter(i2c->adapter); + return ret; +} +EXPORT_SYMBOL(pm860x_page_bulk_read); diff --git a/kernel/drivers/mfd/Kconfig b/kernel/drivers/mfd/Kconfig new file mode 100644 index 000000000..d5ad04dad --- /dev/null +++ b/kernel/drivers/mfd/Kconfig @@ -0,0 +1,1470 @@ +# +# Multifunction miscellaneous devices +# + +if HAS_IOMEM +menu "Multifunction device drivers" + +config MFD_CORE + tristate + select IRQ_DOMAIN + default n + +config MFD_CS5535 + tristate "AMD CS5535 and CS5536 southbridge core functions" + select MFD_CORE + depends on PCI && (X86_32 || (X86 && COMPILE_TEST)) + ---help--- + This is the core driver for CS5535/CS5536 MFD functions. This is + necessary for using the board's GPIO and MFGPT functionality. + +config MFD_AS3711 + bool "AMS AS3711" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + depends on I2C=y + help + Support for the AS3711 PMIC from AMS + +config MFD_AS3722 + bool "ams AS3722 Power Management IC" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + depends on I2C=y && OF + help + The ams AS3722 is a compact system PMU suitable for mobile phones, + tablets etc. It has 4 DC/DC step-down regulators, 3 DC/DC step-down + controllers, 11 LDOs, RTC, automatic battery, temperature and + over current monitoring, GPIOs, ADC and a watchdog. + +config PMIC_ADP5520 + bool "Analog Devices ADP5520/01 MFD PMIC Core Support" + depends on I2C=y + help + Say yes here to add support for Analog Devices AD5520 and ADP5501, + Multifunction Power Management IC. This includes + the I2C driver and the core APIs _only_, you have to select + individual components like LCD backlight, LEDs, GPIOs and Kepad + under the corresponding menus. + +config MFD_AAT2870_CORE + bool "AnalogicTech AAT2870" + select MFD_CORE + depends on I2C=y && GPIOLIB + help + 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. + +config MFD_ATMEL_HLCDC + tristate "Atmel HLCDC (High-end LCD Controller)" + select MFD_CORE + select REGMAP_MMIO + depends on OF + help + If you say yes here you get support for the HLCDC block. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config MFD_BCM590XX + tristate "Broadcom BCM590xx PMUs" + select MFD_CORE + select REGMAP_I2C + depends on I2C + help + Support for the BCM590xx PMUs from Broadcom + +config MFD_AXP20X + bool "X-Powers AXP20X" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + depends on I2C=y + help + If you say Y here you get support for the X-Powers AXP202, AXP209 and + AXP288 power management IC (PMIC). + This driver include only the core APIs. You have to select individual + components like regulators or the PEK (Power Enable Key) under the + corresponding menus. + +config MFD_CROS_EC + tristate "ChromeOS Embedded Controller" + select MFD_CORE + help + If you say Y here you get support for the ChromeOS Embedded + Controller (EC) providing keyboard, battery and power services. + You also need to enable the driver for the bus you are using. The + protocol for talking to the EC is defined by the bus driver. + +config MFD_CROS_EC_I2C + tristate "ChromeOS Embedded Controller (I2C)" + depends on MFD_CROS_EC && I2C + + help + If you say Y here, you get support for talking to the ChromeOS + EC through an I2C bus. This uses a simple byte-level protocol with + a checksum. Failing accesses will be retried three times to + improve reliability. + +config MFD_CROS_EC_SPI + tristate "ChromeOS Embedded Controller (SPI)" + depends on MFD_CROS_EC && SPI && OF + + ---help--- + If you say Y here, you get support for talking to the ChromeOS EC + through a SPI bus, using a byte-level protocol. Since the EC's + response time cannot be guaranteed, we support ignoring + 'pre-amble' bytes before the response actually starts. + +config MFD_ASIC3 + bool "Compaq ASIC3" + depends on GPIOLIB && ARM + select MFD_CORE + ---help--- + This driver supports the ASIC3 multifunction chip found on many + PDAs (mainly iPAQ and HTC based ones) + +config PMIC_DA903X + bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" + depends on I2C=y + help + Say yes here to add support for Dialog Semiconductor DA9030 (a.k.a + ARAVA) and DA9034 (a.k.a MICCO), these are Power Management IC + usually found on PXA processors-based platforms. This includes + the I2C driver and the core APIs _only_, you have to select + individual components like LCD backlight, voltage regulators, + LEDs and battery-charger under the corresponding menus. + +config PMIC_DA9052 + bool + select MFD_CORE + +config MFD_DA9052_SPI + bool "Dialog Semiconductor DA9052/53 PMIC variants with SPI" + select REGMAP_SPI + select REGMAP_IRQ + select PMIC_DA9052 + depends on SPI_MASTER=y + help + Support for the Dialog Semiconductor DA9052 PMIC + when controlled using SPI. This driver provides common support + for accessing the device, additional drivers must be enabled in + order to use the functionality of the device. + +config MFD_DA9052_I2C + bool "Dialog Semiconductor DA9052/53 PMIC variants with I2C" + select REGMAP_I2C + select REGMAP_IRQ + select PMIC_DA9052 + depends on I2C=y + help + Support for the Dialog Semiconductor DA9052 PMIC + when controlled using I2C. This driver provides common support + for accessing the device, additional drivers must be enabled in + order to use the functionality of the device. + +config MFD_DA9055 + bool "Dialog Semiconductor DA9055 PMIC Support" + select REGMAP_I2C + select REGMAP_IRQ + select MFD_CORE + depends on I2C=y + help + Say yes here for support of Dialog Semiconductor DA9055. This is + a Power Management IC. This driver provides common support for + accessing the device as well as the I2C interface to the chip itself. + Additional drivers must be enabled in order to use the functionality + of the device. + + This driver can be built as a module. If built as a module it will be + called "da9055" + +config MFD_DA9063 + bool "Dialog Semiconductor DA9063 PMIC Support" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + depends on I2C=y + help + Say yes here for support for the Dialog Semiconductor DA9063 PMIC. + This includes the I2C driver and core APIs. + Additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_DA9150 + tristate "Dialog Semiconductor DA9150 Charger Fuel-Gauge chip" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + This adds support for the DA9150 integrated charger and fuel-gauge + chip. This driver provides common support for accessing the device. + Additional drivers must be enabled in order to use the specific + features of the device. + +config MFD_DLN2 + tristate "Diolan DLN2 support" + select MFD_CORE + depends on USB + help + This adds support for Diolan USB-I2C/SPI/GPIO Master Adapter + DLN-2. Additional drivers such as I2C_DLN2, GPIO_DLN2, + etc. must be enabled in order to use the functionality of + the device. + +config MFD_MC13XXX + tristate + depends on (SPI_MASTER || I2C) + select MFD_CORE + select REGMAP_IRQ + help + Enable support for the Freescale MC13783 and MC13892 PMICs. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config MFD_MC13XXX_SPI + tristate "Freescale MC13783 and MC13892 SPI interface" + depends on SPI_MASTER + select REGMAP_SPI + select MFD_MC13XXX + help + Select this if your MC13xxx is connected via an SPI bus. + +config MFD_MC13XXX_I2C + tristate "Freescale MC13892 I2C interface" + depends on I2C + select REGMAP_I2C + select MFD_MC13XXX + help + Select this if your MC13xxx is connected via an I2C bus. + +config MFD_HI6421_PMIC + tristate "HiSilicon Hi6421 PMU/Codec IC" + depends on OF + select MFD_CORE + select REGMAP_MMIO + help + Add support for HiSilicon Hi6421 PMIC. Hi6421 includes multi- + functions, such as regulators, RTC, codec, Coulomb counter, etc. + This driver includes core APIs _only_. You have to select + individul components like voltage regulators under corresponding + menus in order to enable them. + We communicate with the Hi6421 via memory-mapped I/O. + +config HTC_EGPIO + bool "HTC EGPIO support" + depends on GPIOLIB && ARM + help + This driver supports the CPLD egpio chip present on + several HTC phones. It provides basic support for input + pins, output pins, and irqs. + +config HTC_PASIC3 + tristate "HTC PASIC3 LED/DS1WM chip support" + select MFD_CORE + help + This core driver provides register access for the LED/DS1WM + chips labeled "AIC2" and "AIC3", found on HTC Blueangel and + HTC Magician devices, respectively. Actual functionality is + handled by the leds-pasic3 and ds1wm drivers. + +config HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C=y && GPIOLIB + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + +config MFD_INTEL_QUARK_I2C_GPIO + tristate "Intel Quark MFD I2C GPIO" + depends on PCI + depends on X86 + depends on COMMON_CLK + select MFD_CORE + help + This MFD provides support for I2C and GPIO that exist only + in a single PCI device. It splits the 2 IO devices to + their respective IO driver. + The GPIO exports a total amount of 8 interrupt-capable GPIOs. + +config LPC_ICH + tristate "Intel ICH LPC" + depends on PCI + select MFD_CORE + help + The LPC bridge function of the Intel ICH provides support for + many functional units. This driver provides needed support for + other drivers to control these functions, currently GPIO and + watchdog. + +config LPC_SCH + tristate "Intel SCH LPC" + depends on PCI + select MFD_CORE + help + LPC bridge function of the Intel SCH provides support for + System Management Bus and General Purpose I/O. + +config INTEL_SOC_PMIC + bool "Support for Intel Atom SoC PMIC" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Select this option to enable support for the PMIC device + on some Intel SoC systems. The PMIC provides ADC, GPIO, + thermal, charger and related power management functions + on these systems. + +config MFD_INTEL_MSIC + bool "Intel MSIC" + depends on INTEL_SCU_IPC + select MFD_CORE + help + Select this option to enable access to Intel MSIC (Avatele + Passage) chip. This chip embeds audio, battery, GPIO, etc. + devices used in Intel Medfield platforms. + +config MFD_IPAQ_MICRO + bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support" + depends on SA1100_H3100 || SA1100_H3600 + select MFD_CORE + help + Select this to get support for the Microcontroller found in + the Compaq iPAQ handheld computers. This is an Atmel + AT90LS8535 microcontroller flashed with a special iPAQ + firmware using the custom protocol implemented in this driver. + +config MFD_JANZ_CMODIO + tristate "Janz CMOD-IO PCI MODULbus Carrier Board" + select MFD_CORE + depends on PCI + help + This is the core driver for the Janz CMOD-IO PCI MODULbus + carrier board. This device is a PCI to MODULbus bridge which may + host many different types of MODULbus daughterboards, including + CAN and GPIO controllers. + +config MFD_JZ4740_ADC + bool "Janz JZ4740 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. + This driver is necessary for jz4740-battery and jz4740-hwmon driver. + +config MFD_KEMPLD + tristate "Kontron module PLD device" + select MFD_CORE + help + This is the core driver for the PLD (Programmable Logic Device) found + on some Kontron ETX and COMexpress (ETXexpress) modules. The PLD + device may provide functions like watchdog, GPIO, UART and I2C bus. + + The following modules are supported: + * COMe-bHL6 + * COMe-bIP# + * COMe-bPC2 (ETXexpress-PC) + * COMe-bSC# (ETXexpress-SC T#) + * COMe-cBL6 + * COMe-cBT6 + * COMe-cCT6 + * COMe-cDC2 (microETXexpress-DC) + * COMe-cHL6 + * COMe-cPC2 (microETXexpress-PC) + * COMe-mBT10 + * COMe-mCT10 + * COMe-mTT10 (nanoETXexpress-TT) + * ETX-OH + + This driver can also be built as a module. If so, the module + will be called kempld-core. + +config MFD_88PM800 + tristate "Marvell 88PM800" + depends on I2C=y + select REGMAP_I2C + select REGMAP_IRQ + select MFD_CORE + help + This supports for Marvell 88PM800 Power Management IC. + This includes the I2C driver and the core APIs _only_, you have to + select individual components like voltage regulators, RTC and + battery-charger under the corresponding menus. + +config MFD_88PM805 + tristate "Marvell 88PM805" + depends on I2C=y + select REGMAP_I2C + select REGMAP_IRQ + select MFD_CORE + help + This supports for Marvell 88PM805 Power Management IC. This includes + the I2C driver and the core APIs _only_, you have to select individual + components like codec device, headset/Mic device under the + corresponding menus. + +config MFD_88PM860X + bool "Marvell 88PM8606/88PM8607" + depends on I2C=y + select REGMAP_I2C + select MFD_CORE + help + This supports for Marvell 88PM8606/88PM8607 Power Management IC. + This includes the I2C driver and the core APIs _only_, you have to + select individual components like voltage regulators, RTC and + battery-charger under the corresponding menus. + +config MFD_MAX14577 + bool "Maxim Semiconductor MAX14577/77836 MUIC + Charger Support" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + select IRQ_DOMAIN + help + Say yes here to add support for Maxim Semiconductor MAX14577 and + MAX77836 Micro-USB ICs with battery charger. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_MAX77686 + bool "Maxim Semiconductor MAX77686/802 PMIC Support" + depends on I2C=y + depends on OF + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + select IRQ_DOMAIN + help + Say yes here to add support for Maxim Semiconductor MAX77686 and + MAX77802 which are Power Management IC with an RTC on chip. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_MAX77693 + bool "Maxim Semiconductor MAX77693 PMIC Support" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to add support for Maxim Semiconductor MAX77693. + This is a companion Power Management IC with Flash, Haptic, Charger, + and MUIC(Micro USB Interface Controller) controls on chip. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_MAX77843 + bool "Maxim Semiconductor MAX77843 PMIC Support" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to add support for Maxim Semiconductor MAX77843. + This is companion Power Management IC with LEDs, Haptic, Charger, + Fuel Gauge, MUIC(Micro USB Interface Controller) controls on chip. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_MAX8907 + tristate "Maxim Semiconductor MAX8907 PMIC Support" + select MFD_CORE + depends on I2C=y + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to add support for Maxim Semiconductor MAX8907. This is + a Power Management IC. This driver provides common support for + accessing the device; additional drivers must be enabled in order + to use the functionality of the device. + +config MFD_MAX8925 + bool "Maxim Semiconductor MAX8925 PMIC Support" + depends on I2C=y + select MFD_CORE + help + Say yes here to add support for Maxim Semiconductor MAX8925. This is + a Power Management IC. This driver provides common support for + accessing the device, additional drivers must be enabled in order + to use the functionality of the device. + +config MFD_MAX8997 + bool "Maxim Semiconductor MAX8997/8966 PMIC Support" + depends on I2C=y + select MFD_CORE + select IRQ_DOMAIN + help + Say yes here to add support for Maxim Semiconductor MAX8997/8966. + This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic, + MUIC controls on chip. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_MAX8998 + bool "Maxim Semiconductor MAX8998/National LP3974 PMIC Support" + depends on I2C=y + select MFD_CORE + select IRQ_DOMAIN + help + Say yes here to add support for Maxim Semiconductor MAX8998 and + National Semiconductor LP3974. This is a Power Management IC. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. + +config MFD_MT6397 + tristate "MediaTek MT6397 PMIC Support" + select MFD_CORE + select IRQ_DOMAIN + help + Say yes here to add support for MediaTek MT6397 PMIC. This is + a Power Management IC. This driver provides common support for + accessing the device; additional drivers must be enabled in order + to use the functionality of the device. + +config MFD_MENF21BMC + tristate "MEN 14F021P00 Board Management Controller Support" + depends on I2C + select MFD_CORE + help + Say yes here to add support for the MEN 14F021P00 BMC + which is a Board Management Controller connected to the I2C bus. + The device supports multiple sub-devices like LED, HWMON and WDT. + This driver provides common support for accessing the devices; + additional drivers must be enabled in order to use the + functionality of the BMC device. + + This driver can also be built as a module. If so the module + will be called menf21bmc. + +config EZX_PCAP + bool "Motorola EZXPCAP Support" + depends on SPI_MASTER + help + This enables the PCAP ASIC present on EZX Phones. This is + needed for MMC, TouchScreen, Sound, USB, etc.. + +config MFD_VIPERBOARD + tristate "Nano River Technologies Viperboard" + select MFD_CORE + depends on USB + default n + help + Say yes here if you want support for Nano River Technologies + Viperboard. + There are mfd cell drivers available for i2c master, adc and + both gpios found on the board. The spi part does not yet + have a driver. + You need to select the mfd cell drivers separately. + The drivers do not support all features the board exposes. + +config MFD_RETU + tristate "Nokia Retu and Tahvo multi-function device" + select MFD_CORE + depends on I2C + select REGMAP_IRQ + help + Retu and Tahvo are a multi-function devices found on Nokia + Internet Tablets (770, N800 and N810). + +config MFD_PCF50633 + tristate "NXP PCF50633" + depends on I2C + select REGMAP_I2C + help + Say yes here if you have NXP PCF50633 chip on your board. + This core driver provides register access and IRQ handling + facilities, and registers devices for the various functions + so that function-specific drivers can bind to them. + +config PCF50633_ADC + tristate "NXP PCF50633 ADC" + depends on MFD_PCF50633 + help + Say yes here if you want to include support for ADC in the + NXP PCF50633 chip. + +config PCF50633_GPIO + tristate "NXP PCF50633 GPIO" + depends on MFD_PCF50633 + help + Say yes here if you want to include support GPIO for pins on + the PCF50633 chip. + +config UCB1400_CORE + tristate "Philips UCB1400 Core driver" + depends on AC97_BUS + depends on GPIOLIB + help + This enables support for the Philips UCB1400 core functions. + The UCB1400 is an AC97 audio codec. + + To compile this driver as a module, choose M here: the + module will be called ucb1400_core. + +config MFD_PM8XXX + tristate + +config MFD_PM8921_CORE + tristate "Qualcomm PM8921 PMIC chip" + depends on (ARM || HEXAGON) + select IRQ_DOMAIN + select MFD_CORE + select MFD_PM8XXX + select REGMAP + help + If you say yes to this option, support will be included for the + built-in PM8921 PMIC chip. + + This is required if your board has a PM8921 and uses its features, + such as: MPPs, GPIOs, regulators, interrupts, and PWM. + + Say M here if you want to include support for PM8921 chip as a module. + This will build a module called "pm8921-core". + +config MFD_QCOM_RPM + tristate "Qualcomm Resource Power Manager (RPM)" + depends on ARCH_QCOM && OF + help + If you say yes to this option, support will be included for the + Resource Power Manager system found in the Qualcomm 8660, 8960 and + 8064 based devices. + + This is required to access many regulators, clocks and bus + frequencies controlled by the RPM on these devices. + + Say M here if you want to include support for the Qualcomm RPM as a + module. This will build a module called "qcom_rpm". + +config MFD_SPMI_PMIC + tristate "Qualcomm SPMI PMICs" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + depends on SPMI + select REGMAP_SPMI + help + This enables support for the Qualcomm SPMI PMICs. + These PMICs are currently used with the Snapdragon 800 series of + SoCs. Note, that this will only be useful paired with descriptions + of the independent functions as children nodes in the device tree. + + Say M here if you want to include support for the SPMI PMIC + series as a module. The module will be called "qcom-spmi-pmic". + +config MFD_RDC321X + tristate "RDC R-321x southbridge" + select MFD_CORE + depends on PCI + help + Say yes here if you want to have support for the RDC R-321x SoC + southbridge which provides access to GPIOs and Watchdog using the + southbridge PCI device configuration space. + +config MFD_RTSX_PCI + tristate "Realtek PCI-E card reader" + depends on PCI + select MFD_CORE + help + This supports for Realtek PCI-Express card reader including rts5209, + rts5229, rtl8411, etc. Realtek card reader supports access to many + types of memory cards, such as Memory Stick, Memory Stick Pro, + Secure Digital and MultiMediaCard. + +config MFD_RT5033 + tristate "Richtek RT5033 Power Management IC" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + This driver provides for the Richtek RT5033 Power Management IC, + which includes the I2C driver and the Core APIs. This driver provides + common support for accessing the device. The device supports multiple + sub-devices like charger, fuel gauge, flash LED, current source, + LDO and Buck. + +config MFD_RTSX_USB + tristate "Realtek USB card reader" + depends on USB + select MFD_CORE + help + Select this option to get support for Realtek USB 2.0 card readers + including RTS5129, RTS5139, RTS5179 and RTS5170. + Realtek card reader supports access to many types of memory cards, + such as Memory Stick Pro, Secure Digital and MultiMediaCard. + +config MFD_RC5T583 + bool "Ricoh RC5T583 Power Management system device" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + help + Select this option to get support for the RICOH583 Power + Management system device. + This driver provides common support for accessing the device + through i2c interface. The device supports multiple sub-devices + like GPIO, interrupts, RTC, LDO and DCDC regulators, onkey. + Additional drivers must be enabled in order to use the + different functionality of the device. + +config MFD_RK808 + tristate "Rockchip RK808 Power Management chip" + depends on I2C && OF + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the RK808 + Power Management chips. + This driver provides common support for accessing the device + through I2C interface. The device supports multiple sub-devices + including interrupts, RTC, LDO & DCDC regulators, and onkey. + +config MFD_RN5T618 + tristate "Ricoh RN5T5618 PMIC" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Say yes here to add support for the Ricoh RN5T618 PMIC. This + driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config MFD_SEC_CORE + bool "SAMSUNG Electronics PMIC Series Support" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Support for the Samsung Electronics MFD series. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device + +config MFD_SI476X_CORE + tristate "Silicon Laboratories 4761/64/68 AM/FM radio." + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + This is the core driver for the SI476x series of AM/FM + radio. This MFD driver connects the radio-si476x V4L2 module + and the si476x audio codec. + + To compile this driver as a module, choose M here: the + module will be called si476x-core. + +config MFD_SM501 + tristate "Silicon Motion SM501" + ---help--- + This is the core driver for the Silicon Motion SM501 multimedia + companion chip. This device is a multifunction device which may + provide numerous interfaces including USB host controller, USB gadget, + asynchronous serial ports, audio functions, and a dual display video + interface. The device may be connected by PCI or local bus with + varying functions enabled. + +config MFD_SM501_GPIO + bool "Export GPIO via GPIO layer" + depends on MFD_SM501 && GPIOLIB + ---help--- + This option uses the gpio library layer to export the 64 GPIO + lines on the SM501. The platform data is used to supply the + base number for the first GPIO line to register. + +config MFD_SKY81452 + tristate "Skyworks Solutions SKY81452" + select MFD_CORE + select REGMAP_I2C + depends on I2C + help + This is the core driver for the Skyworks SKY81452 backlight and + voltage regulator device. + + This driver can also be built as a module. If so, the module + will be called sky81452. + +config MFD_SMSC + bool "SMSC ECE1099 series chips" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + help + If you say yes here you get support for the + ece1099 chips from SMSC. + + To compile this driver as a module, choose M here: the + module will be called smsc. + +config ABX500_CORE + bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions" + default y if ARCH_U300 || ARCH_U8500 + help + Say yes here if you have the ABX500 Mixed Signal IC family + chips. This core driver expose register access functions. + Functionality specific drivers using these functions can + remain unchanged when IC changes. Binding of the functions to + actual register access is done by the IC core driver. + +config AB3100_CORE + bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions" + depends on I2C=y && ABX500_CORE + select MFD_CORE + default y if ARCH_U300 + help + Select this to enable the AB3100 Mixed Signal IC core + functionality. This connects to a AB3100 on the I2C bus + and expose a number of symbols needed for dependent devices + to read and write registers and subscribe to events from + this multi-functional IC. This is needed to use other features + of the AB3100 such as battery-backed RTC, charging control, + LEDs, vibrator, system power and temperature, power management + and ALSA sound. + +config AB3100_OTP + tristate "ST-Ericsson AB3100 OTP functions" + depends on AB3100_CORE + default y if AB3100_CORE + help + Select this to enable the AB3100 Mixed Signal IC OTP (one-time + programmable memory) support. This exposes a sysfs file to read + out OTP values. + +config AB8500_CORE + bool "ST-Ericsson AB8500 Mixed Signal Power Management chip" + depends on ABX500_CORE && MFD_DB8500_PRCMU + select POWER_SUPPLY + select MFD_CORE + select IRQ_DOMAIN + help + Select this option to enable access to AB8500 power management + chip. This connects to U8500 either on the SSP/SPI bus (deprecated + since hardware version v1.0) or the I2C bus via PRCMU. It also adds + the irq_chip parts for handling the Mixed Signal chip events. + This chip embeds various other multimedia funtionalities as well. + +config AB8500_DEBUG + bool "Enable debug info via debugfs" + depends on AB8500_GPADC && DEBUG_FS + default y if DEBUG_FS + help + Select this option if you want debug information using the debug + filesystem, debugfs. + +config AB8500_GPADC + bool "ST-Ericsson AB8500 GPADC driver" + depends on AB8500_CORE && REGULATOR_AB8500 + default y + help + AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage + +config MFD_DB8500_PRCMU + bool "ST-Ericsson DB8500 Power Reset Control Management Unit" + depends on UX500_SOC_DB8500 + select MFD_CORE + help + Select this option to enable support for the DB8500 Power Reset + and Control Management Unit. This is basically an autonomous + system controller running an XP70 microprocessor, which is accessed + through a register map. + +config MFD_STMPE + bool "STMicroelectronics STMPE" + depends on (I2C=y || SPI_MASTER=y) + depends on OF + select MFD_CORE + help + Support for the STMPE family of I/O Expanders from + STMicroelectronics. + + Currently supported devices are: + + STMPE811: GPIO, Touchscreen + STMPE1601: GPIO, Keypad + STMPE1801: GPIO, Keypad + STMPE2401: GPIO, Keypad + STMPE2403: GPIO, Keypad + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. Currently available sub drivers are: + + GPIO: stmpe-gpio + Keypad: stmpe-keypad + Touchscreen: stmpe-ts + +menu "STMicroelectronics STMPE Interface Drivers" +depends on MFD_STMPE + +config STMPE_I2C + bool "STMicroelectronics STMPE I2C Interface" + depends on I2C=y + default y + help + This is used to enable I2C interface of STMPE + +config STMPE_SPI + bool "STMicroelectronics STMPE SPI Interface" + depends on SPI_MASTER + help + This is used to enable SPI interface of STMPE +endmenu + +config MFD_STA2X11 + bool "STMicroelectronics STA2X11" + depends on STA2X11 + select MFD_CORE + select REGMAP_MMIO + +config MFD_SUN6I_PRCM + bool "Allwinner A31 PRCM controller" + depends on ARCH_SUNXI + select MFD_CORE + help + Support for the PRCM (Power/Reset/Clock Management) unit available + in A31 SoC. + +config MFD_SYSCON + bool "System Controller Register R/W Based on Regmap" + select REGMAP_MMIO + help + Select this option to enable accessing system control registers + via regmap. + +config MFD_DAVINCI_VOICECODEC + tristate + select MFD_CORE + select REGMAP_MMIO + +config MFD_TI_AM335X_TSCADC + tristate "TI ADC / Touch Screen chip support" + select MFD_CORE + select REGMAP + select REGMAP_MMIO + help + If you say yes here you get support for Texas Instruments series + of Touch Screen /ADC chips. + To compile this driver as a module, choose M here: the + module will be called ti_am335x_tscadc. + +config MFD_DM355EVM_MSP + bool "TI DaVinci DM355 EVM microcontroller" + depends on I2C=y && MACH_DAVINCI_DM355_EVM + help + This driver supports the MSP430 microcontroller used on these + boards. MSP430 firmware manages resets and power sequencing, + inputs from buttons and the IR remote, LEDs, an RTC, and more. + +config MFD_LP3943 + tristate "TI/National Semiconductor LP3943 MFD Driver" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Support for the TI/National Semiconductor LP3943. + This driver consists of GPIO and PWM drivers. + With these functionalities, it can be used for LED string control or + general usage such like a GPIO controller and a PWM controller. + +config MFD_LP8788 + bool "TI LP8788 Power Management Unit Driver" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select IRQ_DOMAIN + help + TI LP8788 PMU supports regulators, battery charger, RTC, + ADC, backlight driver and current sinks. + +config MFD_OMAP_USB_HOST + bool "TI OMAP USBHS core and TLL driver" + depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3 + default y + help + This is the core driver for the OAMP EHCI and OHCI drivers. + This MFD driver does the required setup functionalities for + OMAP USB Host drivers. + +config MFD_PALMAS + bool "TI Palmas series chips" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + depends on I2C=y + help + If you say yes here you get support for the Palmas + series of PMIC chips from Texas Instruments. + +config TPS6105X + tristate "TI TPS61050/61052 Boost Converters" + depends on I2C + select REGULATOR + select MFD_CORE + select REGULATOR_FIXED_VOLTAGE + help + This option enables a driver for the TP61050/TPS61052 + high-power "white LED driver". This boost converter is + sometimes used for other things than white LEDs, and + also contains a GPIO pin. + +config TPS65010 + tristate "TI TPS6501x Power Management chips" + depends on I2C && GPIOLIB + default y if MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_OSK + help + If you say yes here you get support for the TPS6501x series of + Power Management chips. These include voltage regulators, + lithium ion/polymer battery charging, and other features that + are often used in portable devices like cell phones and cameras. + + This driver can also be built as a module. If so, the module + will be called tps65010. + +config TPS6507X + tristate "TI TPS6507x Power Management / Touch Screen chips" + select MFD_CORE + depends on I2C + help + If you say yes here you get support for the TPS6507x series of + Power Management / Touch Screen chips. These include voltage + regulators, lithium ion/polymer battery charging, touch screen + and other features that are often used in portable devices. + This driver can also be built as a module. If so, the module + will be called tps6507x. + +config TPS65911_COMPARATOR + tristate + +config MFD_TPS65090 + bool "TI TPS65090 Power Management chips" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the TPS65090 series of + Power Management chips. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config MFD_TPS65217 + tristate "TI TPS65217 Power Management / White LED chips" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + If you say yes here you get support for the TPS65217 series of + Power Management / White LED chips. + These include voltage regulators, lithium ion/polymer battery + charger, wled and other features that are often used in portable + devices. + + This driver can also be built as a module. If so, the module + will be called tps65217. + +config MFD_TPS65218 + tristate "TI TPS65218 Power Management chips" + depends on I2C + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the TPS65218 series of + Power Management chips. + These include voltage regulators, gpio and other features + that are often used in portable devices. Only regulator + component is currently supported. + + This driver can also be built as a module. If so, the module + will be called tps65218. + +config MFD_TPS6586X + bool "TI TPS6586x Power Management chips" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + help + If you say yes here you get support for the TPS6586X series of + Power Management chips. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + + This driver can also be built as a module. If so, the module + will be called tps6586x. + +config MFD_TPS65910 + bool "TI TPS65910 Power Management chip" + depends on I2C=y && GPIOLIB + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + select IRQ_DOMAIN + help + if you say yes here you get support for the TPS65910 series of + Power Management chips. + +config MFD_TPS65912 + bool "TI TPS65912 Power Management chip" + depends on GPIOLIB + select MFD_CORE + help + If you say yes here you get support for the TPS65912 series of + PM chips. + +config MFD_TPS65912_I2C + bool "TI TPS65912 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 "TI 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 MFD_TPS80031 + bool "TI TPS80031/TPS80032 Power Management chips" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the Texas Instruments + TPS80031/ TPS80032 Fully Integrated Power Management with Power + Path and Battery Charger. The device provides five configurable + step-down converters, 11 general purpose LDOs, USB OTG Module, + ADC, RTC, 2 PWM, System Voltage Regulator/Battery Charger with + Power Path from USB, 32K clock generator. + +config TWL4030_CORE + bool "TI TWL4030/TWL5030/TWL6030/TPS659x0 Support" + depends on I2C=y + select IRQ_DOMAIN + select REGMAP_I2C + help + Say yes here if you have TWL4030 / TWL6030 family chip on your board. + This core driver provides register access and IRQ handling + facilities, and registers devices for the various functions + so that function-specific drivers can bind to them. + + These multi-function chips are found on many OMAP2 and OMAP3 + boards, providing power management, RTC, GPIO, keypad, a + high speed USB OTG transceiver, an audio codec (on most + versions) and many other features. + +config TWL4030_POWER + bool "TI TWL4030 power resources" + depends on TWL4030_CORE && ARM + help + Say yes here if you want to use the power resources on the + TWL4030 family chips. Most of these resources are regulators, + which have a separate driver; some are control signals, such + as clock request handshaking. + + This driver uses board-specific data to initialize the resources + and load scripts controlling which resources are switched off/on + or reset when a sleep, wakeup or warm reset event occurs. + +config MFD_TWL4030_AUDIO + bool "TI TWL4030 Audio" + depends on TWL4030_CORE + select MFD_CORE + default n + +config TWL6040_CORE + bool "TI TWL6040 audio codec" + depends on I2C=y + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + default n + help + Say yes here if you want support for Texas Instruments TWL6040 audio + codec. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device (audio, vibra). + +config MENELAUS + bool "TI TWL92330/Menelaus PM chip" + depends on I2C=y && ARCH_OMAP2 + help + If you say yes here you get support for the Texas Instruments + TWL92330/Menelaus Power Management chip. This include voltage + regulators, Dual slot memory card transceivers, real-time clock + and other features that are often used in portable devices like + cell phones and PDAs. + +config MFD_WL1273_CORE + tristate "TI WL1273 FM radio" + depends on I2C + select MFD_CORE + default n + help + This is the core driver for the TI WL1273 FM radio. This MFD + driver connects the radio-wl1273 V4L2 module and the wl1273 + audio codec. + +config MFD_LM3533 + tristate "TI/National Semiconductor LM3533 Lighting Power chip" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Say yes here to enable support for National Semiconductor / TI + LM3533 Lighting Power chips. + + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the LED, + backlight or ambient-light-sensor functionality of the device. + +config MFD_TIMBERDALE + tristate "Timberdale FPGA" + select MFD_CORE + depends on PCI && GPIOLIB && (X86_32 || COMPILE_TEST) + ---help--- + This is the core driver for the timberdale FPGA. This device is a + multifunction device which exposes numerous platform devices. + + The timberdale FPGA can be found on the Intel Atom development board + for in-vehicle infontainment, called Russellville. + +config MFD_TC3589X + bool "Toshiba TC35892 and variants" + depends on I2C=y + depends on OF + select MFD_CORE + help + Support for the Toshiba TC35892 and variants I/O Expander. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + +config MFD_TMIO + bool + default n + +config MFD_T7L66XB + bool "Toshiba T7L66XB" + depends on ARM && HAVE_CLK + select MFD_CORE + select MFD_TMIO + help + Support for Toshiba Mobile IO Controller T7L66XB + +config MFD_TC6387XB + bool "Toshiba TC6387XB" + depends on ARM && HAVE_CLK + select MFD_CORE + select MFD_TMIO + help + Support for Toshiba Mobile IO Controller TC6387XB + +config MFD_TC6393XB + bool "Toshiba TC6393XB" + depends on ARM && HAVE_CLK + select GPIOLIB + select MFD_CORE + select MFD_TMIO + help + Support for Toshiba Mobile IO Controller TC6393XB + +config MFD_VX855 + tristate "VIA VX855/VX875 integrated south bridge" + depends on PCI + select MFD_CORE + help + Say yes here to enable support for various functions of the + VIA VX855/VX875 south bridge. You will need to enable the vx855_spi + and/or vx855_gpio drivers for this to do anything useful. + +config MFD_ARIZONA + select REGMAP + select REGMAP_IRQ + select MFD_CORE + bool + +config MFD_ARIZONA_I2C + tristate "Wolfson Microelectronics Arizona platform with I2C" + select MFD_ARIZONA + select MFD_CORE + select REGMAP_I2C + depends on I2C + help + Support for the Wolfson Microelectronics Arizona platform audio SoC + core functionality controlled via I2C. + +config MFD_ARIZONA_SPI + tristate "Wolfson Microelectronics Arizona platform with SPI" + select MFD_ARIZONA + select MFD_CORE + select REGMAP_SPI + depends on SPI_MASTER + help + Support for the Wolfson Microelectronics Arizona platform audio SoC + core functionality controlled via I2C. + +config MFD_WM5102 + bool "Wolfson Microelectronics WM5102" + depends on MFD_ARIZONA + help + Support for Wolfson Microelectronics WM5102 low power audio SoC + +config MFD_WM5110 + bool "Wolfson Microelectronics WM5110 and WM8280/WM8281" + depends on MFD_ARIZONA + help + Support for Wolfson Microelectronics WM5110 and WM8280/WM8281 + low power audio SoC + +config MFD_WM8997 + bool "Wolfson Microelectronics WM8997" + depends on MFD_ARIZONA + help + Support for Wolfson Microelectronics WM8997 low power audio SoC + +config MFD_WM8400 + bool "Wolfson Microelectronics WM8400" + select MFD_CORE + depends on I2C=y + select REGMAP_I2C + help + Support for the Wolfson Microelecronics WM8400 PMIC and audio + CODEC. This driver provides common support for accessing + the device, additional drivers must be enabled in order to use + the functionality of the device. + +config MFD_WM831X + bool + +config MFD_WM831X_I2C + bool "Wolfson Microelectronics WM831x/2x PMICs with I2C" + select MFD_CORE + select MFD_WM831X + select REGMAP_I2C + select IRQ_DOMAIN + depends on I2C=y + help + Support for the Wolfson Microelecronics WM831x and WM832x PMICs + when controlled using I2C. This driver provides common support + for accessing the device, additional drivers must be enabled in + order to use the functionality of the device. + +config MFD_WM831X_SPI + bool "Wolfson Microelectronics WM831x/2x PMICs with SPI" + select MFD_CORE + select MFD_WM831X + select REGMAP_SPI + select IRQ_DOMAIN + depends on SPI_MASTER + help + Support for the Wolfson Microelecronics WM831x and WM832x PMICs + when controlled using SPI. This driver provides common support + for accessing the device, additional drivers must be enabled in + order to use the functionality of the device. + +config MFD_WM8350 + bool + +config MFD_WM8350_I2C + bool "Wolfson Microelectronics WM8350 with I2C" + select MFD_WM8350 + depends on I2C=y + help + The WM8350 is an integrated audio and power management + subsystem with watchdog and RTC functionality for embedded + systems. This option enables core support for the WM8350 with + I2C as the control interface. Additional options must be + selected to enable support for the functionality of the chip. + +config MFD_WM8994 + tristate "Wolfson Microelectronics WM8994" + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + depends on I2C + help + The WM8994 is a highly integrated hi-fi CODEC designed for + smartphone applications. As well as audio functionality it + has on board GPIO and regulator functionality which is + supported via the relevant subsystems. This driver provides + core support for the WM8994, in order to use the actual + functionaltiy of the device other drivers must be enabled. + +config MFD_STW481X + tristate "Support for ST Microelectronics STw481x" + depends on I2C && ARCH_NOMADIK + select REGMAP_I2C + select MFD_CORE + help + Select this option to enable the STw481x chip driver used + in various ST Microelectronics and ST-Ericsson embedded + Nomadik series. + +menu "Multimedia Capabilities Port drivers" + depends on ARCH_SA1100 + +config MCP + tristate + +# Interface drivers +config MCP_SA11X0 + tristate "Support SA11x0 MCP interface" + depends on ARCH_SA1100 + select MCP + +# Chip drivers +config MCP_UCB1200 + tristate "Support for UCB1200 / UCB1300" + depends on MCP_SA11X0 + select MCP + +config MCP_UCB1200_TS + tristate "Touchscreen interface support" + depends on MCP_UCB1200 && INPUT + +endmenu + +config MFD_VEXPRESS_SYSREG + bool "Versatile Express System Registers" + depends on VEXPRESS_CONFIG && GPIOLIB + default y + select CLKSRC_MMIO + select GPIO_GENERIC_PLATFORM + select MFD_CORE + select MFD_SYSCON + help + System Registers are the platform configuration block + on the ARM Ltd. Versatile Express board. + +endmenu +endif diff --git a/kernel/drivers/mfd/Makefile b/kernel/drivers/mfd/Makefile new file mode 100644 index 000000000..0e5cfeba1 --- /dev/null +++ b/kernel/drivers/mfd/Makefile @@ -0,0 +1,187 @@ +# +# Makefile for multifunction miscellaneous devices +# + +88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o +obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o +obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o +obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o +obj-$(CONFIG_MFD_SM501) += sm501.o +obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o +obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o +obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o +obj-$(CONFIG_MFD_CROS_EC_I2C) += cros_ec_i2c.o +obj-$(CONFIG_MFD_CROS_EC_SPI) += cros_ec_spi.o + +rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o +obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o +obj-$(CONFIG_MFD_RTSX_USB) += rtsx_usb.o + +obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o +obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o + +obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o +obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o +obj-$(CONFIG_MFD_TI_AM335X_TSCADC) += ti_am335x_tscadc.o + +obj-$(CONFIG_MFD_STA2X11) += sta2x11-mfd.o +obj-$(CONFIG_MFD_STMPE) += stmpe.o +obj-$(CONFIG_STMPE_I2C) += stmpe-i2c.o +obj-$(CONFIG_STMPE_SPI) += stmpe-spi.o +obj-$(CONFIG_MFD_SUN6I_PRCM) += sun6i-prcm.o +obj-$(CONFIG_MFD_TC3589X) += tc3589x.o +obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o +obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o +obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o + +obj-$(CONFIG_MFD_ARIZONA) += arizona-core.o +obj-$(CONFIG_MFD_ARIZONA) += arizona-irq.o +obj-$(CONFIG_MFD_ARIZONA_I2C) += arizona-i2c.o +obj-$(CONFIG_MFD_ARIZONA_SPI) += arizona-spi.o +ifneq ($(CONFIG_MFD_WM5102),n) +obj-$(CONFIG_MFD_ARIZONA) += wm5102-tables.o +endif +ifneq ($(CONFIG_MFD_WM5110),n) +obj-$(CONFIG_MFD_ARIZONA) += wm5110-tables.o +endif +ifneq ($(CONFIG_MFD_WM8997),n) +obj-$(CONFIG_MFD_ARIZONA) += wm8997-tables.o +endif +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 +wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o +wm8350-objs += wm8350-irq.o +obj-$(CONFIG_MFD_WM8350) += wm8350.o +obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o +obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o wm8994-regmap.o + +obj-$(CONFIG_TPS6105X) += tps6105x.o +obj-$(CONFIG_TPS65010) += tps65010.o +obj-$(CONFIG_TPS6507X) += tps6507x.o +obj-$(CONFIG_MFD_TPS65217) += tps65217.o +obj-$(CONFIG_MFD_TPS65218) += tps65218.o +obj-$(CONFIG_MFD_TPS65910) += tps65910.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_MFD_TPS80031) += tps80031.o +obj-$(CONFIG_MENELAUS) += menelaus.o + +obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o +obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o +obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o +obj-$(CONFIG_TWL6040_CORE) += twl6040.o + +obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o +obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o +obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o + +obj-$(CONFIG_MFD_CORE) += mfd-core.o + +obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o + +obj-$(CONFIG_MCP) += mcp-core.o +obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o +obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o +obj-$(CONFIG_MFD_SMSC) += smsc-ece1099.o +obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o + +ifeq ($(CONFIG_SA1100_ASSABET),y) +obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o +endif +obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o + +obj-$(CONFIG_PMIC_DA903X) += da903x.o + +obj-$(CONFIG_PMIC_DA9052) += da9052-irq.o +obj-$(CONFIG_PMIC_DA9052) += da9052-core.o +obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o +obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o +obj-$(CONFIG_MFD_AXP20X) += axp20x.o + +obj-$(CONFIG_MFD_LP3943) += lp3943.o +obj-$(CONFIG_MFD_LP8788) += lp8788.o lp8788-irq.o + +da9055-objs := da9055-core.o da9055-i2c.o +obj-$(CONFIG_MFD_DA9055) += da9055.o + +da9063-objs := da9063-core.o da9063-irq.o da9063-i2c.o +obj-$(CONFIG_MFD_DA9063) += da9063.o +obj-$(CONFIG_MFD_DA9150) += da9150-core.o +obj-$(CONFIG_MFD_MAX14577) += max14577.o +obj-$(CONFIG_MFD_MAX77686) += max77686.o +obj-$(CONFIG_MFD_MAX77693) += max77693.o +obj-$(CONFIG_MFD_MAX77843) += max77843.o +obj-$(CONFIG_MFD_MAX8907) += max8907.o +max8925-objs := max8925-core.o max8925-i2c.o +obj-$(CONFIG_MFD_MAX8925) += max8925.o +obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o +obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o + +pcf50633-objs := pcf50633-core.o pcf50633-irq.o +obj-$(CONFIG_MFD_PCF50633) += pcf50633.o +obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o +obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o +obj-$(CONFIG_ABX500_CORE) += abx500-core.o +obj-$(CONFIG_AB3100_CORE) += ab3100-core.o +obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o +obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o +obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o +obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o +# ab8500-core need to come after db8500-prcmu (which provides the channel) +obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o +obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o +obj-$(CONFIG_PMIC_ADP5520) += adp5520.o +obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o +obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o +obj-$(CONFIG_LPC_SCH) += lpc_sch.o +obj-$(CONFIG_LPC_ICH) += lpc_ich.o +obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o +obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o +obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o +obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o +obj-$(CONFIG_MFD_VX855) += vx855.o +obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o + +si476x-core-y := si476x-cmd.o si476x-prop.o si476x-i2c.o +obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o + +obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o +obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o +obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o +obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o +obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o +obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o +obj-$(CONFIG_MFD_TPS65090) += tps65090.o +obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o +obj-$(CONFIG_MFD_ATMEL_HLCDC) += atmel-hlcdc.o +obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o +obj-$(CONFIG_MFD_PALMAS) += palmas.o +obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o +obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o +obj-$(CONFIG_MFD_RK808) += rk808.o +obj-$(CONFIG_MFD_RN5T618) += rn5t618.o +obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o +obj-$(CONFIG_MFD_SYSCON) += syscon.o +obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o +obj-$(CONFIG_MFD_VEXPRESS_SYSREG) += vexpress-sysreg.o +obj-$(CONFIG_MFD_RETU) += retu-mfd.o +obj-$(CONFIG_MFD_AS3711) += as3711.o +obj-$(CONFIG_MFD_AS3722) += as3722.o +obj-$(CONFIG_MFD_STW481X) += stw481x.o +obj-$(CONFIG_MFD_IPAQ_MICRO) += ipaq-micro.o +obj-$(CONFIG_MFD_MENF21BMC) += menf21bmc.o +obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-pmic-core.o +obj-$(CONFIG_MFD_DLN2) += dln2.o +obj-$(CONFIG_MFD_RT5033) += rt5033.o +obj-$(CONFIG_MFD_SKY81452) += sky81452.o + +intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o +obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o +obj-$(CONFIG_MFD_MT6397) += mt6397-core.o diff --git a/kernel/drivers/mfd/aat2870-core.c b/kernel/drivers/mfd/aat2870-core.c new file mode 100644 index 000000000..4e6e03d63 --- /dev/null +++ b/kernel/drivers/mfd/aat2870-core.c @@ -0,0 +1,525 @@ +/* + * 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 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]; + ssize_t buf_size; + char *start = buf; + unsigned long addr, val; + int ret; + + buf_size = min(count, (size_t)(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++; + + ret = kstrtoul(start, 16, &addr); + if (ret) + return ret; + + if (addr >= AAT2870_REG_NUM) { + dev_err(aat2870->dev, "Invalid address, 0x%lx\n", addr); + return -EINVAL; + } + + while (*start == ' ') + start++; + + ret = kstrtoul(start, 16, &val); + if (ret) + return ret; + + ret = aat2870->write(aat2870, (u8)addr, (u8)val); + if (ret) + return ret; + + return buf_size; +} + +static const struct file_operations aat2870_reg_fops = { + .open = simple_open, + .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 = dev_get_platdata(&client->dev); + struct aat2870_data *aat2870; + int i, j; + int ret = 0; + + aat2870 = devm_kzalloc(&client->dev, sizeof(struct aat2870_data), + GFP_KERNEL); + if (!aat2870) { + dev_err(&client->dev, + "Failed to allocate memory for aat2870\n"); + return -ENOMEM; + } + + 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 = devm_gpio_request_one(&client->dev, aat2870->en_pin, + GPIOF_OUT_INIT_HIGH, "aat2870-en"); + if (ret < 0) { + dev_err(&client->dev, + "Failed to request GPIO %d\n", aat2870->en_pin); + return ret; + } + } + + 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, NULL); + 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); + 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->uninit) + aat2870->uninit(aat2870); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int aat2870_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct aat2870_data *aat2870 = i2c_get_clientdata(client); + + aat2870_disable(aat2870); + + return 0; +} + +static int aat2870_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + 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; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(aat2870_pm_ops, aat2870_i2c_suspend, + aat2870_i2c_resume); + +static const 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, + .pm = &aat2870_pm_ops, + }, + .probe = aat2870_i2c_probe, + .remove = aat2870_i2c_remove, + .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/kernel/drivers/mfd/ab3100-core.c b/kernel/drivers/mfd/ab3100-core.c new file mode 100644 index 000000000..4659ac1db --- /dev/null +++ b/kernel/drivers/mfd/ab3100-core.c @@ -0,0 +1,997 @@ +/* + * Copyright (C) 2007-2010 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Low-level core for exclusive access to the AB3100 IC on the I2C bus + * and some basic chip-configuration. + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ + +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/random.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ab3100.h> +#include <linux/mfd/abx500.h> + +/* These are the only registers inside AB3100 used in this main file */ + +/* Interrupt event registers */ +#define AB3100_EVENTA1 0x21 +#define AB3100_EVENTA2 0x22 +#define AB3100_EVENTA3 0x23 + +/* AB3100 DAC converter registers */ +#define AB3100_DIS 0x00 +#define AB3100_D0C 0x01 +#define AB3100_D1C 0x02 +#define AB3100_D2C 0x03 +#define AB3100_D3C 0x04 + +/* Chip ID register */ +#define AB3100_CID 0x20 + +/* AB3100 interrupt registers */ +#define AB3100_IMRA1 0x24 +#define AB3100_IMRA2 0x25 +#define AB3100_IMRA3 0x26 +#define AB3100_IMRB1 0x2B +#define AB3100_IMRB2 0x2C +#define AB3100_IMRB3 0x2D + +/* System Power Monitoring and control registers */ +#define AB3100_MCA 0x2E +#define AB3100_MCB 0x2F + +/* SIM power up */ +#define AB3100_SUP 0x50 + +/* + * I2C communication + * + * The AB3100 is usually assigned address 0x48 (7-bit) + * The chip is defined in the platform i2c_board_data section. + */ +static int ab3100_get_chip_id(struct device *dev) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return (int)ab3100->chip_id; +} + +static int ab3100_set_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 regval) +{ + u8 regandval[2] = {reg, regval}; + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* + * A two-byte write message with the first byte containing the register + * number and the second byte containing the value to be written + * effectively sets a register in the AB3100. + */ + err = i2c_master_send(ab3100->i2c_client, regandval, 2); + if (err < 0) { + dev_err(ab3100->dev, + "write error (write register): %d\n", + err); + } else if (err != 2) { + dev_err(ab3100->dev, + "write error (write register)\n" + " %d bytes transferred (expected 2)\n", + err); + err = -EIO; + } else { + /* All is well */ + err = 0; + } + mutex_unlock(&ab3100->access_mutex); + return err; +} + +static int set_register_interruptible(struct device *dev, + u8 bank, u8 reg, u8 value) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_set_register_interruptible(ab3100, reg, value); +} + +/* + * The test registers exist at an I2C bus address up one + * from the ordinary base. They are not supposed to be used + * in production code, but sometimes you have to do that + * anyway. It's currently only used from this file so declare + * it static and do not export. + */ +static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 regval) +{ + u8 regandval[2] = {reg, regval}; + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + err = i2c_master_send(ab3100->testreg_client, regandval, 2); + if (err < 0) { + dev_err(ab3100->dev, + "write error (write test register): %d\n", + err); + } else if (err != 2) { + dev_err(ab3100->dev, + "write error (write test register)\n" + " %d bytes transferred (expected 2)\n", + err); + err = -EIO; + } else { + /* All is well */ + err = 0; + } + mutex_unlock(&ab3100->access_mutex); + + return err; +} + +static int ab3100_get_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 *regval) +{ + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* + * AB3100 require an I2C "stop" command between each message, else + * it will not work. The only way of achieveing this with the + * message transport layer is to send the read and write messages + * separately. + */ + err = i2c_master_send(ab3100->i2c_client, ®, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (send register address): %d\n", + err); + goto get_reg_out_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (send register address)\n" + " %d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_reg_out_unlock; + } else { + /* All is well */ + err = 0; + } + + err = i2c_master_recv(ab3100->i2c_client, regval, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (read register): %d\n", + err); + goto get_reg_out_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (read register)\n" + " %d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_reg_out_unlock; + } else { + /* All is well */ + err = 0; + } + + get_reg_out_unlock: + mutex_unlock(&ab3100->access_mutex); + return err; +} + +static int get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_get_register_interruptible(ab3100, reg, value); +} + +static int ab3100_get_register_page_interruptible(struct ab3100 *ab3100, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + if (ab3100->chip_id == 0xa0 || + ab3100->chip_id == 0xa1) + /* These don't support paged reads */ + return -EIO; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* + * Paged read also require an I2C "stop" command. + */ + err = i2c_master_send(ab3100->i2c_client, &first_reg, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (send first register address): %d\n", + err); + goto get_reg_page_out_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (send first register address)\n" + " %d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_reg_page_out_unlock; + } + + err = i2c_master_recv(ab3100->i2c_client, regvals, numregs); + if (err < 0) { + dev_err(ab3100->dev, + "write error (read register page): %d\n", + err); + goto get_reg_page_out_unlock; + } else if (err != numregs) { + dev_err(ab3100->dev, + "write error (read register page)\n" + " %d bytes transferred (expected %d)\n", + err, numregs); + err = -EIO; + goto get_reg_page_out_unlock; + } + + /* All is well */ + err = 0; + + get_reg_page_out_unlock: + mutex_unlock(&ab3100->access_mutex); + return err; +} + +static int get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_get_register_page_interruptible(ab3100, + first_reg, regvals, numregs); +} + +static int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100, + u8 reg, u8 andmask, u8 ormask) +{ + u8 regandval[2] = {reg, 0}; + int err; + + err = mutex_lock_interruptible(&ab3100->access_mutex); + if (err) + return err; + + /* First read out the target register */ + err = i2c_master_send(ab3100->i2c_client, ®, 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (maskset send address): %d\n", + err); + goto get_maskset_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (maskset send address)\n" + " %d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_maskset_unlock; + } + + err = i2c_master_recv(ab3100->i2c_client, ®andval[1], 1); + if (err < 0) { + dev_err(ab3100->dev, + "write error (maskset read register): %d\n", + err); + goto get_maskset_unlock; + } else if (err != 1) { + dev_err(ab3100->dev, + "write error (maskset read register)\n" + " %d bytes transferred (expected 1)\n", + err); + err = -EIO; + goto get_maskset_unlock; + } + + /* Modify the register */ + regandval[1] &= andmask; + regandval[1] |= ormask; + + /* Write the register */ + err = i2c_master_send(ab3100->i2c_client, regandval, 2); + if (err < 0) { + dev_err(ab3100->dev, + "write error (write register): %d\n", + err); + goto get_maskset_unlock; + } else if (err != 2) { + dev_err(ab3100->dev, + "write error (write register)\n" + " %d bytes transferred (expected 2)\n", + err); + err = -EIO; + goto get_maskset_unlock; + } + + /* All is well */ + err = 0; + + get_maskset_unlock: + mutex_unlock(&ab3100->access_mutex); + return err; +} + +static int mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + + return ab3100_mask_and_set_register_interruptible(ab3100, + reg, bitmask, (bitmask & bitvalues)); +} + +/* + * Register a simple callback for handling any AB3100 events. + */ +int ab3100_event_register(struct ab3100 *ab3100, + struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&ab3100->event_subscribers, + nb); +} +EXPORT_SYMBOL(ab3100_event_register); + +/* + * Remove a previously registered callback. + */ +int ab3100_event_unregister(struct ab3100 *ab3100, + struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&ab3100->event_subscribers, + nb); +} +EXPORT_SYMBOL(ab3100_event_unregister); + + +static int ab3100_event_registers_startup_state_get(struct device *dev, + u8 *event) +{ + struct ab3100 *ab3100 = dev_get_drvdata(dev->parent); + if (!ab3100->startup_events_read) + return -EAGAIN; /* Try again later */ + memcpy(event, ab3100->startup_events, 3); + return 0; +} + +static struct abx500_ops ab3100_ops = { + .get_chip_id = ab3100_get_chip_id, + .set_register = set_register_interruptible, + .get_register = get_register_interruptible, + .get_register_page = get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab3100_event_registers_startup_state_get, + .startup_irq_enabled = NULL, +}; + +/* + * This is a threaded interrupt handler so we can make some + * I2C calls etc. + */ +static irqreturn_t ab3100_irq_handler(int irq, void *data) +{ + struct ab3100 *ab3100 = data; + u8 event_regs[3]; + u32 fatevent; + int err; + + err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1, + event_regs, 3); + if (err) + goto err_event; + + fatevent = (event_regs[0] << 16) | + (event_regs[1] << 8) | + event_regs[2]; + + if (!ab3100->startup_events_read) { + ab3100->startup_events[0] = event_regs[0]; + ab3100->startup_events[1] = event_regs[1]; + ab3100->startup_events[2] = event_regs[2]; + ab3100->startup_events_read = true; + } + /* + * The notified parties will have to mask out the events + * they're interested in and react to them. They will be + * notified on all events, then they use the fatevent value + * to determine if they're interested. + */ + blocking_notifier_call_chain(&ab3100->event_subscribers, + fatevent, NULL); + + dev_dbg(ab3100->dev, + "IRQ Event: 0x%08x\n", fatevent); + + return IRQ_HANDLED; + + err_event: + dev_dbg(ab3100->dev, + "error reading event status\n"); + return IRQ_HANDLED; +} + +#ifdef CONFIG_DEBUG_FS +/* + * Some debugfs entries only exposed if we're using debug + */ +static int ab3100_registers_print(struct seq_file *s, void *p) +{ + struct ab3100 *ab3100 = s->private; + u8 value; + u8 reg; + + seq_puts(s, "AB3100 registers:\n"); + + for (reg = 0; reg < 0xff; reg++) { + ab3100_get_register_interruptible(ab3100, reg, &value); + seq_printf(s, "[0x%x]: 0x%x\n", reg, value); + } + return 0; +} + +static int ab3100_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3100_registers_print, inode->i_private); +} + +static const struct file_operations ab3100_registers_fops = { + .open = ab3100_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +struct ab3100_get_set_reg_priv { + struct ab3100 *ab3100; + bool mode; +}; + +static ssize_t ab3100_get_set_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab3100_get_set_reg_priv *priv = file->private_data; + struct ab3100 *ab3100 = priv->ab3100; + char buf[32]; + ssize_t buf_size; + int regp; + u8 user_reg; + int err; + int i = 0; + + /* 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; + + /* + * The idea is here to parse a string which is either + * "0xnn" for reading a register, or "0xaa 0xbb" for + * writing 0xbb to the register 0xaa. First move past + * whitespace and then begin to parse the register. + */ + while ((i < buf_size) && (buf[i] == ' ')) + i++; + regp = i; + + /* + * Advance pointer to end of string then terminate + * the register string. This is needed to satisfy + * the kstrtou8() function. + */ + while ((i < buf_size) && (buf[i] != ' ')) + i++; + buf[i] = '\0'; + + err = kstrtou8(&buf[regp], 16, &user_reg); + if (err) + return err; + + /* Either we read or we write a register here */ + if (!priv->mode) { + /* Reading */ + u8 regvalue; + + ab3100_get_register_interruptible(ab3100, user_reg, ®value); + + dev_info(ab3100->dev, + "debug read AB3100 reg[0x%02x]: 0x%02x\n", + user_reg, regvalue); + } else { + int valp; + u8 user_value; + u8 regvalue; + + /* + * Writing, we need some value to write to + * the register so keep parsing the string + * from userspace. + */ + i++; + while ((i < buf_size) && (buf[i] == ' ')) + i++; + valp = i; + while ((i < buf_size) && (buf[i] != ' ')) + i++; + buf[i] = '\0'; + + err = kstrtou8(&buf[valp], 16, &user_value); + if (err) + return err; + + ab3100_set_register_interruptible(ab3100, user_reg, user_value); + ab3100_get_register_interruptible(ab3100, user_reg, ®value); + + dev_info(ab3100->dev, + "debug write reg[0x%02x]\n" + " with 0x%02x, after readback: 0x%02x\n", + user_reg, user_value, regvalue); + } + return buf_size; +} + +static const struct file_operations ab3100_get_set_reg_fops = { + .open = simple_open, + .write = ab3100_get_set_reg, + .llseek = noop_llseek, +}; + +static struct dentry *ab3100_dir; +static struct dentry *ab3100_reg_file; +static struct ab3100_get_set_reg_priv ab3100_get_priv; +static struct dentry *ab3100_get_reg_file; +static struct ab3100_get_set_reg_priv ab3100_set_priv; +static struct dentry *ab3100_set_reg_file; + +static void ab3100_setup_debugfs(struct ab3100 *ab3100) +{ + int err; + + ab3100_dir = debugfs_create_dir("ab3100", NULL); + if (!ab3100_dir) + goto exit_no_debugfs; + + ab3100_reg_file = debugfs_create_file("registers", + S_IRUGO, ab3100_dir, ab3100, + &ab3100_registers_fops); + if (!ab3100_reg_file) { + err = -ENOMEM; + goto exit_destroy_dir; + } + + ab3100_get_priv.ab3100 = ab3100; + ab3100_get_priv.mode = false; + ab3100_get_reg_file = debugfs_create_file("get_reg", + S_IWUSR, ab3100_dir, &ab3100_get_priv, + &ab3100_get_set_reg_fops); + if (!ab3100_get_reg_file) { + err = -ENOMEM; + goto exit_destroy_reg; + } + + ab3100_set_priv.ab3100 = ab3100; + ab3100_set_priv.mode = true; + ab3100_set_reg_file = debugfs_create_file("set_reg", + S_IWUSR, ab3100_dir, &ab3100_set_priv, + &ab3100_get_set_reg_fops); + if (!ab3100_set_reg_file) { + err = -ENOMEM; + goto exit_destroy_get_reg; + } + return; + + exit_destroy_get_reg: + debugfs_remove(ab3100_get_reg_file); + exit_destroy_reg: + debugfs_remove(ab3100_reg_file); + exit_destroy_dir: + debugfs_remove(ab3100_dir); + exit_no_debugfs: + return; +} +static inline void ab3100_remove_debugfs(void) +{ + debugfs_remove(ab3100_set_reg_file); + debugfs_remove(ab3100_get_reg_file); + debugfs_remove(ab3100_reg_file); + debugfs_remove(ab3100_dir); +} +#else +static inline void ab3100_setup_debugfs(struct ab3100 *ab3100) +{ +} +static inline void ab3100_remove_debugfs(void) +{ +} +#endif + +/* + * Basic set-up, datastructure creation/destruction and I2C interface. + * This sets up a default config in the AB3100 chip so that it + * will work as expected. + */ + +struct ab3100_init_setting { + u8 abreg; + u8 setting; +}; + +static const struct ab3100_init_setting ab3100_init_settings[] = { + { + .abreg = AB3100_MCA, + .setting = 0x01 + }, { + .abreg = AB3100_MCB, + .setting = 0x30 + }, { + .abreg = AB3100_IMRA1, + .setting = 0x00 + }, { + .abreg = AB3100_IMRA2, + .setting = 0xFF + }, { + .abreg = AB3100_IMRA3, + .setting = 0x01 + }, { + .abreg = AB3100_IMRB1, + .setting = 0xBF + }, { + .abreg = AB3100_IMRB2, + .setting = 0xFF + }, { + .abreg = AB3100_IMRB3, + .setting = 0xFF + }, { + .abreg = AB3100_SUP, + .setting = 0x00 + }, { + .abreg = AB3100_DIS, + .setting = 0xF0 + }, { + .abreg = AB3100_D0C, + .setting = 0x00 + }, { + .abreg = AB3100_D1C, + .setting = 0x00 + }, { + .abreg = AB3100_D2C, + .setting = 0x00 + }, { + .abreg = AB3100_D3C, + .setting = 0x00 + }, +}; + +static int ab3100_setup(struct ab3100 *ab3100) +{ + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(ab3100_init_settings); i++) { + err = ab3100_set_register_interruptible(ab3100, + ab3100_init_settings[i].abreg, + ab3100_init_settings[i].setting); + if (err) + goto exit_no_setup; + } + + /* + * Special trick to make the AB3100 use the 32kHz clock (RTC) + * bit 3 in test register 0x02 is a special, undocumented test + * register bit that only exist in AB3100 P1E + */ + if (ab3100->chip_id == 0xc4) { + dev_warn(ab3100->dev, + "AB3100 P1E variant detected forcing chip to 32KHz\n"); + err = ab3100_set_test_register_interruptible(ab3100, + 0x02, 0x08); + } + + exit_no_setup: + return err; +} + +/* The subdevices of the AB3100 */ +static struct mfd_cell ab3100_devs[] = { + { + .name = "ab3100-dac", + .id = -1, + }, + { + .name = "ab3100-leds", + .id = -1, + }, + { + .name = "ab3100-power", + .id = -1, + }, + { + .name = "ab3100-regulators", + .of_compatible = "stericsson,ab3100-regulators", + .id = -1, + }, + { + .name = "ab3100-sim", + .id = -1, + }, + { + .name = "ab3100-uart", + .id = -1, + }, + { + .name = "ab3100-rtc", + .id = -1, + }, + { + .name = "ab3100-charger", + .id = -1, + }, + { + .name = "ab3100-boost", + .id = -1, + }, + { + .name = "ab3100-adc", + .id = -1, + }, + { + .name = "ab3100-fuelgauge", + .id = -1, + }, + { + .name = "ab3100-vibrator", + .id = -1, + }, + { + .name = "ab3100-otp", + .id = -1, + }, + { + .name = "ab3100-codec", + .id = -1, + }, +}; + +struct ab_family_id { + u8 id; + char *name; +}; + +static const struct ab_family_id ids[] = { + /* AB3100 */ + { + .id = 0xc0, + .name = "P1A" + }, { + .id = 0xc1, + .name = "P1B" + }, { + .id = 0xc2, + .name = "P1C" + }, { + .id = 0xc3, + .name = "P1D" + }, { + .id = 0xc4, + .name = "P1E" + }, { + .id = 0xc5, + .name = "P1F/R1A" + }, { + .id = 0xc6, + .name = "P1G/R1A" + }, { + .id = 0xc7, + .name = "P2A/R2A" + }, { + .id = 0xc8, + .name = "P2B/R2B" + }, + /* AB3000 variants, not supported */ + { + .id = 0xa0 + }, { + .id = 0xa1 + }, { + .id = 0xa2 + }, { + .id = 0xa3 + }, { + .id = 0xa4 + }, { + .id = 0xa5 + }, { + .id = 0xa6 + }, { + .id = 0xa7 + }, + /* Terminator */ + { + .id = 0x00, + }, +}; + +static int ab3100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ab3100 *ab3100; + struct ab3100_platform_data *ab3100_plf_data = + dev_get_platdata(&client->dev); + int err; + int i; + + ab3100 = devm_kzalloc(&client->dev, sizeof(struct ab3100), GFP_KERNEL); + if (!ab3100) { + dev_err(&client->dev, "could not allocate AB3100 device\n"); + return -ENOMEM; + } + + /* Initialize data structure */ + mutex_init(&ab3100->access_mutex); + BLOCKING_INIT_NOTIFIER_HEAD(&ab3100->event_subscribers); + + ab3100->i2c_client = client; + ab3100->dev = &ab3100->i2c_client->dev; + + i2c_set_clientdata(client, ab3100); + + /* Read chip ID register */ + err = ab3100_get_register_interruptible(ab3100, AB3100_CID, + &ab3100->chip_id); + if (err) { + dev_err(&client->dev, + "failed to communicate with AB3100 chip\n"); + goto exit_no_detect; + } + + for (i = 0; ids[i].id != 0x0; i++) { + if (ids[i].id == ab3100->chip_id) { + if (ids[i].name != NULL) { + snprintf(&ab3100->chip_name[0], + sizeof(ab3100->chip_name) - 1, + "AB3100 %s", + ids[i].name); + break; + } else { + dev_err(&client->dev, + "AB3000 is not supported\n"); + goto exit_no_detect; + } + } + } + + if (ids[i].id == 0x0) { + dev_err(&client->dev, "unknown analog baseband chip id: 0x%x\n", + ab3100->chip_id); + dev_err(&client->dev, + "accepting it anyway. Please update the driver.\n"); + goto exit_no_detect; + } + + dev_info(&client->dev, "Detected chip: %s\n", + &ab3100->chip_name[0]); + + /* Attach a second dummy i2c_client to the test register address */ + ab3100->testreg_client = i2c_new_dummy(client->adapter, + client->addr + 1); + if (!ab3100->testreg_client) { + err = -ENOMEM; + goto exit_no_testreg_client; + } + + err = ab3100_setup(ab3100); + if (err) + goto exit_no_setup; + + err = devm_request_threaded_irq(&client->dev, + client->irq, NULL, ab3100_irq_handler, + IRQF_ONESHOT, "ab3100-core", ab3100); + if (err) + goto exit_no_irq; + + err = abx500_register_ops(&client->dev, &ab3100_ops); + if (err) + goto exit_no_ops; + + /* Set up and register the platform devices. */ + for (i = 0; i < ARRAY_SIZE(ab3100_devs); i++) { + ab3100_devs[i].platform_data = ab3100_plf_data; + ab3100_devs[i].pdata_size = sizeof(struct ab3100_platform_data); + } + + err = mfd_add_devices(&client->dev, 0, ab3100_devs, + ARRAY_SIZE(ab3100_devs), NULL, 0, NULL); + + ab3100_setup_debugfs(ab3100); + + return 0; + + exit_no_ops: + exit_no_irq: + exit_no_setup: + i2c_unregister_device(ab3100->testreg_client); + exit_no_testreg_client: + exit_no_detect: + return err; +} + +static int ab3100_remove(struct i2c_client *client) +{ + struct ab3100 *ab3100 = i2c_get_clientdata(client); + + /* Unregister subdevices */ + mfd_remove_devices(&client->dev); + ab3100_remove_debugfs(); + i2c_unregister_device(ab3100->testreg_client); + return 0; +} + +static const struct i2c_device_id ab3100_id[] = { + { "ab3100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ab3100_id); + +static struct i2c_driver ab3100_driver = { + .driver = { + .name = "ab3100", + .owner = THIS_MODULE, + }, + .id_table = ab3100_id, + .probe = ab3100_probe, + .remove = ab3100_remove, +}; + +static int __init ab3100_i2c_init(void) +{ + return i2c_add_driver(&ab3100_driver); +} + +static void __exit ab3100_i2c_exit(void) +{ + i2c_del_driver(&ab3100_driver); +} + +subsys_initcall(ab3100_i2c_init); +module_exit(ab3100_i2c_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("AB3100 core driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/ab3100-otp.c b/kernel/drivers/mfd/ab3100-otp.c new file mode 100644 index 000000000..f391c5fee --- /dev/null +++ b/kernel/drivers/mfd/ab3100-otp.c @@ -0,0 +1,250 @@ +/* + * drivers/mfd/ab3100_otp.c + * + * Copyright (C) 2007-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Driver to read out OTP from the AB3100 Mixed-signal circuit + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mfd/abx500.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +/* The OTP registers */ +#define AB3100_OTP0 0xb0 +#define AB3100_OTP1 0xb1 +#define AB3100_OTP2 0xb2 +#define AB3100_OTP3 0xb3 +#define AB3100_OTP4 0xb4 +#define AB3100_OTP5 0xb5 +#define AB3100_OTP6 0xb6 +#define AB3100_OTP7 0xb7 +#define AB3100_OTPP 0xbf + +/** + * struct ab3100_otp + * @dev containing device + * @locked whether the OTP is locked, after locking, no more bits + * can be changed but before locking it is still possible + * to change bits from 1->0. + * @freq clocking frequency for the OTP, this frequency is either + * 32768Hz or 1MHz/30 + * @paf product activation flag, indicates whether this is a real + * product (paf true) or a lab board etc (paf false) + * @imeich if this is set it is possible to override the + * IMEI number found in the tac, fac and svn fields with + * (secured) software + * @cid customer ID + * @tac type allocation code of the IMEI + * @fac final assembly code of the IMEI + * @svn software version number of the IMEI + * @debugfs a debugfs file used when dumping to file + */ +struct ab3100_otp { + struct device *dev; + bool locked; + u32 freq; + bool paf; + bool imeich; + u16 cid:14; + u32 tac:20; + u8 fac; + u32 svn:20; + struct dentry *debugfs; +}; + +static int __init ab3100_otp_read(struct ab3100_otp *otp) +{ + u8 otpval[8]; + u8 otpp; + int err; + + err = abx500_get_register_interruptible(otp->dev, 0, + AB3100_OTPP, &otpp); + if (err) { + dev_err(otp->dev, "unable to read OTPP register\n"); + return err; + } + + err = abx500_get_register_page_interruptible(otp->dev, 0, + AB3100_OTP0, otpval, 8); + if (err) { + dev_err(otp->dev, "unable to read OTP register page\n"); + return err; + } + + /* Cache OTP properties, they never change by nature */ + otp->locked = (otpp & 0x80); + otp->freq = (otpp & 0x40) ? 32768 : 34100; + otp->paf = (otpval[1] & 0x80); + otp->imeich = (otpval[1] & 0x40); + otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff; + otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2]; + otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4); + otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4); + return 0; +} + +/* + * This is a simple debugfs human-readable file that dumps out + * the contents of the OTP. + */ +#ifdef CONFIG_DEBUG_FS +static int ab3100_show_otp(struct seq_file *s, void *v) +{ + struct ab3100_otp *otp = s->private; + + seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED"); + seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq); + seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET"); + seq_printf(s, "IMEI is %s\n", otp->imeich ? + "CHANGEABLE" : "NOT CHANGEABLE"); + seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid); + seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn); + return 0; +} + +static int ab3100_otp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab3100_show_otp, inode->i_private); +} + +static const struct file_operations ab3100_otp_operations = { + .open = ab3100_otp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO, + NULL, otp, + &ab3100_otp_operations); + if (!otp->debugfs) { + dev_err(dev, "AB3100 debugfs OTP file registration failed!\n"); + return -ENOENT; + } + return 0; +} + +static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ + debugfs_remove(otp->debugfs); +} +#else +/* Compile this out if debugfs not selected */ +static inline int __init ab3100_otp_init_debugfs(struct device *dev, + struct ab3100_otp *otp) +{ + return 0; +} + +static inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp) +{ +} +#endif + +#define SHOW_AB3100_ATTR(name) \ +static ssize_t ab3100_otp_##name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{\ + struct ab3100_otp *otp = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", otp->name); \ +} + +SHOW_AB3100_ATTR(locked) +SHOW_AB3100_ATTR(freq) +SHOW_AB3100_ATTR(paf) +SHOW_AB3100_ATTR(imeich) +SHOW_AB3100_ATTR(cid) +SHOW_AB3100_ATTR(fac) +SHOW_AB3100_ATTR(tac) +SHOW_AB3100_ATTR(svn) + +static struct device_attribute ab3100_otp_attrs[] = { + __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL), + __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL), + __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL), + __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL), + __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL), + __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL), + __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL), + __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL), +}; + +static int __init ab3100_otp_probe(struct platform_device *pdev) +{ + struct ab3100_otp *otp; + int err = 0; + int i; + + otp = devm_kzalloc(&pdev->dev, sizeof(struct ab3100_otp), GFP_KERNEL); + if (!otp) { + dev_err(&pdev->dev, "could not allocate AB3100 OTP device\n"); + return -ENOMEM; + } + otp->dev = &pdev->dev; + + /* Replace platform data coming in with a local struct */ + platform_set_drvdata(pdev, otp); + + err = ab3100_otp_read(otp); + if (err) + return err; + + dev_info(&pdev->dev, "AB3100 OTP readout registered\n"); + + /* sysfs entries */ + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) { + err = device_create_file(&pdev->dev, + &ab3100_otp_attrs[i]); + if (err) + goto err; + } + + /* debugfs entries */ + err = ab3100_otp_init_debugfs(&pdev->dev, otp); + if (err) + goto err; + + return 0; + +err: + while (--i >= 0) + device_remove_file(&pdev->dev, &ab3100_otp_attrs[i]); + return err; +} + +static int __exit ab3100_otp_remove(struct platform_device *pdev) +{ + struct ab3100_otp *otp = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) + device_remove_file(&pdev->dev, + &ab3100_otp_attrs[i]); + ab3100_otp_exit_debugfs(otp); + return 0; +} + +static struct platform_driver ab3100_otp_driver = { + .driver = { + .name = "ab3100-otp", + }, + .remove = __exit_p(ab3100_otp_remove), +}; + +module_platform_driver_probe(ab3100_otp_driver, ab3100_otp_probe); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("AB3100 OTP Readout Driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/ab8500-core.c b/kernel/drivers/mfd/ab8500-core.c new file mode 100644 index 000000000..c80a2925f --- /dev/null +++ b/kernel/drivers/mfd/ab8500-core.c @@ -0,0 +1,1871 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Srinidhi Kasagar <srinidhi.kasagar@stericsson.com> + * Author: Rabin Vincent <rabin.vincent@stericsson.com> + * Author: Mattias Wallin <mattias.wallin@stericsson.com> + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/regulator/ab8500.h> +#include <linux/of.h> +#include <linux/of_device.h> + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE1_REG 0x00 +#define AB8500_IT_SOURCE2_REG 0x01 +#define AB8500_IT_SOURCE3_REG 0x02 +#define AB8500_IT_SOURCE4_REG 0x03 +#define AB8500_IT_SOURCE5_REG 0x04 +#define AB8500_IT_SOURCE6_REG 0x05 +#define AB8500_IT_SOURCE7_REG 0x06 +#define AB8500_IT_SOURCE8_REG 0x07 +#define AB9540_IT_SOURCE13_REG 0x0C +#define AB8500_IT_SOURCE19_REG 0x12 +#define AB8500_IT_SOURCE20_REG 0x13 +#define AB8500_IT_SOURCE21_REG 0x14 +#define AB8500_IT_SOURCE22_REG 0x15 +#define AB8500_IT_SOURCE23_REG 0x16 +#define AB8500_IT_SOURCE24_REG 0x17 + +/* + * latch registers + */ +#define AB8500_IT_LATCH1_REG 0x20 +#define AB8500_IT_LATCH2_REG 0x21 +#define AB8500_IT_LATCH3_REG 0x22 +#define AB8500_IT_LATCH4_REG 0x23 +#define AB8500_IT_LATCH5_REG 0x24 +#define AB8500_IT_LATCH6_REG 0x25 +#define AB8500_IT_LATCH7_REG 0x26 +#define AB8500_IT_LATCH8_REG 0x27 +#define AB8500_IT_LATCH9_REG 0x28 +#define AB8500_IT_LATCH10_REG 0x29 +#define AB8500_IT_LATCH12_REG 0x2B +#define AB9540_IT_LATCH13_REG 0x2C +#define AB8500_IT_LATCH19_REG 0x32 +#define AB8500_IT_LATCH20_REG 0x33 +#define AB8500_IT_LATCH21_REG 0x34 +#define AB8500_IT_LATCH22_REG 0x35 +#define AB8500_IT_LATCH23_REG 0x36 +#define AB8500_IT_LATCH24_REG 0x37 + +/* + * mask registers + */ + +#define AB8500_IT_MASK1_REG 0x40 +#define AB8500_IT_MASK2_REG 0x41 +#define AB8500_IT_MASK3_REG 0x42 +#define AB8500_IT_MASK4_REG 0x43 +#define AB8500_IT_MASK5_REG 0x44 +#define AB8500_IT_MASK6_REG 0x45 +#define AB8500_IT_MASK7_REG 0x46 +#define AB8500_IT_MASK8_REG 0x47 +#define AB8500_IT_MASK9_REG 0x48 +#define AB8500_IT_MASK10_REG 0x49 +#define AB8500_IT_MASK11_REG 0x4A +#define AB8500_IT_MASK12_REG 0x4B +#define AB8500_IT_MASK13_REG 0x4C +#define AB8500_IT_MASK14_REG 0x4D +#define AB8500_IT_MASK15_REG 0x4E +#define AB8500_IT_MASK16_REG 0x4F +#define AB8500_IT_MASK17_REG 0x50 +#define AB8500_IT_MASK18_REG 0x51 +#define AB8500_IT_MASK19_REG 0x52 +#define AB8500_IT_MASK20_REG 0x53 +#define AB8500_IT_MASK21_REG 0x54 +#define AB8500_IT_MASK22_REG 0x55 +#define AB8500_IT_MASK23_REG 0x56 +#define AB8500_IT_MASK24_REG 0x57 +#define AB8500_IT_MASK25_REG 0x58 + +/* + * latch hierarchy registers + */ +#define AB8500_IT_LATCHHIER1_REG 0x60 +#define AB8500_IT_LATCHHIER2_REG 0x61 +#define AB8500_IT_LATCHHIER3_REG 0x62 +#define AB8540_IT_LATCHHIER4_REG 0x63 + +#define AB8500_IT_LATCHHIER_NUM 3 +#define AB8540_IT_LATCHHIER_NUM 4 + +#define AB8500_REV_REG 0x80 +#define AB8500_IC_NAME_REG 0x82 +#define AB8500_SWITCH_OFF_STATUS 0x00 + +#define AB8500_TURN_ON_STATUS 0x00 +#define AB8505_TURN_ON_STATUS_2 0x04 + +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 + +static DEFINE_SPINLOCK(on_stat_lock); +static u8 turn_on_stat_mask = 0xFF; +static u8 turn_on_stat_set; +static bool no_bm; /* No battery management */ +module_param(no_bm, bool, S_IRUGO); + +#define AB9540_MODEM_CTRL2_REG 0x23 +#define AB9540_MODEM_CTRL2_SWDBBRSTN_BIT BIT(2) + +/* + * Map interrupt numbers to the LATCH and MASK register offsets, Interrupt + * numbers are indexed into this array with (num / 8). The interupts are + * defined in linux/mfd/ab8500.h + * + * This is one off from the register names, i.e. AB8500_IT_MASK1_REG is at + * offset 0. + */ +/* AB8500 support */ +static const int ab8500_irq_regoffset[AB8500_NUM_IRQ_REGS] = { + 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, +}; + +/* AB9540 / AB8505 support */ +static const int ab9540_irq_regoffset[AB9540_NUM_IRQ_REGS] = { + 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23 +}; + +/* AB8540 support */ +static const int ab8540_irq_regoffset[AB8540_NUM_IRQ_REGS] = { + 0, 1, 2, 3, 4, -1, -1, -1, -1, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, + 23, 25, 26, 27, 28, 29, 30, 31, +}; + +static const char ab8500_version_str[][7] = { + [AB8500_VERSION_AB8500] = "AB8500", + [AB8500_VERSION_AB8505] = "AB8505", + [AB8500_VERSION_AB9540] = "AB9540", + [AB8500_VERSION_AB8540] = "AB8540", +}; + +static int ab8500_prcmu_write(struct ab8500 *ab8500, u16 addr, u8 data) +{ + int ret; + + ret = prcmu_abb_write((u8)(addr >> 8), (u8)(addr & 0xFF), &data, 1); + if (ret < 0) + dev_err(ab8500->dev, "prcmu i2c error %d\n", ret); + return ret; +} + +static int ab8500_prcmu_write_masked(struct ab8500 *ab8500, u16 addr, u8 mask, + u8 data) +{ + int ret; + + ret = prcmu_abb_write_masked((u8)(addr >> 8), (u8)(addr & 0xFF), &data, + &mask, 1); + if (ret < 0) + dev_err(ab8500->dev, "prcmu i2c error %d\n", ret); + return ret; +} + +static int ab8500_prcmu_read(struct ab8500 *ab8500, u16 addr) +{ + int ret; + u8 data; + + ret = prcmu_abb_read((u8)(addr >> 8), (u8)(addr & 0xFF), &data, 1); + if (ret < 0) { + dev_err(ab8500->dev, "prcmu i2c error %d\n", ret); + return ret; + } + return (int)data; +} + +static int ab8500_get_chip_id(struct device *dev) +{ + struct ab8500 *ab8500; + + if (!dev) + return -EINVAL; + ab8500 = dev_get_drvdata(dev->parent); + return ab8500 ? (int)ab8500->chip_id : -EINVAL; +} + +static int set_register_interruptible(struct ab8500 *ab8500, u8 bank, + u8 reg, u8 data) +{ + int ret; + /* + * Put the u8 bank and u8 register together into a an u16. + * The bank on higher 8 bits and register in lower 8 bits. + * */ + u16 addr = ((u16)bank) << 8 | reg; + + dev_vdbg(ab8500->dev, "wr: addr %#x <= %#x\n", addr, data); + + mutex_lock(&ab8500->lock); + + ret = ab8500->write(ab8500, addr, data); + if (ret < 0) + dev_err(ab8500->dev, "failed to write reg %#x: %d\n", + addr, ret); + mutex_unlock(&ab8500->lock); + + return ret; +} + +static int ab8500_set_register(struct device *dev, u8 bank, + u8 reg, u8 value) +{ + int ret; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); + + atomic_inc(&ab8500->transfer_ongoing); + ret = set_register_interruptible(ab8500, bank, reg, value); + atomic_dec(&ab8500->transfer_ongoing); + return ret; +} + +static int get_register_interruptible(struct ab8500 *ab8500, u8 bank, + u8 reg, u8 *value) +{ + int ret; + /* put the u8 bank and u8 reg together into a an u16. + * bank on higher 8 bits and reg in lower */ + u16 addr = ((u16)bank) << 8 | reg; + + mutex_lock(&ab8500->lock); + + ret = ab8500->read(ab8500, addr); + if (ret < 0) + dev_err(ab8500->dev, "failed to read reg %#x: %d\n", + addr, ret); + else + *value = ret; + + mutex_unlock(&ab8500->lock); + dev_vdbg(ab8500->dev, "rd: addr %#x => data %#x\n", addr, ret); + + return ret; +} + +static int ab8500_get_register(struct device *dev, u8 bank, + u8 reg, u8 *value) +{ + int ret; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); + + atomic_inc(&ab8500->transfer_ongoing); + ret = get_register_interruptible(ab8500, bank, reg, value); + atomic_dec(&ab8500->transfer_ongoing); + return ret; +} + +static int mask_and_set_register_interruptible(struct ab8500 *ab8500, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + int ret; + /* put the u8 bank and u8 reg together into a an u16. + * bank on higher 8 bits and reg in lower */ + u16 addr = ((u16)bank) << 8 | reg; + + mutex_lock(&ab8500->lock); + + if (ab8500->write_masked == NULL) { + u8 data; + + ret = ab8500->read(ab8500, addr); + if (ret < 0) { + dev_err(ab8500->dev, "failed to read reg %#x: %d\n", + addr, ret); + goto out; + } + + data = (u8)ret; + data = (~bitmask & data) | (bitmask & bitvalues); + + ret = ab8500->write(ab8500, addr, data); + if (ret < 0) + dev_err(ab8500->dev, "failed to write reg %#x: %d\n", + addr, ret); + + dev_vdbg(ab8500->dev, "mask: addr %#x => data %#x\n", addr, + data); + goto out; + } + ret = ab8500->write_masked(ab8500, addr, bitmask, bitvalues); + if (ret < 0) + dev_err(ab8500->dev, "failed to modify reg %#x: %d\n", addr, + ret); +out: + mutex_unlock(&ab8500->lock); + return ret; +} + +static int ab8500_mask_and_set_register(struct device *dev, + u8 bank, u8 reg, u8 bitmask, u8 bitvalues) +{ + int ret; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); + + atomic_inc(&ab8500->transfer_ongoing); + ret = mask_and_set_register_interruptible(ab8500, bank, reg, + bitmask, bitvalues); + atomic_dec(&ab8500->transfer_ongoing); + return ret; +} + +static struct abx500_ops ab8500_ops = { + .get_chip_id = ab8500_get_chip_id, + .get_register = ab8500_get_register, + .set_register = ab8500_set_register, + .get_register_page = NULL, + .set_register_page = NULL, + .mask_and_set_register = ab8500_mask_and_set_register, + .event_registers_startup_state_get = NULL, + .startup_irq_enabled = NULL, + .dump_all_banks = ab8500_dump_all_banks, +}; + +static void ab8500_irq_lock(struct irq_data *data) +{ + struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data); + + mutex_lock(&ab8500->irq_lock); + atomic_inc(&ab8500->transfer_ongoing); +} + +static void ab8500_irq_sync_unlock(struct irq_data *data) +{ + struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ab8500->mask_size; i++) { + u8 old = ab8500->oldmask[i]; + u8 new = ab8500->mask[i]; + int reg; + + if (new == old) + continue; + + /* + * Interrupt register 12 doesn't exist prior to AB8500 version + * 2.0 + */ + if (ab8500->irq_reg_offset[i] == 11 && + is_ab8500_1p1_or_earlier(ab8500)) + continue; + + if (ab8500->irq_reg_offset[i] < 0) + continue; + + ab8500->oldmask[i] = new; + + reg = AB8500_IT_MASK1_REG + ab8500->irq_reg_offset[i]; + set_register_interruptible(ab8500, AB8500_INTERRUPT, reg, new); + } + atomic_dec(&ab8500->transfer_ongoing); + mutex_unlock(&ab8500->irq_lock); +} + +static void ab8500_irq_mask(struct irq_data *data) +{ + struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data); + int offset = data->hwirq; + int index = offset / 8; + int mask = 1 << (offset % 8); + + ab8500->mask[index] |= mask; + + /* The AB8500 GPIOs have two interrupts each (rising & falling). */ + if (offset >= AB8500_INT_GPIO6R && offset <= AB8500_INT_GPIO41R) + ab8500->mask[index + 2] |= mask; + if (offset >= AB9540_INT_GPIO50R && offset <= AB9540_INT_GPIO54R) + ab8500->mask[index + 1] |= mask; + if (offset == AB8540_INT_GPIO43R || offset == AB8540_INT_GPIO44R) + /* Here the falling IRQ is one bit lower */ + ab8500->mask[index] |= (mask << 1); +} + +static void ab8500_irq_unmask(struct irq_data *data) +{ + struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data); + unsigned int type = irqd_get_trigger_type(data); + int offset = data->hwirq; + int index = offset / 8; + int mask = 1 << (offset % 8); + + if (type & IRQ_TYPE_EDGE_RISING) + ab8500->mask[index] &= ~mask; + + /* The AB8500 GPIOs have two interrupts each (rising & falling). */ + if (type & IRQ_TYPE_EDGE_FALLING) { + if (offset >= AB8500_INT_GPIO6R && offset <= AB8500_INT_GPIO41R) + ab8500->mask[index + 2] &= ~mask; + else if (offset >= AB9540_INT_GPIO50R && + offset <= AB9540_INT_GPIO54R) + ab8500->mask[index + 1] &= ~mask; + else if (offset == AB8540_INT_GPIO43R || + offset == AB8540_INT_GPIO44R) + /* Here the falling IRQ is one bit lower */ + ab8500->mask[index] &= ~(mask << 1); + else + ab8500->mask[index] &= ~mask; + } else { + /* Satisfies the case where type is not set. */ + ab8500->mask[index] &= ~mask; + } +} + +static int ab8500_irq_set_type(struct irq_data *data, unsigned int type) +{ + return 0; +} + +static struct irq_chip ab8500_irq_chip = { + .name = "ab8500", + .irq_bus_lock = ab8500_irq_lock, + .irq_bus_sync_unlock = ab8500_irq_sync_unlock, + .irq_mask = ab8500_irq_mask, + .irq_disable = ab8500_irq_mask, + .irq_unmask = ab8500_irq_unmask, + .irq_set_type = ab8500_irq_set_type, +}; + +static void update_latch_offset(u8 *offset, int i) +{ + /* Fix inconsistent ITFromLatch25 bit mapping... */ + if (unlikely(*offset == 17)) + *offset = 24; + /* Fix inconsistent ab8540 bit mapping... */ + if (unlikely(*offset == 16)) + *offset = 25; + if ((i == 3) && (*offset >= 24)) + *offset += 2; +} + +static int ab8500_handle_hierarchical_line(struct ab8500 *ab8500, + int latch_offset, u8 latch_val) +{ + int int_bit, line, i; + + for (i = 0; i < ab8500->mask_size; i++) + if (ab8500->irq_reg_offset[i] == latch_offset) + break; + + if (i >= ab8500->mask_size) { + dev_err(ab8500->dev, "Register offset 0x%2x not declared\n", + latch_offset); + return -ENXIO; + } + + /* ignore masked out interrupts */ + latch_val &= ~ab8500->mask[i]; + + while (latch_val) { + int_bit = __ffs(latch_val); + line = (i << 3) + int_bit; + latch_val &= ~(1 << int_bit); + + /* + * This handles the falling edge hwirqs from the GPIO + * lines. Route them back to the line registered for the + * rising IRQ, as this is merely a flag for the same IRQ + * in linux terms. + */ + if (line >= AB8500_INT_GPIO6F && line <= AB8500_INT_GPIO41F) + line -= 16; + if (line >= AB9540_INT_GPIO50F && line <= AB9540_INT_GPIO54F) + line -= 8; + if (line == AB8540_INT_GPIO43F || line == AB8540_INT_GPIO44F) + line += 1; + + handle_nested_irq(irq_create_mapping(ab8500->domain, line)); + } + + return 0; +} + +static int ab8500_handle_hierarchical_latch(struct ab8500 *ab8500, + int hier_offset, u8 hier_val) +{ + int latch_bit, status; + u8 latch_offset, latch_val; + + do { + latch_bit = __ffs(hier_val); + latch_offset = (hier_offset << 3) + latch_bit; + + update_latch_offset(&latch_offset, hier_offset); + + status = get_register_interruptible(ab8500, + AB8500_INTERRUPT, + AB8500_IT_LATCH1_REG + latch_offset, + &latch_val); + if (status < 0 || latch_val == 0) + goto discard; + + status = ab8500_handle_hierarchical_line(ab8500, + latch_offset, latch_val); + if (status < 0) + return status; +discard: + hier_val &= ~(1 << latch_bit); + } while (hier_val); + + return 0; +} + +static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev) +{ + struct ab8500 *ab8500 = dev; + u8 i; + + dev_vdbg(ab8500->dev, "interrupt\n"); + + /* Hierarchical interrupt version */ + for (i = 0; i < (ab8500->it_latchhier_num); i++) { + int status; + u8 hier_val; + + status = get_register_interruptible(ab8500, AB8500_INTERRUPT, + AB8500_IT_LATCHHIER1_REG + i, &hier_val); + if (status < 0 || hier_val == 0) + continue; + + status = ab8500_handle_hierarchical_latch(ab8500, i, hier_val); + if (status < 0) + break; + } + return IRQ_HANDLED; +} + +static int ab8500_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct ab8500 *ab8500 = d->host_data; + + if (!ab8500) + return -EINVAL; + + irq_set_chip_data(virq, ab8500); + irq_set_chip_and_handler(virq, &ab8500_irq_chip, + handle_simple_irq); + irq_set_nested_thread(virq, 1); +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static struct irq_domain_ops ab8500_irq_ops = { + .map = ab8500_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int ab8500_irq_init(struct ab8500 *ab8500, struct device_node *np) +{ + int num_irqs; + + if (is_ab8540(ab8500)) + num_irqs = AB8540_NR_IRQS; + else if (is_ab9540(ab8500)) + num_irqs = AB9540_NR_IRQS; + else if (is_ab8505(ab8500)) + num_irqs = AB8505_NR_IRQS; + else + num_irqs = AB8500_NR_IRQS; + + /* If ->irq_base is zero this will give a linear mapping */ + ab8500->domain = irq_domain_add_simple(ab8500->dev->of_node, + num_irqs, 0, + &ab8500_irq_ops, ab8500); + + if (!ab8500->domain) { + dev_err(ab8500->dev, "Failed to create irqdomain\n"); + return -ENOSYS; + } + + return 0; +} + +int ab8500_suspend(struct ab8500 *ab8500) +{ + if (atomic_read(&ab8500->transfer_ongoing)) + return -EINVAL; + + return 0; +} + +static struct resource ab8500_gpadc_resources[] = { + { + .name = "HW_CONV_END", + .start = AB8500_INT_GP_HW_ADC_CONV_END, + .end = AB8500_INT_GP_HW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, + { + .name = "SW_CONV_END", + .start = AB8500_INT_GP_SW_ADC_CONV_END, + .end = AB8500_INT_GP_SW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8505_gpadc_resources[] = { + { + .name = "SW_CONV_END", + .start = AB8500_INT_GP_SW_ADC_CONV_END, + .end = AB8500_INT_GP_SW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_rtc_resources[] = { + { + .name = "60S", + .start = AB8500_INT_RTC_60S, + .end = AB8500_INT_RTC_60S, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALARM", + .start = AB8500_INT_RTC_ALARM, + .end = AB8500_INT_RTC_ALARM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8540_rtc_resources[] = { + { + .name = "1S", + .start = AB8540_INT_RTC_1S, + .end = AB8540_INT_RTC_1S, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALARM", + .start = AB8500_INT_RTC_ALARM, + .end = AB8500_INT_RTC_ALARM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_poweronkey_db_resources[] = { + { + .name = "ONKEY_DBF", + .start = AB8500_INT_PON_KEY1DB_F, + .end = AB8500_INT_PON_KEY1DB_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ONKEY_DBR", + .start = AB8500_INT_PON_KEY1DB_R, + .end = AB8500_INT_PON_KEY1DB_R, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_av_acc_detect_resources[] = { + { + .name = "ACC_DETECT_1DB_F", + .start = AB8500_INT_ACC_DETECT_1DB_F, + .end = AB8500_INT_ACC_DETECT_1DB_F, + .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 ab8500_charger_resources[] = { + { + .name = "MAIN_CH_UNPLUG_DET", + .start = AB8500_INT_MAIN_CH_UNPLUG_DET, + .end = AB8500_INT_MAIN_CH_UNPLUG_DET, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MAIN_CHARGE_PLUG_DET", + .start = AB8500_INT_MAIN_CH_PLUG_DET, + .end = AB8500_INT_MAIN_CH_PLUG_DET, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_DET_R", + .start = AB8500_INT_VBUS_DET_R, + .end = AB8500_INT_VBUS_DET_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_DET_F", + .start = AB8500_INT_VBUS_DET_F, + .end = AB8500_INT_VBUS_DET_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB_LINK_STATUS", + .start = AB8500_INT_USB_LINK_STATUS, + .end = AB8500_INT_USB_LINK_STATUS, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_OVV", + .start = AB8500_INT_VBUS_OVV, + .end = AB8500_INT_VBUS_OVV, + .flags = IORESOURCE_IRQ, + }, + { + .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 = "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 = "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_OKR, + .end = AB8500_INT_USB_CHARGER_NOT_OKR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "CH_WD_EXP", + .start = AB8500_INT_CH_WD_EXP, + .end = AB8500_INT_CH_WD_EXP, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_CH_DROP_END", + .start = AB8500_INT_VBUS_CH_DROP_END, + .end = AB8500_INT_VBUS_CH_DROP_END, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_btemp_resources[] = { + { + .name = "BAT_CTRL_INDB", + .start = AB8500_INT_BAT_CTRL_INDB, + .end = AB8500_INT_BAT_CTRL_INDB, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BTEMP_LOW", + .start = AB8500_INT_BTEMP_LOW, + .end = AB8500_INT_BTEMP_LOW, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BTEMP_HIGH", + .start = AB8500_INT_BTEMP_HIGH, + .end = AB8500_INT_BTEMP_HIGH, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BTEMP_LOW_MEDIUM", + .start = AB8500_INT_BTEMP_LOW_MEDIUM, + .end = AB8500_INT_BTEMP_LOW_MEDIUM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BTEMP_MEDIUM_HIGH", + .start = AB8500_INT_BTEMP_MEDIUM_HIGH, + .end = AB8500_INT_BTEMP_MEDIUM_HIGH, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_fg_resources[] = { + { + .name = "NCONV_ACCU", + .start = AB8500_INT_CCN_CONV_ACC, + .end = AB8500_INT_CCN_CONV_ACC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT_OVV", + .start = AB8500_INT_BATT_OVV, + .end = AB8500_INT_BATT_OVV, + .flags = IORESOURCE_IRQ, + }, + { + .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, + }, + { + .name = "CCEOC", + .start = AB8500_INT_CCEOC, + .end = AB8500_INT_CCEOC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_chargalg_resources[] = {}; + +#ifdef CONFIG_DEBUG_FS +static struct resource ab8500_debug_resources[] = { + { + .name = "IRQ_AB8500", + /* + * Number will be filled in. NOTE: this is deliberately + * not flagged as an IRQ in ordet to avoid remapping using + * the irqdomain in the MFD core, so that this IRQ passes + * unremapped to the debug code. + */ + }, + { + .name = "IRQ_FIRST", + .start = AB8500_INT_MAIN_EXT_CH_NOT_OK, + .end = AB8500_INT_MAIN_EXT_CH_NOT_OK, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IRQ_LAST", + .start = AB8500_INT_XTAL32K_KO, + .end = AB8500_INT_XTAL32K_KO, + .flags = IORESOURCE_IRQ, + }, +}; +#endif + +static struct resource ab8500_usb_resources[] = { + { + .name = "ID_WAKEUP_R", + .start = AB8500_INT_ID_WAKEUP_R, + .end = AB8500_INT_ID_WAKEUP_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_WAKEUP_F", + .start = AB8500_INT_ID_WAKEUP_F, + .end = AB8500_INT_ID_WAKEUP_F, + .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 = "USB_LINK_STATUS", + .start = AB8500_INT_USB_LINK_STATUS, + .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 ab8505_iddet_resources[] = { + { + .name = "KeyDeglitch", + .start = AB8505_INT_KEYDEGLITCH, + .end = AB8505_INT_KEYDEGLITCH, + .flags = IORESOURCE_IRQ, + }, + { + .name = "KP", + .start = AB8505_INT_KP, + .end = AB8505_INT_KP, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IKP", + .start = AB8505_INT_IKP, + .end = AB8505_INT_IKP, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IKR", + .start = AB8505_INT_IKR, + .end = AB8505_INT_IKR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "KeyStuck", + .start = AB8505_INT_KEYSTUCK, + .end = AB8505_INT_KEYSTUCK, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_DET_R", + .start = AB8500_INT_VBUS_DET_R, + .end = AB8500_INT_VBUS_DET_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_DET_F", + .start = AB8500_INT_VBUS_DET_F, + .end = AB8500_INT_VBUS_DET_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_DET_PLUGR", + .start = AB8500_INT_ID_DET_PLUGR, + .end = AB8500_INT_ID_DET_PLUGR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_DET_PLUGF", + .start = AB8500_INT_ID_DET_PLUGF, + .end = AB8500_INT_ID_DET_PLUGF, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_temp_resources[] = { + { + .name = "ABX500_TEMP_WARM", + .start = AB8500_INT_TEMP_WARM, + .end = AB8500_INT_TEMP_WARM, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell ab8500_bm_devs[] = { + { + .name = "ab8500-charger", + .of_compatible = "stericsson,ab8500-charger", + .num_resources = ARRAY_SIZE(ab8500_charger_resources), + .resources = ab8500_charger_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-btemp", + .of_compatible = "stericsson,ab8500-btemp", + .num_resources = ARRAY_SIZE(ab8500_btemp_resources), + .resources = ab8500_btemp_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-fg", + .of_compatible = "stericsson,ab8500-fg", + .num_resources = ARRAY_SIZE(ab8500_fg_resources), + .resources = ab8500_fg_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-chargalg", + .of_compatible = "stericsson,ab8500-chargalg", + .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), + .resources = ab8500_chargalg_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, +}; + +static const struct mfd_cell ab8500_devs[] = { +#ifdef CONFIG_DEBUG_FS + { + .name = "ab8500-debug", + .of_compatible = "stericsson,ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", + .of_compatible = "stericsson,ab8500-sysctrl", + }, + { + .name = "ab8500-ext-regulator", + .of_compatible = "stericsson,ab8500-ext-regulator", + }, + { + .name = "ab8500-regulator", + .of_compatible = "stericsson,ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .of_compatible = "stericsson,ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), + .resources = ab8500_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .of_compatible = "stericsson,ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .of_compatible = "stericsson,ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + + .name = "ab8500-poweron-key", + .of_compatible = "stericsson,ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .of_compatible = "stericsson,ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-pwm", + .of_compatible = "stericsson,ab8500-pwm", + .id = 2, + }, + { + .name = "ab8500-pwm", + .of_compatible = "stericsson,ab8500-pwm", + .id = 3, + }, + { + .name = "ab8500-denc", + .of_compatible = "stericsson,ab8500-denc", + }, + { + .name = "pinctrl-ab8500", + .of_compatible = "stericsson,ab8500-gpio", + }, + { + .name = "abx500-temp", + .of_compatible = "stericsson,abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "ab8500-usb", + .of_compatible = "stericsson,ab8500-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab8500-codec", + .of_compatible = "stericsson,ab8500-codec", + }, +}; + +static const struct mfd_cell ab9540_devs[] = { +#ifdef CONFIG_DEBUG_FS + { + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", + }, + { + .name = "ab8500-ext-regulator", + }, + { + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .of_compatible = "stericsson,ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), + .resources = ab8500_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "pinctrl-ab9540", + .of_compatible = "stericsson,ab9540-gpio", + }, + { + .name = "ab9540-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab9540-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, + }, +}; + +/* Device list for ab8505 */ +static const struct mfd_cell ab8505_devs[] = { +#ifdef CONFIG_DEBUG_FS + { + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", + }, + { + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .of_compatible = "stericsson,ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8505_gpadc_resources), + .resources = ab8505_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "pinctrl-ab8505", + }, + { + .name = "ab8500-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab8500-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, + }, +}; + +static const struct mfd_cell ab8540_devs[] = { +#ifdef CONFIG_DEBUG_FS + { + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", + }, + { + .name = "ab8500-ext-regulator", + }, + { + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .of_compatible = "stericsson,ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8505_gpadc_resources), + .resources = ab8505_gpadc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "pinctrl-ab8540", + }, + { + .name = "ab8540-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab8540-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, + }, +}; + +static const struct mfd_cell ab8540_cut1_devs[] = { + { + .name = "ab8500-rtc", + .of_compatible = "stericsson,ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, +}; + +static const struct mfd_cell ab8540_cut2_devs[] = { + { + .name = "ab8540-rtc", + .of_compatible = "stericsson,ab8540-rtc", + .num_resources = ARRAY_SIZE(ab8540_rtc_resources), + .resources = ab8540_rtc_resources, + }, +}; + +static ssize_t show_chip_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + + return sprintf(buf, "%#x\n", ab8500 ? ab8500->chip_id : -EINVAL); +} + +/* + * ab8500 has switched off due to (SWITCH_OFF_STATUS): + * 0x01 Swoff bit programming + * 0x02 Thermal protection activation + * 0x04 Vbat lower then BattOk falling threshold + * 0x08 Watchdog expired + * 0x10 Non presence of 32kHz clock + * 0x20 Battery level lower than power on reset threshold + * 0x40 Power on key 1 pressed longer than 10 seconds + * 0x80 DB8500 thermal shutdown + */ +static ssize_t show_switch_off_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_RTC, + AB8500_SWITCH_OFF_STATUS, &value); + if (ret < 0) + return ret; + return sprintf(buf, "%#x\n", value); +} + +/* use mask and set to override the register turn_on_stat value */ +void ab8500_override_turn_on_stat(u8 mask, u8 set) +{ + spin_lock(&on_stat_lock); + turn_on_stat_mask = mask; + turn_on_stat_set = set; + spin_unlock(&on_stat_lock); +} + +/* + * ab8500 has turned on due to (TURN_ON_STATUS): + * 0x01 PORnVbat + * 0x02 PonKey1dbF + * 0x04 PonKey2dbF + * 0x08 RTCAlarm + * 0x10 MainChDet + * 0x20 VbusDet + * 0x40 UsbIDDetect + * 0x80 Reserved + */ +static ssize_t show_turn_on_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8500_TURN_ON_STATUS, &value); + if (ret < 0) + return ret; + + /* + * In L9540, turn_on_status register is not updated correctly if + * the device is rebooted with AC/USB charger connected. Due to + * this, the device boots android instead of entering into charge + * only mode. Read the AC/USB status register to detect the charger + * presence and update the turn on status manually. + */ + if (is_ab9540(ab8500)) { + spin_lock(&on_stat_lock); + value = (value & turn_on_stat_mask) | turn_on_stat_set; + spin_unlock(&on_stat_lock); + } + + return sprintf(buf, "%#x\n", value); +} + +static ssize_t show_turn_on_status_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8505_TURN_ON_STATUS_2, &value); + if (ret < 0) + return ret; + return sprintf(buf, "%#x\n", (value & 0x1)); +} + +static ssize_t show_ab9540_dbbrstn(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ab8500 *ab8500; + int ret; + u8 value; + + ab8500 = dev_get_drvdata(dev); + + ret = get_register_interruptible(ab8500, AB8500_REGU_CTRL2, + AB9540_MODEM_CTRL2_REG, &value); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + (value & AB9540_MODEM_CTRL2_SWDBBRSTN_BIT) ? 1 : 0); +} + +static ssize_t store_ab9540_dbbrstn(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ab8500 *ab8500; + int ret = count; + int err; + u8 bitvalues; + + ab8500 = dev_get_drvdata(dev); + + if (count > 0) { + switch (buf[0]) { + case '0': + bitvalues = 0; + break; + case '1': + bitvalues = AB9540_MODEM_CTRL2_SWDBBRSTN_BIT; + break; + default: + goto exit; + } + + err = mask_and_set_register_interruptible(ab8500, + AB8500_REGU_CTRL2, AB9540_MODEM_CTRL2_REG, + AB9540_MODEM_CTRL2_SWDBBRSTN_BIT, bitvalues); + if (err) + dev_info(ab8500->dev, + "Failed to set DBBRSTN %c, err %#x\n", + buf[0], err); + } + +exit: + return ret; +} + +static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); +static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL); +static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); +static DEVICE_ATTR(turn_on_status_2, S_IRUGO, show_turn_on_status_2, NULL); +static DEVICE_ATTR(dbbrstn, S_IRUGO | S_IWUSR, + show_ab9540_dbbrstn, store_ab9540_dbbrstn); + +static struct attribute *ab8500_sysfs_entries[] = { + &dev_attr_chip_id.attr, + &dev_attr_switch_off_status.attr, + &dev_attr_turn_on_status.attr, + NULL, +}; + +static struct attribute *ab8505_sysfs_entries[] = { + &dev_attr_turn_on_status_2.attr, + NULL, +}; + +static struct attribute *ab9540_sysfs_entries[] = { + &dev_attr_chip_id.attr, + &dev_attr_switch_off_status.attr, + &dev_attr_turn_on_status.attr, + &dev_attr_dbbrstn.attr, + NULL, +}; + +static struct attribute_group ab8500_attr_group = { + .attrs = ab8500_sysfs_entries, +}; + +static struct attribute_group ab8505_attr_group = { + .attrs = ab8505_sysfs_entries, +}; + +static struct attribute_group ab9540_attr_group = { + .attrs = ab9540_sysfs_entries, +}; + +static int ab8500_probe(struct platform_device *pdev) +{ + static const char *switch_off_status[] = { + "Swoff bit programming", + "Thermal protection activation", + "Vbat lower then BattOk falling threshold", + "Watchdog expired", + "Non presence of 32kHz clock", + "Battery level lower than power on reset threshold", + "Power on key 1 pressed longer than 10 seconds", + "DB8500 thermal shutdown"}; + static const char *turn_on_status[] = { + "Battery rising (Vbat)", + "Power On Key 1 dbF", + "Power On Key 2 dbF", + "RTC Alarm", + "Main Charger Detect", + "Vbus Detect (USB)", + "USB ID Detect", + "UART Factory Mode Detect"}; + struct ab8500_platform_data *plat = dev_get_platdata(&pdev->dev); + const struct platform_device_id *platid = platform_get_device_id(pdev); + enum ab8500_version version = AB8500_VERSION_UNDEFINED; + struct device_node *np = pdev->dev.of_node; + struct ab8500 *ab8500; + struct resource *resource; + int ret; + int i; + u8 value; + + ab8500 = devm_kzalloc(&pdev->dev, sizeof(*ab8500), GFP_KERNEL); + if (!ab8500) + return -ENOMEM; + + ab8500->dev = &pdev->dev; + + resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!resource) { + dev_err(&pdev->dev, "no IRQ resource\n"); + return -ENODEV; + } + + ab8500->irq = resource->start; + + ab8500->read = ab8500_prcmu_read; + ab8500->write = ab8500_prcmu_write; + ab8500->write_masked = ab8500_prcmu_write_masked; + + mutex_init(&ab8500->lock); + mutex_init(&ab8500->irq_lock); + atomic_set(&ab8500->transfer_ongoing, 0); + + platform_set_drvdata(pdev, ab8500); + + if (platid) + version = platid->driver_data; + + if (version != AB8500_VERSION_UNDEFINED) + ab8500->version = version; + else { + ret = get_register_interruptible(ab8500, AB8500_MISC, + AB8500_IC_NAME_REG, &value); + if (ret < 0) { + dev_err(&pdev->dev, "could not probe HW\n"); + return ret; + } + + ab8500->version = value; + } + + ret = get_register_interruptible(ab8500, AB8500_MISC, + AB8500_REV_REG, &value); + if (ret < 0) + return ret; + + ab8500->chip_id = value; + + dev_info(ab8500->dev, "detected chip, %s rev. %1x.%1x\n", + ab8500_version_str[ab8500->version], + ab8500->chip_id >> 4, + ab8500->chip_id & 0x0F); + + /* Configure AB8540 */ + if (is_ab8540(ab8500)) { + ab8500->mask_size = AB8540_NUM_IRQ_REGS; + ab8500->irq_reg_offset = ab8540_irq_regoffset; + ab8500->it_latchhier_num = AB8540_IT_LATCHHIER_NUM; + } /* Configure AB8500 or AB9540 IRQ */ + else if (is_ab9540(ab8500) || is_ab8505(ab8500)) { + ab8500->mask_size = AB9540_NUM_IRQ_REGS; + ab8500->irq_reg_offset = ab9540_irq_regoffset; + ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM; + } else { + ab8500->mask_size = AB8500_NUM_IRQ_REGS; + ab8500->irq_reg_offset = ab8500_irq_regoffset; + ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM; + } + ab8500->mask = devm_kzalloc(&pdev->dev, ab8500->mask_size, + GFP_KERNEL); + if (!ab8500->mask) + return -ENOMEM; + ab8500->oldmask = devm_kzalloc(&pdev->dev, ab8500->mask_size, + GFP_KERNEL); + if (!ab8500->oldmask) + return -ENOMEM; + + /* + * ab8500 has switched off due to (SWITCH_OFF_STATUS): + * 0x01 Swoff bit programming + * 0x02 Thermal protection activation + * 0x04 Vbat lower then BattOk falling threshold + * 0x08 Watchdog expired + * 0x10 Non presence of 32kHz clock + * 0x20 Battery level lower than power on reset threshold + * 0x40 Power on key 1 pressed longer than 10 seconds + * 0x80 DB8500 thermal shutdown + */ + + ret = get_register_interruptible(ab8500, AB8500_RTC, + AB8500_SWITCH_OFF_STATUS, &value); + if (ret < 0) + return ret; + dev_info(ab8500->dev, "switch off cause(s) (%#x): ", value); + + if (value) { + for (i = 0; i < ARRAY_SIZE(switch_off_status); i++) { + if (value & 1) + pr_cont(" \"%s\"", switch_off_status[i]); + value = value >> 1; + + } + pr_cont("\n"); + } else { + pr_cont(" None\n"); + } + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8500_TURN_ON_STATUS, &value); + if (ret < 0) + return ret; + dev_info(ab8500->dev, "turn on reason(s) (%#x): ", value); + + if (value) { + for (i = 0; i < ARRAY_SIZE(turn_on_status); i++) { + if (value & 1) + pr_cont("\"%s\" ", turn_on_status[i]); + value = value >> 1; + } + pr_cont("\n"); + } else { + pr_cont("None\n"); + } + + if (plat && plat->init) + plat->init(ab8500); + + if (is_ab9540(ab8500)) { + ret = get_register_interruptible(ab8500, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &value); + if (ret < 0) + return ret; + if ((value & VBUS_DET_DBNC1) && (value & VBUS_DET_DBNC100)) + ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, + AB8500_VBUS_DET); + } + + /* Clear and mask all interrupts */ + for (i = 0; i < ab8500->mask_size; i++) { + /* + * Interrupt register 12 doesn't exist prior to AB8500 version + * 2.0 + */ + if (ab8500->irq_reg_offset[i] == 11 && + is_ab8500_1p1_or_earlier(ab8500)) + continue; + + if (ab8500->irq_reg_offset[i] < 0) + continue; + + get_register_interruptible(ab8500, AB8500_INTERRUPT, + AB8500_IT_LATCH1_REG + ab8500->irq_reg_offset[i], + &value); + set_register_interruptible(ab8500, AB8500_INTERRUPT, + AB8500_IT_MASK1_REG + ab8500->irq_reg_offset[i], 0xff); + } + + ret = abx500_register_ops(ab8500->dev, &ab8500_ops); + if (ret) + return ret; + + for (i = 0; i < ab8500->mask_size; i++) + ab8500->mask[i] = ab8500->oldmask[i] = 0xff; + + ret = ab8500_irq_init(ab8500, np); + if (ret) + return ret; + + ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, + ab8500_hierarchical_irq, + IRQF_ONESHOT | IRQF_NO_SUSPEND, + "ab8500", ab8500); + if (ret) + return ret; + +#ifdef CONFIG_DEBUG_FS + /* Pass to debugfs */ + ab8500_debug_resources[0].start = ab8500->irq; + ab8500_debug_resources[0].end = ab8500->irq; +#endif + + if (is_ab9540(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs, + ARRAY_SIZE(ab9540_devs), NULL, + 0, ab8500->domain); + else if (is_ab8540(ab8500)) { + ret = mfd_add_devices(ab8500->dev, 0, ab8540_devs, + ARRAY_SIZE(ab8540_devs), NULL, + 0, ab8500->domain); + if (ret) + return ret; + + if (is_ab8540_1p2_or_earlier(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab8540_cut1_devs, + ARRAY_SIZE(ab8540_cut1_devs), NULL, + 0, ab8500->domain); + else /* ab8540 >= cut2 */ + ret = mfd_add_devices(ab8500->dev, 0, ab8540_cut2_devs, + ARRAY_SIZE(ab8540_cut2_devs), NULL, + 0, ab8500->domain); + } else if (is_ab8505(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab8505_devs, + ARRAY_SIZE(ab8505_devs), NULL, + 0, ab8500->domain); + else + ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs, + ARRAY_SIZE(ab8500_devs), NULL, + 0, ab8500->domain); + if (ret) + return ret; + + if (!no_bm) { + /* Add battery management devices */ + ret = mfd_add_devices(ab8500->dev, 0, ab8500_bm_devs, + ARRAY_SIZE(ab8500_bm_devs), NULL, + 0, ab8500->domain); + if (ret) + dev_err(ab8500->dev, "error adding bm devices\n"); + } + + if (((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500)) + ret = sysfs_create_group(&ab8500->dev->kobj, + &ab9540_attr_group); + else + ret = sysfs_create_group(&ab8500->dev->kobj, + &ab8500_attr_group); + + if ((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) + ret = sysfs_create_group(&ab8500->dev->kobj, + &ab8505_attr_group); + + if (ret) + dev_err(ab8500->dev, "error creating sysfs entries\n"); + + return ret; +} + +static int ab8500_remove(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = platform_get_drvdata(pdev); + + if (((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500)) + sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group); + else + sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group); + + if ((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) + sysfs_remove_group(&ab8500->dev->kobj, &ab8505_attr_group); + + mfd_remove_devices(ab8500->dev); + + return 0; +} + +static const struct platform_device_id ab8500_id[] = { + { "ab8500-core", AB8500_VERSION_AB8500 }, + { "ab8505-i2c", AB8500_VERSION_AB8505 }, + { "ab9540-i2c", AB8500_VERSION_AB9540 }, + { "ab8540-i2c", AB8500_VERSION_AB8540 }, + { } +}; + +static struct platform_driver ab8500_core_driver = { + .driver = { + .name = "ab8500-core", + }, + .probe = ab8500_probe, + .remove = ab8500_remove, + .id_table = ab8500_id, +}; + +static int __init ab8500_core_init(void) +{ + return platform_driver_register(&ab8500_core_driver); +} + +static void __exit ab8500_core_exit(void) +{ + platform_driver_unregister(&ab8500_core_driver); +} +core_initcall(ab8500_core_init); +module_exit(ab8500_core_exit); + +MODULE_AUTHOR("Mattias Wallin, Srinidhi Kasagar, Rabin Vincent"); +MODULE_DESCRIPTION("AB8500 MFD core"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/ab8500-debugfs.c b/kernel/drivers/mfd/ab8500-debugfs.c new file mode 100644 index 000000000..cdd6f3d63 --- /dev/null +++ b/kernel/drivers/mfd/ab8500-debugfs.c @@ -0,0 +1,3331 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson. + * License Terms: GNU General Public License v2 + */ +/* + * AB8500 register access + * ====================== + * + * read: + * # echo BANK > <debugfs>/ab8500/register-bank + * # echo ADDR > <debugfs>/ab8500/register-address + * # cat <debugfs>/ab8500/register-value + * + * write: + * # echo BANK > <debugfs>/ab8500/register-bank + * # echo ADDR > <debugfs>/ab8500/register-address + * # echo VALUE > <debugfs>/ab8500/register-value + * + * read all registers from a bank: + * # echo BANK > <debugfs>/ab8500/register-bank + * # cat <debugfs>/ab8500/all-bank-register + * + * BANK target AB8500 register bank + * ADDR target AB8500 register address + * VALUE decimal or 0x-prefixed hexadecimal + * + * + * User Space notification on AB8500 IRQ + * ===================================== + * + * Allows user space entity to be notified when target AB8500 IRQ occurs. + * When subscribed, a sysfs entry is created in ab8500.i2c platform device. + * One can pool this file to get target IRQ occurence information. + * + * subscribe to an AB8500 IRQ: + * # echo IRQ > <debugfs>/ab8500/irq-subscribe + * + * unsubscribe from an AB8500 IRQ: + * # echo IRQ > <debugfs>/ab8500/irq-unsubscribe + * + * + * AB8500 register formated read/write access + * ========================================== + * + * Read: read data, data>>SHIFT, data&=MASK, output data + * [0xABCDEF98] shift=12 mask=0xFFF => 0x00000CDE + * Write: read data, data &= ~(MASK<<SHIFT), data |= (VALUE<<SHIFT), write data + * [0xABCDEF98] shift=12 mask=0xFFF value=0x123 => [0xAB123F98] + * + * Usage: + * # echo "CMD [OPTIONS] BANK ADRESS [VALUE]" > $debugfs/ab8500/hwreg + * + * CMD read read access + * write write access + * + * BANK target reg bank + * ADDRESS target reg address + * VALUE (write) value to be updated + * + * OPTIONS + * -d|-dec (read) output in decimal + * -h|-hexa (read) output in 0x-hexa (default) + * -l|-w|-b 32bit (default), 16bit or 8bit reg access + * -m|-mask MASK 0x-hexa mask (default 0xFFFFFFFF) + * -s|-shift SHIFT bit shift value (read:left, write:right) + * -o|-offset OFFSET address offset to add to ADDRESS value + * + * Warning: bit shift operation is applied to bit-mask. + * Warning: bit shift direction depends on read or right command. + */ + +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/kobject.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> + +#ifdef CONFIG_DEBUG_FS +#include <linux/string.h> +#include <linux/ctype.h> +#endif + +static u32 debug_bank; +static u32 debug_address; + +static int irq_ab8500; +static int irq_first; +static int irq_last; +static u32 *irq_count; +static int num_irqs; + +static struct device_attribute **dev_attr; +static char **event_name; + +static u8 avg_sample = SAMPLE_16; +static u8 trig_edge = RISING_EDGE; +static u8 conv_type = ADC_SW; +static u8 trig_timer; + +/** + * struct ab8500_reg_range + * @first: the first address of the range + * @last: the last address of the range + * @perm: access permissions for the range + */ +struct ab8500_reg_range { + u8 first; + u8 last; + u8 perm; +}; + +/** + * struct ab8500_prcmu_ranges + * @num_ranges: the number of ranges in the list + * @bankid: bank identifier + * @range: the list of register ranges + */ +struct ab8500_prcmu_ranges { + u8 num_ranges; + u8 bankid; + const struct ab8500_reg_range *range; +}; + +/* hwreg- "mask" and "shift" entries ressources */ +struct hwreg_cfg { + u32 bank; /* target bank */ + unsigned long addr; /* target address */ + uint fmt; /* format */ + unsigned long mask; /* read/write mask, applied before any bit shift */ + long shift; /* bit shift (read:right shift, write:left shift */ +}; +/* fmt bit #0: 0=hexa, 1=dec */ +#define REG_FMT_DEC(c) ((c)->fmt & 0x1) +#define REG_FMT_HEX(c) (!REG_FMT_DEC(c)) + +static struct hwreg_cfg hwreg_cfg = { + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ +}; + +#define AB8500_NAME_STRING "ab8500" +#define AB8500_ADC_NAME_STRING "gpadc" +#define AB8500_NUM_BANKS 24 + +#define AB8500_REV_REG 0x80 + +static struct ab8500_prcmu_ranges *debug_ranges; + +static struct ab8500_prcmu_ranges ab8500_debug_ranges[AB8500_NUM_BANKS] = { + [0x0] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x17, + }, + { + .first = 0x30, + .last = 0x30, + }, + { + .first = 0x32, + .last = 0x33, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x03, + .last = 0x10, + }, + { + .first = 0x80, + .last = 0x84, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x22, + }, + { + .first = 0x40, + .last = 0x44, + }, + /* 0x80-0x8B is SIM registers and should + * not be accessed from here */ + }, + }, + [AB8500_USB] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x12, + }, + { + .first = 0x15, + .last = 0x17, + }, + { + .first = 0x19, + .last = 0x21, + }, + { + .first = 0x27, + .last = 0x2C, + }, + { + .first = 0x41, + .last = 0x41, + }, + { + .first = 0x45, + .last = 0x5B, + }, + { + .first = 0x5D, + .last = 0x5D, + }, + { + .first = 0x69, + .last = 0x69, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, + [AB8500_DBI] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [0x9] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + }, + { + .first = 0x05, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x40, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x44, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x55, + }, + { + .first = 0x80, + .last = 0x82, + }, + { + .first = 0xC0, + .last = 0xC2, + }, + { + .first = 0xf5, + .last = 0xf6, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x6F, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_RTC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0F, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x05, + }, + { + .first = 0x10, + .last = 0x15, + }, + { + .first = 0x20, + .last = 0x25, + }, + { + .first = 0x30, + .last = 0x35, + }, + { + .first = 0x40, + .last = 0x45, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x67, + }, + { + .first = 0x80, + .last = 0x80, + }, + }, + }, + [0x11] = { + .num_ranges = 0, + .range = NULL, + }, + [0x12] = { + .num_ranges = 0, + .range = NULL, + }, + [0x13] = { + .num_ranges = 0, + .range = NULL, + }, + [0x14] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x0F, + }, + }, + }, +}; + +static struct ab8500_prcmu_ranges ab8505_debug_ranges[AB8500_NUM_BANKS] = { + [0x0] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x52, + .last = 0x52, + }, + { + .first = 0x54, + .last = 0x57, + }, + { + .first = 0x80, + .last = 0x83, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x17, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x30, + .last = 0x30, + }, + { + .first = 0x32, + .last = 0x3A, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x03, + .last = 0x11, + }, + { + .first = 0x80, + .last = 0x86, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x08, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x30, + }, + { + .first = 0x40, + .last = 0x48, + }, + /* 0x80-0x8B is SIM registers and should + * not be accessed from here */ + }, + }, + [AB8500_USB] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x91, + .last = 0x94, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_DBI] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_RESERVED] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x02, + .last = 0x03, + }, + { + .first = 0x05, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x57, + }, + { + .first = 0x60, + .last = 0x60, + }, + { + .first = 0xA0, + .last = 0xA7, + }, + { + .first = 0xAF, + .last = 0xB2, + }, + { + .first = 0xC0, + .last = 0xC2, + }, + { + .first = 0xF5, + .last = 0xF5, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x83, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 11, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x07, + }, + { + .first = 0x09, + .last = 0x09, + }, + { + .first = 0x0B, + .last = 0x0C, + }, + { + .first = 0x12, + .last = 0x15, + }, + { + .first = 0x18, + .last = 0x18, + }, + /* Latch registers should not be read here */ + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x46, + .last = 0x49, + }, + { + .first = 0x4B, + .last = 0x4D, + }, + { + .first = 0x52, + .last = 0x55, + }, + { + .first = 0x58, + .last = 0x58, + }, + /* LatchHier registers should not be read here */ + }, + }, + [AB8500_RTC] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x14, + }, + { + .first = 0x16, + .last = 0x17, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x16, + }, + { + .first = 0x20, + .last = 0x26, + }, + { + .first = 0x30, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x46, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x6B, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x05, + .last = 0x05, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, + [AB8500_PROD_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_STE_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x15, + }, + }, + }, +}; + +static struct ab8500_prcmu_ranges ab8540_debug_ranges[AB8500_NUM_BANKS] = { + [AB8500_M_FSM_RANK] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + }, + }, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x57, + .last = 0x57, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x90, + .last = 0x90, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x10, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x32, + .last = 0x3C, + }, + { + .first = 0x40, + .last = 0x42, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x03, + .last = 0x15, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x80, + .last = 0x85, + }, + { + .first = 0x87, + .last = 0x88, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x08, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x2F, + }, + { + .first = 0x31, + .last = 0x3A, + }, + { + .first = 0x43, + .last = 0x44, + }, + { + .first = 0x48, + .last = 0x49, + }, + }, + }, + [AB8500_USB] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x91, + .last = 0x94, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 0, + .range = NULL + }, + [AB8500_DBI] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x11, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x30, + .last = 0x43, + }, + }, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_RESERVED] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x04, + .last = 0x06, + }, + { + .first = 0x09, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 10, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x02, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x57, + }, + { + .first = 0x60, + .last = 0x60, + }, + { + .first = 0x70, + .last = 0x70, + }, + { + .first = 0xA0, + .last = 0xA9, + }, + { + .first = 0xAF, + .last = 0xB2, + }, + { + .first = 0xC0, + .last = 0xC6, + }, + { + .first = 0xF5, + .last = 0xF5, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x9f, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x05, + }, + { + .first = 0x0B, + .last = 0x0D, + }, + { + .first = 0x12, + .last = 0x20, + }, + /* Latch registers should not be read here */ + { + .first = 0x40, + .last = 0x45, + }, + { + .first = 0x4B, + .last = 0x4D, + }, + { + .first = 0x52, + .last = 0x60, + }, + /* LatchHier registers should not be read here */ + }, + }, + [AB8500_RTC] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x0B, + .last = 0x18, + }, + { + .first = 0x20, + .last = 0x25, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x16, + }, + { + .first = 0x20, + .last = 0x26, + }, + { + .first = 0x30, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x49, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x6B, + }, + { + .first = 0x70, + .last = 0x74, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x06, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x21, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x0C, + }, + { + .first = 0x0E, + .last = 0x11, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, + [AB8500_PROD_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_STE_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x3F, + }, + }, + }, +}; + + +static irqreturn_t ab8500_debug_handler(int irq, void *data) +{ + char buf[16]; + struct kobject *kobj = (struct kobject *)data; + unsigned int irq_abb = irq - irq_first; + + if (irq_abb < num_irqs) + irq_count[irq_abb]++; + /* + * This makes it possible to use poll for events (POLLPRI | POLLERR) + * from userspace on sysfs file named <irq-nr> + */ + sprintf(buf, "%d", irq); + sysfs_notify(kobj, NULL, buf); + + return IRQ_HANDLED; +} + +/* Prints to seq_file or log_buf */ +static int ab8500_registers_print(struct device *dev, u32 bank, + struct seq_file *s) +{ + unsigned int i; + + for (i = 0; i < debug_ranges[bank].num_ranges; i++) { + u32 reg; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + int err; + + err = abx500_get_register_interruptible(dev, + (u8)bank, (u8)reg, &value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + + if (s) { + seq_printf(s, " [0x%02X/0x%02X]: 0x%02X\n", + bank, reg, value); + /* Error is not returned here since + * the output is wanted in any case */ + if (seq_has_overflowed(s)) + return 0; + } else { + dev_info(dev, " [0x%02X/0x%02X]: 0x%02X\n", + bank, reg, value); + } + } + } + + return 0; +} + +static int ab8500_print_bank_registers(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + u32 bank = debug_bank; + + seq_puts(s, AB8500_NAME_STRING " register values:\n"); + + seq_printf(s, " bank 0x%02X:\n", bank); + + return ab8500_registers_print(dev, bank, s); +} + +static int ab8500_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_print_bank_registers, inode->i_private); +} + +static const struct file_operations ab8500_registers_fops = { + .open = ab8500_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_print_all_banks(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + unsigned int i; + + seq_puts(s, AB8500_NAME_STRING " register values:\n"); + + for (i = 0; i < AB8500_NUM_BANKS; i++) { + int err; + + seq_printf(s, " bank 0x%02X:\n", i); + err = ab8500_registers_print(dev, i, s); + if (err) + return err; + } + return 0; +} + +/* Dump registers to kernel log */ +void ab8500_dump_all_banks(struct device *dev) +{ + unsigned int i; + + dev_info(dev, "ab8500 register values:\n"); + + for (i = 1; i < AB8500_NUM_BANKS; i++) { + dev_info(dev, " bank 0x%02X:\n", i); + ab8500_registers_print(dev, i, NULL); + } +} + +/* Space for 500 registers. */ +#define DUMP_MAX_REGS 700 +static struct ab8500_register_dump +{ + u8 bank; + u8 reg; + u8 value; +} ab8500_complete_register_dump[DUMP_MAX_REGS]; + +/* This shall only be called upon kernel panic! */ +void ab8500_dump_all_banks_to_mem(void) +{ + int i, r = 0; + u8 bank; + int err = 0; + + pr_info("Saving all ABB registers for crash analysis.\n"); + + for (bank = 0; bank < AB8500_NUM_BANKS; bank++) { + for (i = 0; i < debug_ranges[bank].num_ranges; i++) { + u8 reg; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = prcmu_abb_read(bank, reg, &value, 1); + + if (err < 0) + goto out; + + ab8500_complete_register_dump[r].bank = bank; + ab8500_complete_register_dump[r].reg = reg; + ab8500_complete_register_dump[r].value = value; + + r++; + + if (r >= DUMP_MAX_REGS) { + pr_err("%s: too many register to dump!\n", + __func__); + err = -EINVAL; + goto out; + } + } + } + } +out: + if (err >= 0) + pr_info("Saved all ABB registers.\n"); + else + pr_info("Failed to save all ABB registers.\n"); +} + +static int ab8500_all_banks_open(struct inode *inode, struct file *file) +{ + struct seq_file *s; + int err; + + err = single_open(file, ab8500_print_all_banks, inode->i_private); + if (!err) { + /* Default buf size in seq_read is not enough */ + s = (struct seq_file *)file->private_data; + s->size = (PAGE_SIZE * 2); + s->buf = kmalloc(s->size, GFP_KERNEL); + if (!s->buf) { + single_release(inode, file); + err = -ENOMEM; + } + } + return err; +} + +static const struct file_operations ab8500_all_banks_fops = { + .open = ab8500_all_banks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_bank_print(struct seq_file *s, void *p) +{ + seq_printf(s, "0x%02X\n", debug_bank); + return 0; +} + +static int ab8500_bank_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_bank_print, inode->i_private); +} + +static ssize_t ab8500_bank_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_bank; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_bank); + if (err) + return err; + + if (user_bank >= AB8500_NUM_BANKS) { + dev_err(dev, "debugfs error input > number of banks\n"); + return -EINVAL; + } + + debug_bank = user_bank; + + return count; +} + +static int ab8500_address_print(struct seq_file *s, void *p) +{ + seq_printf(s, "0x%02X\n", debug_address); + return 0; +} + +static int ab8500_address_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_address_print, inode->i_private); +} + +static ssize_t ab8500_address_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_address; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_address); + if (err) + return err; + + if (user_address > 0xff) { + dev_err(dev, "debugfs error input > 0xff\n"); + return -EINVAL; + } + debug_address = user_address; + + return count; +} + +static int ab8500_val_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int ret; + u8 regvalue; + + ret = abx500_get_register_interruptible(dev, + (u8)debug_bank, (u8)debug_address, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + seq_printf(s, "0x%02X\n", regvalue); + + return 0; +} + +static int ab8500_val_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_val_print, inode->i_private); +} + +static ssize_t ab8500_val_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_val; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_val); + if (err) + return err; + + if (user_val > 0xff) { + dev_err(dev, "debugfs error input > 0xff\n"); + return -EINVAL; + } + err = abx500_set_register_interruptible(dev, + (u8)debug_bank, debug_address, (u8)user_val); + if (err < 0) { + pr_err("abx500_set_reg failed %d, %d", err, __LINE__); + return -EINVAL; + } + + return count; +} + +/* + * Interrupt status + */ +static u32 num_interrupts[AB8500_MAX_NR_IRQS]; +static u32 num_wake_interrupts[AB8500_MAX_NR_IRQS]; +static int num_interrupt_lines; + +bool __attribute__((weak)) suspend_test_wake_cause_interrupt_is_mine(u32 my_int) +{ + return false; +} + +void ab8500_debug_register_interrupt(int line) +{ + if (line < num_interrupt_lines) { + num_interrupts[line]++; + if (suspend_test_wake_cause_interrupt_is_mine(irq_ab8500)) + num_wake_interrupts[line]++; + } +} + +static int ab8500_interrupts_print(struct seq_file *s, void *p) +{ + int line; + + seq_puts(s, "name: number: number of: wake:\n"); + + for (line = 0; line < num_interrupt_lines; line++) { + struct irq_desc *desc = irq_to_desc(line + irq_first); + + seq_printf(s, "%3i: %6i %4i", + line, + num_interrupts[line], + num_wake_interrupts[line]); + + if (desc && desc->name) + seq_printf(s, "-%-8s", desc->name); + if (desc && desc->action) { + struct irqaction *action = desc->action; + + seq_printf(s, " %s", action->name); + while ((action = action->next) != NULL) + seq_printf(s, ", %s", action->name); + } + seq_putc(s, '\n'); + } + + return 0; +} + +static int ab8500_interrupts_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_interrupts_print, inode->i_private); +} + +/* + * - HWREG DB8500 formated routines + */ +static int ab8500_hwreg_print(struct seq_file *s, void *d) +{ + struct device *dev = s->private; + int ret; + u8 regvalue; + + ret = abx500_get_register_interruptible(dev, + (u8)hwreg_cfg.bank, (u8)hwreg_cfg.addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (hwreg_cfg.shift >= 0) + regvalue >>= hwreg_cfg.shift; + else + regvalue <<= -hwreg_cfg.shift; + regvalue &= hwreg_cfg.mask; + + if (REG_FMT_DEC(&hwreg_cfg)) + seq_printf(s, "%d\n", regvalue); + else + seq_printf(s, "0x%02X\n", regvalue); + return 0; +} + +static int ab8500_hwreg_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_hwreg_print, inode->i_private); +} + +#define AB8500_SUPPLY_CONTROL_CONFIG_1 0x01 +#define AB8500_SUPPLY_CONTROL_REG 0x00 +#define AB8500_FIRST_SIM_REG 0x80 +#define AB8500_LAST_SIM_REG 0x8B +#define AB8505_LAST_SIM_REG 0x8C + +static int ab8500_print_modem_registers(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + struct ab8500 *ab8500; + int err; + u8 value; + u8 orig_value; + u32 bank = AB8500_REGU_CTRL2; + u32 last_sim_reg = AB8500_LAST_SIM_REG; + u32 reg; + + ab8500 = dev_get_drvdata(dev->parent); + dev_warn(dev, "WARNING! This operation can interfer with modem side\n" + "and should only be done with care\n"); + + err = abx500_get_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, &orig_value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + /* Config 1 will allow APE side to read SIM registers */ + err = abx500_set_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, + AB8500_SUPPLY_CONTROL_CONFIG_1); + if (err < 0) { + dev_err(dev, "ab->write fail %d\n", err); + return err; + } + + seq_printf(s, " bank 0x%02X:\n", bank); + + if (is_ab9540(ab8500) || is_ab8505(ab8500)) + last_sim_reg = AB8505_LAST_SIM_REG; + + for (reg = AB8500_FIRST_SIM_REG; reg <= last_sim_reg; reg++) { + err = abx500_get_register_interruptible(dev, + bank, reg, &value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + seq_printf(s, " [0x%02X/0x%02X]: 0x%02X\n", bank, reg, value); + } + err = abx500_set_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, orig_value); + if (err < 0) { + dev_err(dev, "ab->write fail %d\n", err); + return err; + } + return 0; +} + +static int ab8500_modem_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_print_modem_registers, + inode->i_private); +} + +static const struct file_operations ab8500_modem_fops = { + .open = ab8500_modem_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL, + avg_sample, trig_edge, trig_timer, conv_type); + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BAT_CTRL, bat_ctrl_raw); + + seq_printf(s, "%d,0x%X\n", bat_ctrl_convert, bat_ctrl_raw); + + return 0; +} + +static int ab8500_gpadc_bat_ctrl_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bat_ctrl_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bat_ctrl_fops = { + .open = ab8500_gpadc_bat_ctrl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_btemp_ball_print(struct seq_file *s, void *p) +{ + int btemp_ball_raw; + int btemp_ball_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL, + avg_sample, trig_edge, trig_timer, conv_type); + btemp_ball_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + btemp_ball_raw); + + seq_printf(s, "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); + + return 0; +} + +static int ab8500_gpadc_btemp_ball_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_btemp_ball_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_btemp_ball_fops = { + .open = ab8500_gpadc_btemp_ball_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_v_print(struct seq_file *s, void *p) +{ + int main_charger_v_raw; + int main_charger_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V, + avg_sample, trig_edge, trig_timer, conv_type); + main_charger_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_V, main_charger_v_raw); + + seq_printf(s, "%d,0x%X\n", main_charger_v_convert, main_charger_v_raw); + + return 0; +} + +static int ab8500_gpadc_main_charger_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_v_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_v_fops = { + .open = ab8500_gpadc_main_charger_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect1_print(struct seq_file *s, void *p) +{ + int acc_detect1_raw; + int acc_detect1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1, + avg_sample, trig_edge, trig_timer, conv_type); + acc_detect1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ACC_DETECT1, + acc_detect1_raw); + + seq_printf(s, "%d,0x%X\n", acc_detect1_convert, acc_detect1_raw); + + return 0; +} + +static int ab8500_gpadc_acc_detect1_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect1_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect1_fops = { + .open = ab8500_gpadc_acc_detect1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect2_print(struct seq_file *s, void *p) +{ + int acc_detect2_raw; + int acc_detect2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2, + avg_sample, trig_edge, trig_timer, conv_type); + acc_detect2_convert = ab8500_gpadc_ad_to_voltage(gpadc, + ACC_DETECT2, acc_detect2_raw); + + seq_printf(s, "%d,0x%X\n", acc_detect2_convert, acc_detect2_raw); + + return 0; +} + +static int ab8500_gpadc_acc_detect2_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect2_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect2_fops = { + .open = ab8500_gpadc_acc_detect2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux1_print(struct seq_file *s, void *p) +{ + int aux1_raw; + int aux1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1, + avg_sample, trig_edge, trig_timer, conv_type); + aux1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX1, + aux1_raw); + + seq_printf(s, "%d,0x%X\n", aux1_convert, aux1_raw); + + return 0; +} + +static int ab8500_gpadc_aux1_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux1_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux1_fops = { + .open = ab8500_gpadc_aux1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux2_print(struct seq_file *s, void *p) +{ + int aux2_raw; + int aux2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2, + avg_sample, trig_edge, trig_timer, conv_type); + aux2_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX2, + aux2_raw); + + seq_printf(s, "%d,0x%X\n", aux2_convert, aux2_raw); + + return 0; +} + +static int ab8500_gpadc_aux2_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux2_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux2_fops = { + .open = ab8500_gpadc_aux2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_bat_v_print(struct seq_file *s, void *p) +{ + int main_bat_v_raw; + int main_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V, + avg_sample, trig_edge, trig_timer, conv_type); + main_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + main_bat_v_raw); + + seq_printf(s, "%d,0x%X\n", main_bat_v_convert, main_bat_v_raw); + + return 0; +} + +static int ab8500_gpadc_main_bat_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_bat_v_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_bat_v_fops = { + .open = ab8500_gpadc_main_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_vbus_v_print(struct seq_file *s, void *p) +{ + int vbus_v_raw; + int vbus_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V, + avg_sample, trig_edge, trig_timer, conv_type); + vbus_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBUS_V, + vbus_v_raw); + + seq_printf(s, "%d,0x%X\n", vbus_v_convert, vbus_v_raw); + + return 0; +} + +static int ab8500_gpadc_vbus_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_vbus_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_vbus_v_fops = { + .open = ab8500_gpadc_vbus_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_c_print(struct seq_file *s, void *p) +{ + int main_charger_c_raw; + int main_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C, + avg_sample, trig_edge, trig_timer, conv_type); + main_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_C, main_charger_c_raw); + + seq_printf(s, "%d,0x%X\n", main_charger_c_convert, main_charger_c_raw); + + return 0; +} + +static int ab8500_gpadc_main_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_c_fops = { + .open = ab8500_gpadc_main_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_usb_charger_c_print(struct seq_file *s, void *p) +{ + int usb_charger_c_raw; + int usb_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C, + avg_sample, trig_edge, trig_timer, conv_type); + usb_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + USB_CHARGER_C, usb_charger_c_raw); + + seq_printf(s, "%d,0x%X\n", usb_charger_c_convert, usb_charger_c_raw); + + return 0; +} + +static int ab8500_gpadc_usb_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_charger_c_fops = { + .open = ab8500_gpadc_usb_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_bk_bat_v_print(struct seq_file *s, void *p) +{ + int bk_bat_v_raw; + int bk_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V, + avg_sample, trig_edge, trig_timer, conv_type); + bk_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BK_BAT_V, bk_bat_v_raw); + + seq_printf(s, "%d,0x%X\n", bk_bat_v_convert, bk_bat_v_raw); + + return 0; +} + +static int ab8500_gpadc_bk_bat_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bk_bat_v_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bk_bat_v_fops = { + .open = ab8500_gpadc_bk_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_die_temp_print(struct seq_file *s, void *p) +{ + int die_temp_raw; + int die_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP, + avg_sample, trig_edge, trig_timer, conv_type); + die_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, DIE_TEMP, + die_temp_raw); + + seq_printf(s, "%d,0x%X\n", die_temp_convert, die_temp_raw); + + return 0; +} + +static int ab8500_gpadc_die_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_die_temp_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_die_temp_fops = { + .open = ab8500_gpadc_die_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_usb_id_print(struct seq_file *s, void *p) +{ + int usb_id_raw; + int usb_id_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + usb_id_raw = ab8500_gpadc_read_raw(gpadc, USB_ID, + avg_sample, trig_edge, trig_timer, conv_type); + usb_id_convert = ab8500_gpadc_ad_to_voltage(gpadc, USB_ID, + usb_id_raw); + + seq_printf(s, "%d,0x%X\n", usb_id_convert, usb_id_raw); + + return 0; +} + +static int ab8500_gpadc_usb_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_id_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_id_fops = { + .open = ab8500_gpadc_usb_id_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_xtal_temp_print(struct seq_file *s, void *p) +{ + int xtal_temp_raw; + int xtal_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + xtal_temp_raw = ab8500_gpadc_read_raw(gpadc, XTAL_TEMP, + avg_sample, trig_edge, trig_timer, conv_type); + xtal_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, XTAL_TEMP, + xtal_temp_raw); + + seq_printf(s, "%d,0x%X\n", xtal_temp_convert, xtal_temp_raw); + + return 0; +} + +static int ab8540_gpadc_xtal_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8540_gpadc_xtal_temp_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_xtal_temp_fops = { + .open = ab8540_gpadc_xtal_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_true_meas_print(struct seq_file *s, void *p) +{ + int vbat_true_meas_raw; + int vbat_true_meas_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_true_meas_raw = ab8500_gpadc_read_raw(gpadc, VBAT_TRUE_MEAS, + avg_sample, trig_edge, trig_timer, conv_type); + vbat_true_meas_convert = + ab8500_gpadc_ad_to_voltage(gpadc, VBAT_TRUE_MEAS, + vbat_true_meas_raw); + + seq_printf(s, "%d,0x%X\n", vbat_true_meas_convert, vbat_true_meas_raw); + + return 0; +} + +static int ab8540_gpadc_vbat_true_meas_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_true_meas_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_true_meas_fops = { + .open = ab8540_gpadc_vbat_true_meas_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_bat_ctrl_and_ibat_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_ctrl_raw = ab8500_gpadc_double_read_raw(gpadc, BAT_CTRL_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, BAT_CTRL, + bat_ctrl_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + seq_printf(s, + "%d,0x%X\n" + "%d,0x%X\n", + bat_ctrl_convert, bat_ctrl_raw, + ibat_convert, ibat_raw); + + return 0; +} + +static int ab8540_gpadc_bat_ctrl_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_bat_ctrl_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_bat_ctrl_and_ibat_fops = { + .open = ab8540_gpadc_bat_ctrl_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_meas_and_ibat_print(struct seq_file *s, void *p) +{ + int vbat_meas_raw; + int vbat_meas_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_meas_raw = ab8500_gpadc_double_read_raw(gpadc, VBAT_MEAS_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + vbat_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + vbat_meas_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + seq_printf(s, + "%d,0x%X\n" + "%d,0x%X\n", + vbat_meas_convert, vbat_meas_raw, + ibat_convert, ibat_raw); + + return 0; +} + +static int ab8540_gpadc_vbat_meas_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_meas_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_meas_and_ibat_fops = { + .open = ab8540_gpadc_vbat_meas_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_true_meas_and_ibat_print(struct seq_file *s, + void *p) +{ + int vbat_true_meas_raw; + int vbat_true_meas_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_true_meas_raw = ab8500_gpadc_double_read_raw(gpadc, + VBAT_TRUE_MEAS_AND_IBAT, avg_sample, trig_edge, + trig_timer, conv_type, &ibat_raw); + vbat_true_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, + VBAT_TRUE_MEAS, vbat_true_meas_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + seq_printf(s, + "%d,0x%X\n" + "%d,0x%X\n", + vbat_true_meas_convert, vbat_true_meas_raw, + ibat_convert, ibat_raw); + + return 0; +} + +static int ab8540_gpadc_vbat_true_meas_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_true_meas_and_ibat_print, + inode->i_private); +} + +static const struct file_operations +ab8540_gpadc_vbat_true_meas_and_ibat_fops = { + .open = ab8540_gpadc_vbat_true_meas_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_bat_temp_and_ibat_print(struct seq_file *s, void *p) +{ + int bat_temp_raw; + int bat_temp_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_temp_raw = ab8500_gpadc_double_read_raw(gpadc, BAT_TEMP_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + bat_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + bat_temp_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + seq_printf(s, + "%d,0x%X\n" + "%d,0x%X\n", + bat_temp_convert, bat_temp_raw, + ibat_convert, ibat_raw); + + return 0; +} + +static int ab8540_gpadc_bat_temp_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_bat_temp_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_bat_temp_and_ibat_fops = { + .open = ab8540_gpadc_bat_temp_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_otp_cal_print(struct seq_file *s, void *p) +{ + struct ab8500_gpadc *gpadc; + u16 vmain_l, vmain_h, btemp_l, btemp_h; + u16 vbat_l, vbat_h, ibat_l, ibat_h; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + ab8540_gpadc_get_otp(gpadc, &vmain_l, &vmain_h, &btemp_l, &btemp_h, + &vbat_l, &vbat_h, &ibat_l, &ibat_h); + seq_printf(s, + "VMAIN_L:0x%X\n" + "VMAIN_H:0x%X\n" + "BTEMP_L:0x%X\n" + "BTEMP_H:0x%X\n" + "VBAT_L:0x%X\n" + "VBAT_H:0x%X\n" + "IBAT_L:0x%X\n" + "IBAT_H:0x%X\n", + vmain_l, vmain_h, btemp_l, btemp_h, + vbat_l, vbat_h, ibat_l, ibat_h); + + return 0; +} + +static int ab8540_gpadc_otp_cal_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8540_gpadc_otp_cal_print, inode->i_private); +} + +static const struct file_operations ab8540_gpadc_otp_calib_fops = { + .open = ab8540_gpadc_otp_cal_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_avg_sample_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", avg_sample); + + return 0; +} + +static int ab8500_gpadc_avg_sample_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_avg_sample_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_avg_sample_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_avg_sample; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_avg_sample); + if (err) + return err; + + if ((user_avg_sample == SAMPLE_1) || (user_avg_sample == SAMPLE_4) + || (user_avg_sample == SAMPLE_8) + || (user_avg_sample == SAMPLE_16)) { + avg_sample = (u8) user_avg_sample; + } else { + dev_err(dev, + "debugfs err input: should be egal to 1, 4, 8 or 16\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_avg_sample_fops = { + .open = ab8500_gpadc_avg_sample_open, + .read = seq_read, + .write = ab8500_gpadc_avg_sample_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_trig_edge_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", trig_edge); + + return 0; +} + +static int ab8500_gpadc_trig_edge_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_trig_edge_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_trig_edge_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_trig_edge; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_trig_edge); + if (err) + return err; + + if ((user_trig_edge == RISING_EDGE) + || (user_trig_edge == FALLING_EDGE)) { + trig_edge = (u8) user_trig_edge; + } else { + dev_err(dev, "Wrong input:\n" + "Enter 0. Rising edge\n" + "Enter 1. Falling edge\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_trig_edge_fops = { + .open = ab8500_gpadc_trig_edge_open, + .read = seq_read, + .write = ab8500_gpadc_trig_edge_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_trig_timer_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", trig_timer); + + return 0; +} + +static int ab8500_gpadc_trig_timer_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_trig_timer_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_trig_timer_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_trig_timer; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_trig_timer); + if (err) + return err; + + if (user_trig_timer & ~0xFF) { + dev_err(dev, + "debugfs error input: should be beetween 0 to 255\n"); + return -EINVAL; + } + + trig_timer = (u8) user_trig_timer; + + return count; +} + +static const struct file_operations ab8500_gpadc_trig_timer_fops = { + .open = ab8500_gpadc_trig_timer_open, + .read = seq_read, + .write = ab8500_gpadc_trig_timer_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_conv_type_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", conv_type); + + return 0; +} + +static int ab8500_gpadc_conv_type_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_conv_type_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_conv_type_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_conv_type; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_conv_type); + if (err) + return err; + + if ((user_conv_type == ADC_SW) + || (user_conv_type == ADC_HW)) { + conv_type = (u8) user_conv_type; + } else { + dev_err(dev, "Wrong input:\n" + "Enter 0. ADC SW conversion\n" + "Enter 1. ADC HW conversion\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_conv_type_fops = { + .open = ab8500_gpadc_conv_type_open, + .read = seq_read, + .write = ab8500_gpadc_conv_type_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* + * return length of an ASCII numerical value, 0 is string is not a + * numerical value. + * string shall start at value 1st char. + * string can be tailed with \0 or space or newline chars only. + * value can be decimal or hexadecimal (prefixed 0x or 0X). + */ +static int strval_len(char *b) +{ + char *s = b; + + if ((*s == '0') && ((*(s+1) == 'x') || (*(s+1) == 'X'))) { + s += 2; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isxdigit(*s)) + return 0; + } + } else { + if (*s == '-') + s++; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isdigit(*s)) + return 0; + } + } + return (int) (s-b); +} + +/* + * parse hwreg input data. + * update global hwreg_cfg only if input data syntax is ok. + */ +static ssize_t hwreg_common_write(char *b, struct hwreg_cfg *cfg, + struct device *dev) +{ + uint write, val = 0; + u8 regvalue; + int ret; + struct hwreg_cfg loc = { + .bank = 0, /* default: invalid phys addr */ + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ + }; + + /* read or write ? */ + if (!strncmp(b, "read ", 5)) { + write = 0; + b += 5; + } else if (!strncmp(b, "write ", 6)) { + write = 1; + b += 6; + } else + return -EINVAL; + + /* OPTIONS -l|-w|-b -s -m -o */ + while ((*b == ' ') || (*b == '-')) { + if (*(b-1) != ' ') { + b++; + continue; + } + if ((!strncmp(b, "-d ", 3)) || + (!strncmp(b, "-dec ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt |= (1<<0); + } else if ((!strncmp(b, "-h ", 3)) || + (!strncmp(b, "-hex ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt &= ~(1<<0); + } else if ((!strncmp(b, "-m ", 3)) || + (!strncmp(b, "-mask ", 6))) { + b += (*(b+2) == ' ') ? 3 : 6; + if (strval_len(b) == 0) + return -EINVAL; + ret = kstrtoul(b, 0, &loc.mask); + if (ret) + return ret; + } else if ((!strncmp(b, "-s ", 3)) || + (!strncmp(b, "-shift ", 7))) { + b += (*(b+2) == ' ') ? 3 : 7; + if (strval_len(b) == 0) + return -EINVAL; + ret = kstrtol(b, 0, &loc.shift); + if (ret) + return ret; + } else { + return -EINVAL; + } + } + /* get arg BANK and ADDRESS */ + if (strval_len(b) == 0) + return -EINVAL; + ret = kstrtouint(b, 0, &loc.bank); + if (ret) + return ret; + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + ret = kstrtoul(b, 0, &loc.addr); + if (ret) + return ret; + + if (write) { + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + ret = kstrtouint(b, 0, &val); + if (ret) + return ret; + } + + /* args are ok, update target cfg (mainly for read) */ + *cfg = loc; + +#ifdef ABB_HWREG_DEBUG + pr_warn("HWREG request: %s, %s,\n" + " addr=0x%08X, mask=0x%X, shift=%d" "value=0x%X\n", + (write) ? "write" : "read", + REG_FMT_DEC(cfg) ? "decimal" : "hexa", + cfg->addr, cfg->mask, cfg->shift, val); +#endif + + if (!write) + return 0; + + ret = abx500_get_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (cfg->shift >= 0) { + regvalue &= ~(cfg->mask << (cfg->shift)); + val = (val & cfg->mask) << (cfg->shift); + } else { + regvalue &= ~(cfg->mask >> (-cfg->shift)); + val = (val & cfg->mask) >> (-cfg->shift); + } + val = val | regvalue; + + ret = abx500_set_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, (u8)val); + if (ret < 0) { + pr_err("abx500_set_reg failed %d, %d", ret, __LINE__); + return -EINVAL; + } + + return 0; +} + +static ssize_t ab8500_hwreg_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[128]; + int buf_size, ret; + + /* 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; + + /* get args and process */ + ret = hwreg_common_write(buf, &hwreg_cfg, dev); + return (ret) ? ret : buf_size; +} + +/* + * - irq subscribe/unsubscribe stuff + */ +static int ab8500_subscribe_unsubscribe_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", irq_first); + + return 0; +} + +static int ab8500_subscribe_unsubscribe_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_subscribe_unsubscribe_print, + inode->i_private); +} + +/* + * Userspace should use poll() on this file. When an event occur + * the blocking poll will be released. + */ +static ssize_t show_irq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long name; + unsigned int irq_index; + int err; + + err = kstrtoul(attr->attr.name, 0, &name); + if (err) + return err; + + irq_index = name - irq_first; + if (irq_index >= num_irqs) + return -EINVAL; + + return sprintf(buf, "%u\n", irq_count[irq_index]); +} + +static ssize_t ab8500_subscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_val; + int err; + unsigned int irq_index; + + err = kstrtoul_from_user(user_buf, count, 0, &user_val); + if (err) + return err; + + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= num_irqs) + return -EINVAL; + + /* + * This will create a sysfs file named <irq-nr> which userspace can + * use to select or poll and get the AB8500 events + */ + dev_attr[irq_index] = kmalloc(sizeof(struct device_attribute), + GFP_KERNEL); + if (!dev_attr[irq_index]) + return -ENOMEM; + + event_name[irq_index] = kmalloc(count, GFP_KERNEL); + if (!event_name[irq_index]) + return -ENOMEM; + + sprintf(event_name[irq_index], "%lu", user_val); + dev_attr[irq_index]->show = show_irq; + dev_attr[irq_index]->store = NULL; + dev_attr[irq_index]->attr.name = event_name[irq_index]; + dev_attr[irq_index]->attr.mode = S_IRUGO; + err = sysfs_create_file(&dev->kobj, &dev_attr[irq_index]->attr); + if (err < 0) { + pr_info("sysfs_create_file failed %d\n", err); + return err; + } + + err = request_threaded_irq(user_val, NULL, ab8500_debug_handler, + IRQF_SHARED | IRQF_NO_SUSPEND, + "ab8500-debug", &dev->kobj); + if (err < 0) { + pr_info("request_threaded_irq failed %d, %lu\n", + err, user_val); + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + return err; + } + + return count; +} + +static ssize_t ab8500_unsubscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_val; + int err; + unsigned int irq_index; + + err = kstrtoul_from_user(user_buf, count, 0, &user_val); + if (err) + return err; + + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= num_irqs) + return -EINVAL; + + /* Set irq count to 0 when unsubscribe */ + irq_count[irq_index] = 0; + + if (dev_attr[irq_index]) + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + + + free_irq(user_val, &dev->kobj); + kfree(event_name[irq_index]); + kfree(dev_attr[irq_index]); + + return count; +} + +/* + * - several deubgfs nodes fops + */ + +static const struct file_operations ab8500_bank_fops = { + .open = ab8500_bank_open, + .write = ab8500_bank_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_address_fops = { + .open = ab8500_address_open, + .write = ab8500_address_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_val_fops = { + .open = ab8500_val_open, + .write = ab8500_val_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_interrupts_fops = { + .open = ab8500_interrupts_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_subscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_subscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_unsubscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_unsubscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_hwreg_fops = { + .open = ab8500_hwreg_open, + .write = ab8500_hwreg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab8500_dir; +static struct dentry *ab8500_gpadc_dir; + +static int ab8500_debug_probe(struct platform_device *plf) +{ + struct dentry *file; + struct ab8500 *ab8500; + struct resource *res; + + debug_bank = AB8500_MISC; + debug_address = AB8500_REV_REG & 0x00FF; + + ab8500 = dev_get_drvdata(plf->dev.parent); + num_irqs = ab8500->mask_size; + + irq_count = devm_kzalloc(&plf->dev, + sizeof(*irq_count)*num_irqs, GFP_KERNEL); + if (!irq_count) + return -ENOMEM; + + dev_attr = devm_kzalloc(&plf->dev, + sizeof(*dev_attr)*num_irqs, GFP_KERNEL); + if (!dev_attr) + return -ENOMEM; + + event_name = devm_kzalloc(&plf->dev, + sizeof(*event_name)*num_irqs, GFP_KERNEL); + if (!event_name) + return -ENOMEM; + + res = platform_get_resource_byname(plf, 0, "IRQ_AB8500"); + if (!res) { + dev_err(&plf->dev, "AB8500 irq not found, err %d\n", irq_first); + return -ENXIO; + } + irq_ab8500 = res->start; + + irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); + if (irq_first < 0) { + dev_err(&plf->dev, "First irq not found, err %d\n", irq_first); + return irq_first; + } + + irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); + if (irq_last < 0) { + dev_err(&plf->dev, "Last irq not found, err %d\n", irq_last); + return irq_last; + } + + ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); + if (!ab8500_dir) + goto err; + + ab8500_gpadc_dir = debugfs_create_dir(AB8500_ADC_NAME_STRING, + ab8500_dir); + if (!ab8500_gpadc_dir) + goto err; + + file = debugfs_create_file("all-bank-registers", S_IRUGO, ab8500_dir, + &plf->dev, &ab8500_registers_fops); + if (!file) + goto err; + + file = debugfs_create_file("all-banks", S_IRUGO, ab8500_dir, + &plf->dev, &ab8500_all_banks_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-bank", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_bank_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-address", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_address_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-value", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_val_fops); + if (!file) + goto err; + + file = debugfs_create_file("irq-subscribe", + (S_IRUGO | S_IWUSR | S_IWGRP), ab8500_dir, + &plf->dev, &ab8500_subscribe_fops); + if (!file) + goto err; + + if (is_ab8500(ab8500)) { + debug_ranges = ab8500_debug_ranges; + num_interrupt_lines = AB8500_NR_IRQS; + } else if (is_ab8505(ab8500)) { + debug_ranges = ab8505_debug_ranges; + num_interrupt_lines = AB8505_NR_IRQS; + } else if (is_ab9540(ab8500)) { + debug_ranges = ab8505_debug_ranges; + num_interrupt_lines = AB9540_NR_IRQS; + } else if (is_ab8540(ab8500)) { + debug_ranges = ab8540_debug_ranges; + num_interrupt_lines = AB8540_NR_IRQS; + } + + file = debugfs_create_file("interrupts", (S_IRUGO), ab8500_dir, + &plf->dev, &ab8500_interrupts_fops); + if (!file) + goto err; + + file = debugfs_create_file("irq-unsubscribe", + (S_IRUGO | S_IWUSR | S_IWGRP), ab8500_dir, + &plf->dev, &ab8500_unsubscribe_fops); + if (!file) + goto err; + + file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_hwreg_fops); + if (!file) + goto err; + + file = debugfs_create_file("all-modem-registers", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_modem_fops); + if (!file) + goto err; + + file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_bat_ctrl_fops); + if (!file) + goto err; + + file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, + &plf->dev, &ab8500_gpadc_btemp_ball_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_v", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_main_charger_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect1", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_acc_detect1_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect2", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_acc_detect2_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_aux1_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_aux2_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_main_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_vbus_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_c", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_main_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_charger_c", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, + &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_bk_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_die_temp_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_id", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_usb_id_fops); + if (!file) + goto err; + + if (is_ab8540(ab8500)) { + file = debugfs_create_file("xtal_temp", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8540_gpadc_xtal_temp_fops); + if (!file) + goto err; + file = debugfs_create_file("vbattruemeas", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8540_gpadc_vbat_true_meas_fops); + if (!file) + goto err; + file = debugfs_create_file("batctrl_and_ibat", + (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, + &plf->dev, + &ab8540_gpadc_bat_ctrl_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("vbatmeas_and_ibat", + (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, + &ab8540_gpadc_vbat_meas_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("vbattruemeas_and_ibat", + (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, + &plf->dev, + &ab8540_gpadc_vbat_true_meas_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("battemp_and_ibat", + (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, + &plf->dev, &ab8540_gpadc_bat_temp_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("otp_calib", + (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, + &plf->dev, &ab8540_gpadc_otp_calib_fops); + if (!file) + goto err; + } + file = debugfs_create_file("avg_sample", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_avg_sample_fops); + if (!file) + goto err; + + file = debugfs_create_file("trig_edge", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_trig_edge_fops); + if (!file) + goto err; + + file = debugfs_create_file("trig_timer", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_trig_timer_fops); + if (!file) + goto err; + + file = debugfs_create_file("conv_type", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8500_gpadc_conv_type_fops); + if (!file) + goto err; + + return 0; + +err: + debugfs_remove_recursive(ab8500_dir); + dev_err(&plf->dev, "failed to create debugfs entries.\n"); + + return -ENOMEM; +} + +static int ab8500_debug_remove(struct platform_device *plf) +{ + debugfs_remove_recursive(ab8500_dir); + + return 0; +} + +static struct platform_driver ab8500_debug_driver = { + .driver = { + .name = "ab8500-debug", + }, + .probe = ab8500_debug_probe, + .remove = ab8500_debug_remove +}; + +static int __init ab8500_debug_init(void) +{ + return platform_driver_register(&ab8500_debug_driver); +} + +static void __exit ab8500_debug_exit(void) +{ + platform_driver_unregister(&ab8500_debug_driver); +} +subsys_initcall(ab8500_debug_init); +module_exit(ab8500_debug_exit); + +MODULE_AUTHOR("Mattias WALLIN <mattias.wallin@stericsson.com"); +MODULE_DESCRIPTION("AB8500 DEBUG"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/ab8500-gpadc.c b/kernel/drivers/mfd/ab8500-gpadc.c new file mode 100644 index 000000000..dabbc93ab --- /dev/null +++ b/kernel/drivers/mfd/ab8500-gpadc.c @@ -0,0 +1,1087 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Arun R Murthy <arun.murthy@stericsson.com> + * Author: Daniel Willerud <daniel.willerud@stericsson.com> + * Author: Johan Palsson <johan.palsson@stericsson.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> + +/* + * GPADC register offsets + * Bank : 0x0A + */ +#define AB8500_GPADC_CTRL1_REG 0x00 +#define AB8500_GPADC_CTRL2_REG 0x01 +#define AB8500_GPADC_CTRL3_REG 0x02 +#define AB8500_GPADC_AUTO_TIMER_REG 0x03 +#define AB8500_GPADC_STAT_REG 0x04 +#define AB8500_GPADC_MANDATAL_REG 0x05 +#define AB8500_GPADC_MANDATAH_REG 0x06 +#define AB8500_GPADC_AUTODATAL_REG 0x07 +#define AB8500_GPADC_AUTODATAH_REG 0x08 +#define AB8500_GPADC_MUX_CTRL_REG 0x09 +#define AB8540_GPADC_MANDATA2L_REG 0x09 +#define AB8540_GPADC_MANDATA2H_REG 0x0A +#define AB8540_GPADC_APEAAX_REG 0x10 +#define AB8540_GPADC_APEAAT_REG 0x11 +#define AB8540_GPADC_APEAAM_REG 0x12 +#define AB8540_GPADC_APEAAH_REG 0x13 +#define AB8540_GPADC_APEAAL_REG 0x14 + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_GPADC_CAL_1 0x0F +#define AB8500_GPADC_CAL_2 0x10 +#define AB8500_GPADC_CAL_3 0x11 +#define AB8500_GPADC_CAL_4 0x12 +#define AB8500_GPADC_CAL_5 0x13 +#define AB8500_GPADC_CAL_6 0x14 +#define AB8500_GPADC_CAL_7 0x15 +/* New calibration for 8540 */ +#define AB8540_GPADC_OTP4_REG_7 0x38 +#define AB8540_GPADC_OTP4_REG_6 0x39 +#define AB8540_GPADC_OTP4_REG_5 0x3A + +/* gpadc constants */ +#define EN_VINTCORE12 0x04 +#define EN_VTVOUT 0x02 +#define EN_GPADC 0x01 +#define DIS_GPADC 0x00 +#define AVG_1 0x00 +#define AVG_4 0x20 +#define AVG_8 0x40 +#define AVG_16 0x60 +#define ADC_SW_CONV 0x04 +#define EN_ICHAR 0x80 +#define BTEMP_PULL_UP 0x08 +#define EN_BUF 0x40 +#define DIS_ZERO 0x00 +#define GPADC_BUSY 0x01 +#define EN_FALLING 0x10 +#define EN_TRIG_EDGE 0x02 +#define EN_VBIAS_XTAL_TEMP 0x02 + +/* GPADC constants from AB8500 spec, UM0836 */ +#define ADC_RESOLUTION 1024 +#define ADC_CH_BTEMP_MIN 0 +#define ADC_CH_BTEMP_MAX 1350 +#define ADC_CH_DIETEMP_MIN 0 +#define ADC_CH_DIETEMP_MAX 1350 +#define ADC_CH_CHG_V_MIN 0 +#define ADC_CH_CHG_V_MAX 20030 +#define ADC_CH_ACCDET2_MIN 0 +#define ADC_CH_ACCDET2_MAX 2500 +#define ADC_CH_VBAT_MIN 2300 +#define ADC_CH_VBAT_MAX 4800 +#define ADC_CH_CHG_I_MIN 0 +#define ADC_CH_CHG_I_MAX 1500 +#define ADC_CH_BKBAT_MIN 0 +#define ADC_CH_BKBAT_MAX 3200 + +/* GPADC constants from AB8540 spec */ +#define ADC_CH_IBAT_MIN (-6000) /* mA range measured by ADC for ibat*/ +#define ADC_CH_IBAT_MAX 6000 +#define ADC_CH_IBAT_MIN_V (-60) /* mV range measured by ADC for ibat*/ +#define ADC_CH_IBAT_MAX_V 60 +#define IBAT_VDROP_L (-56) /* mV */ +#define IBAT_VDROP_H 56 + +/* This is used to not lose precision when dividing to get gain and offset */ +#define CALIB_SCALE 1000 +/* + * Number of bits shift used to not lose precision + * when dividing to get ibat gain. + */ +#define CALIB_SHIFT_IBAT 20 + +/* Time in ms before disabling regulator */ +#define GPADC_AUDOSUSPEND_DELAY 1 + +#define CONVERSION_TIME 500 /* ms */ + +enum cal_channels { + ADC_INPUT_VMAIN = 0, + ADC_INPUT_BTEMP, + ADC_INPUT_VBAT, + ADC_INPUT_IBAT, + NBR_CAL_INPUTS, +}; + +/** + * struct adc_cal_data - Table for storing gain and offset for the calibrated + * ADC channels + * @gain: Gain of the ADC channel + * @offset: Offset of the ADC channel + */ +struct adc_cal_data { + s64 gain; + s64 offset; + u16 otp_calib_hi; + u16 otp_calib_lo; +}; + +/** + * struct ab8500_gpadc - AB8500 GPADC device information + * @dev: pointer to the struct device + * @node: a list of AB8500 GPADCs, hence prepared for + reentrance + * @parent: pointer to the struct ab8500 + * @ab8500_gpadc_complete: pointer to the struct completion, to indicate + * the completion of gpadc conversion + * @ab8500_gpadc_lock: structure of type mutex + * @regu: pointer to the struct regulator + * @irq_sw: interrupt number that is used by gpadc for Sw + * conversion + * @irq_hw: interrupt number that is used by gpadc for Hw + * conversion + * @cal_data array of ADC calibration data structs + */ +struct ab8500_gpadc { + struct device *dev; + struct list_head node; + struct ab8500 *parent; + struct completion ab8500_gpadc_complete; + struct mutex ab8500_gpadc_lock; + struct regulator *regu; + int irq_sw; + int irq_hw; + struct adc_cal_data cal_data[NBR_CAL_INPUTS]; +}; + +static LIST_HEAD(ab8500_gpadc_list); + +/** + * ab8500_gpadc_get() - returns a reference to the primary AB8500 GPADC + * (i.e. the first GPADC in the instance list) + */ +struct ab8500_gpadc *ab8500_gpadc_get(char *name) +{ + struct ab8500_gpadc *gpadc; + + list_for_each_entry(gpadc, &ab8500_gpadc_list, node) { + if (!strcmp(name, dev_name(gpadc->dev))) + return gpadc; + } + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL(ab8500_gpadc_get); + +/** + * ab8500_gpadc_ad_to_voltage() - Convert a raw ADC value to a voltage + */ +int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, + int ad_value) +{ + int res; + + switch (channel) { + case MAIN_CHARGER_V: + /* For some reason we don't have calibrated data */ + if (!gpadc->cal_data[ADC_INPUT_VMAIN].gain) { + res = ADC_CH_CHG_V_MIN + (ADC_CH_CHG_V_MAX - + ADC_CH_CHG_V_MIN) * ad_value / + ADC_RESOLUTION; + break; + } + /* Here we can use the calibrated data */ + res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_VMAIN].gain + + gpadc->cal_data[ADC_INPUT_VMAIN].offset) / CALIB_SCALE; + break; + + case XTAL_TEMP: + case BAT_CTRL: + case BTEMP_BALL: + case ACC_DETECT1: + case ADC_AUX1: + case ADC_AUX2: + /* For some reason we don't have calibrated data */ + if (!gpadc->cal_data[ADC_INPUT_BTEMP].gain) { + res = ADC_CH_BTEMP_MIN + (ADC_CH_BTEMP_MAX - + ADC_CH_BTEMP_MIN) * ad_value / + ADC_RESOLUTION; + break; + } + /* Here we can use the calibrated data */ + res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_BTEMP].gain + + gpadc->cal_data[ADC_INPUT_BTEMP].offset) / CALIB_SCALE; + break; + + case MAIN_BAT_V: + case VBAT_TRUE_MEAS: + /* For some reason we don't have calibrated data */ + if (!gpadc->cal_data[ADC_INPUT_VBAT].gain) { + res = ADC_CH_VBAT_MIN + (ADC_CH_VBAT_MAX - + ADC_CH_VBAT_MIN) * ad_value / + ADC_RESOLUTION; + break; + } + /* Here we can use the calibrated data */ + res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_VBAT].gain + + gpadc->cal_data[ADC_INPUT_VBAT].offset) / CALIB_SCALE; + break; + + case DIE_TEMP: + res = ADC_CH_DIETEMP_MIN + + (ADC_CH_DIETEMP_MAX - ADC_CH_DIETEMP_MIN) * ad_value / + ADC_RESOLUTION; + break; + + case ACC_DETECT2: + res = ADC_CH_ACCDET2_MIN + + (ADC_CH_ACCDET2_MAX - ADC_CH_ACCDET2_MIN) * ad_value / + ADC_RESOLUTION; + break; + + case VBUS_V: + res = ADC_CH_CHG_V_MIN + + (ADC_CH_CHG_V_MAX - ADC_CH_CHG_V_MIN) * ad_value / + ADC_RESOLUTION; + break; + + case MAIN_CHARGER_C: + case USB_CHARGER_C: + res = ADC_CH_CHG_I_MIN + + (ADC_CH_CHG_I_MAX - ADC_CH_CHG_I_MIN) * ad_value / + ADC_RESOLUTION; + break; + + case BK_BAT_V: + res = ADC_CH_BKBAT_MIN + + (ADC_CH_BKBAT_MAX - ADC_CH_BKBAT_MIN) * ad_value / + ADC_RESOLUTION; + break; + + case IBAT_VIRTUAL_CHANNEL: + /* For some reason we don't have calibrated data */ + if (!gpadc->cal_data[ADC_INPUT_IBAT].gain) { + res = ADC_CH_IBAT_MIN + (ADC_CH_IBAT_MAX - + ADC_CH_IBAT_MIN) * ad_value / + ADC_RESOLUTION; + break; + } + /* Here we can use the calibrated data */ + res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_IBAT].gain + + gpadc->cal_data[ADC_INPUT_IBAT].offset) + >> CALIB_SHIFT_IBAT; + break; + + default: + dev_err(gpadc->dev, + "unknown channel, not possible to convert\n"); + res = -EINVAL; + break; + + } + return res; +} +EXPORT_SYMBOL(ab8500_gpadc_ad_to_voltage); + +/** + * ab8500_gpadc_sw_hw_convert() - gpadc conversion + * @channel: analog channel to be converted to digital data + * @avg_sample: number of ADC sample to average + * @trig_egde: selected ADC trig edge + * @trig_timer: selected ADC trigger delay timer + * @conv_type: selected conversion type (HW or SW conversion) + * + * This function converts the selected analog i/p to digital + * data. + */ +int ab8500_gpadc_sw_hw_convert(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type) +{ + int ad_value; + int voltage; + + ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample, + trig_edge, trig_timer, conv_type); +/* On failure retry a second time */ + if (ad_value < 0) + ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample, + trig_edge, trig_timer, conv_type); +if (ad_value < 0) { + dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", + channel); + return ad_value; + } + + voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value); + if (voltage < 0) + dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:" + " %d AD: 0x%x\n", channel, ad_value); + + return voltage; +} +EXPORT_SYMBOL(ab8500_gpadc_sw_hw_convert); + +/** + * ab8500_gpadc_read_raw() - gpadc read + * @channel: analog channel to be read + * @avg_sample: number of ADC sample to average + * @trig_edge: selected trig edge + * @trig_timer: selected ADC trigger delay timer + * @conv_type: selected conversion type (HW or SW conversion) + * + * This function obtains the raw ADC value for an hardware conversion, + * this then needs to be converted by calling ab8500_gpadc_ad_to_voltage() + */ +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type) +{ + int raw_data; + raw_data = ab8500_gpadc_double_read_raw(gpadc, channel, + avg_sample, trig_edge, trig_timer, conv_type, NULL); + return raw_data; +} + +int ab8500_gpadc_double_read_raw(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type, + int *ibat) +{ + int ret; + int looplimit = 0; + unsigned long completion_timeout; + u8 val, low_data, high_data, low_data2, high_data2; + u8 val_reg1 = 0; + unsigned int delay_min = 0; + unsigned int delay_max = 0; + u8 data_low_addr, data_high_addr; + + if (!gpadc) + return -ENODEV; + + /* check if convertion is supported */ + if ((gpadc->irq_sw < 0) && (conv_type == ADC_SW)) + return -ENOTSUPP; + if ((gpadc->irq_hw < 0) && (conv_type == ADC_HW)) + return -ENOTSUPP; + + mutex_lock(&gpadc->ab8500_gpadc_lock); + /* Enable VTVout LDO this is required for GPADC */ + pm_runtime_get_sync(gpadc->dev); + + /* Check if ADC is not busy, lock and proceed */ + do { + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_STAT_REG, &val); + if (ret < 0) + goto out; + if (!(val & GPADC_BUSY)) + break; + msleep(10); + } while (++looplimit < 10); + if (looplimit >= 10 && (val & GPADC_BUSY)) { + dev_err(gpadc->dev, "gpadc_conversion: GPADC busy"); + ret = -EINVAL; + goto out; + } + + /* Enable GPADC */ + val_reg1 |= EN_GPADC; + + /* Select the channel source and set average samples */ + switch (avg_sample) { + case SAMPLE_1: + val = channel | AVG_1; + break; + case SAMPLE_4: + val = channel | AVG_4; + break; + case SAMPLE_8: + val = channel | AVG_8; + break; + default: + val = channel | AVG_16; + break; + } + + if (conv_type == ADC_HW) { + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL3_REG, val); + val_reg1 |= EN_TRIG_EDGE; + if (trig_edge) + val_reg1 |= EN_FALLING; + } + else + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL2_REG, val); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: set avg samples failed\n"); + goto out; + } + + /* + * Enable ADC, buffering, select rising edge and enable ADC path + * charging current sense if it needed, ABB 3.0 needs some special + * treatment too. + */ + switch (channel) { + case MAIN_CHARGER_C: + case USB_CHARGER_C: + val_reg1 |= EN_BUF | EN_ICHAR; + break; + case BTEMP_BALL: + if (!is_ab8500_2p0_or_earlier(gpadc->parent)) { + val_reg1 |= EN_BUF | BTEMP_PULL_UP; + /* + * Delay might be needed for ABB8500 cut 3.0, if not, + * remove when hardware will be availible + */ + delay_min = 1000; /* Delay in micro seconds */ + delay_max = 10000; /* large range to optimise sleep mode */ + break; + } + /* Intentional fallthrough */ + default: + val_reg1 |= EN_BUF; + break; + } + + /* Write configuration to register */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, val_reg1); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: set Control register failed\n"); + goto out; + } + + if (delay_min != 0) + usleep_range(delay_min, delay_max); + + if (conv_type == ADC_HW) { + /* Set trigger delay timer */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_AUTO_TIMER_REG, trig_timer); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: trig timer failed\n"); + goto out; + } + completion_timeout = 2 * HZ; + data_low_addr = AB8500_GPADC_AUTODATAL_REG; + data_high_addr = AB8500_GPADC_AUTODATAH_REG; + } else { + /* Start SW conversion */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, + ADC_SW_CONV, ADC_SW_CONV); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: start s/w conv failed\n"); + goto out; + } + completion_timeout = msecs_to_jiffies(CONVERSION_TIME); + data_low_addr = AB8500_GPADC_MANDATAL_REG; + data_high_addr = AB8500_GPADC_MANDATAH_REG; + } + + /* wait for completion of conversion */ + if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete, + completion_timeout)) { + dev_err(gpadc->dev, + "timeout didn't receive GPADC conv interrupt\n"); + ret = -EINVAL; + goto out; + } + + /* Read the converted RAW data */ + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_low_addr, &low_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_high_addr, &high_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc_conversion: read high data failed\n"); + goto out; + } + + /* Check if double convertion is required */ + if ((channel == BAT_CTRL_AND_IBAT) || + (channel == VBAT_MEAS_AND_IBAT) || + (channel == VBAT_TRUE_MEAS_AND_IBAT) || + (channel == BAT_TEMP_AND_IBAT)) { + + if (conv_type == ADC_HW) { + /* not supported */ + ret = -ENOTSUPP; + dev_err(gpadc->dev, + "gpadc_conversion: only SW double conversion supported\n"); + goto out; + } else { + /* Read the converted RAW data 2 */ + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2L_REG, + &low_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw low data 2 failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2H_REG, + &high_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw high data 2 failed\n"); + goto out; + } + if (ibat != NULL) { + *ibat = (high_data2 << 8) | low_data2; + } else { + dev_warn(gpadc->dev, + "gpadc_conversion: ibat not stored\n"); + } + + } + } + + /* Disable GPADC */ + ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, DIS_GPADC); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc_conversion: disable gpadc failed\n"); + goto out; + } + + /* Disable VTVout LDO this is required for GPADC */ + pm_runtime_mark_last_busy(gpadc->dev); + pm_runtime_put_autosuspend(gpadc->dev); + + mutex_unlock(&gpadc->ab8500_gpadc_lock); + + return (high_data << 8) | low_data; + +out: + /* + * It has shown to be needed to turn off the GPADC if an error occurs, + * otherwise we might have problem when waiting for the busy bit in the + * GPADC status register to go low. In V1.1 there wait_for_completion + * seems to timeout when waiting for an interrupt.. Not seen in V2.0 + */ + (void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, DIS_GPADC); + pm_runtime_put(gpadc->dev); + mutex_unlock(&gpadc->ab8500_gpadc_lock); + dev_err(gpadc->dev, + "gpadc_conversion: Failed to AD convert channel %d\n", channel); + return ret; +} +EXPORT_SYMBOL(ab8500_gpadc_read_raw); + +/** + * ab8500_bm_gpadcconvend_handler() - isr for gpadc conversion completion + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for gpadc conversion completion. + * Notifies the gpadc completion is completed and the converted raw value + * can be read from the registers. + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_bm_gpadcconvend_handler(int irq, void *_gpadc) +{ + struct ab8500_gpadc *gpadc = _gpadc; + + complete(&gpadc->ab8500_gpadc_complete); + + return IRQ_HANDLED; +} + +static int otp_cal_regs[] = { + AB8500_GPADC_CAL_1, + AB8500_GPADC_CAL_2, + AB8500_GPADC_CAL_3, + AB8500_GPADC_CAL_4, + AB8500_GPADC_CAL_5, + AB8500_GPADC_CAL_6, + AB8500_GPADC_CAL_7, +}; + +static int otp4_cal_regs[] = { + AB8540_GPADC_OTP4_REG_7, + AB8540_GPADC_OTP4_REG_6, + AB8540_GPADC_OTP4_REG_5, +}; + +static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) +{ + int i; + int ret[ARRAY_SIZE(otp_cal_regs)]; + u8 gpadc_cal[ARRAY_SIZE(otp_cal_regs)]; + int ret_otp4[ARRAY_SIZE(otp4_cal_regs)]; + u8 gpadc_otp4[ARRAY_SIZE(otp4_cal_regs)]; + int vmain_high, vmain_low; + int btemp_high, btemp_low; + int vbat_high, vbat_low; + int ibat_high, ibat_low; + s64 V_gain, V_offset, V2A_gain, V2A_offset; + struct ab8500 *ab8500; + + ab8500 = gpadc->parent; + + /* First we read all OTP registers and store the error code */ + for (i = 0; i < ARRAY_SIZE(otp_cal_regs); i++) { + ret[i] = abx500_get_register_interruptible(gpadc->dev, + AB8500_OTP_EMUL, otp_cal_regs[i], &gpadc_cal[i]); + if (ret[i] < 0) + dev_err(gpadc->dev, "%s: read otp reg 0x%02x failed\n", + __func__, otp_cal_regs[i]); + } + + /* + * The ADC calibration data is stored in OTP registers. + * The layout of the calibration data is outlined below and a more + * detailed description can be found in UM0836 + * + * vm_h/l = vmain_high/low + * bt_h/l = btemp_high/low + * vb_h/l = vbat_high/low + * + * Data bits 8500/9540: + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | vm_h9 | vm_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | + * |.......|.......|.......|.......|.......|.......|.......|....... + * + * Data bits 8540: + * OTP2 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h9 | vm_h8 | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | + * |.......|.......|.......|.......|.......|.......|.......|....... + * + * Data bits 8540: + * OTP4 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | ib_h9 | ib_h8 | ib_h7 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_h6 | ib_h5 | ib_h4 | ib_h3 | ib_h2 | ib_h1 | ib_h0 | ib_l5 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_l4 | ib_l3 | ib_l2 | ib_l1 | ib_l0 | + * + * + * Ideal output ADC codes corresponding to injected input voltages + * during manufacturing is: + * + * vmain_high: Vin = 19500mV / ADC ideal code = 997 + * vmain_low: Vin = 315mV / ADC ideal code = 16 + * btemp_high: Vin = 1300mV / ADC ideal code = 985 + * btemp_low: Vin = 21mV / ADC ideal code = 16 + * vbat_high: Vin = 4700mV / ADC ideal code = 982 + * vbat_low: Vin = 2380mV / ADC ideal code = 33 + */ + + if (is_ab8540(ab8500)) { + /* Calculate gain and offset for VMAIN if all reads succeeded*/ + if (!(ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[1] & 0xFF) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * + 19500 - (CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + } + + /* Read IBAT calibration Data */ + for (i = 0; i < ARRAY_SIZE(otp4_cal_regs); i++) { + ret_otp4[i] = abx500_get_register_interruptible( + gpadc->dev, AB8500_OTP_EMUL, + otp4_cal_regs[i], &gpadc_otp4[i]); + if (ret_otp4[i] < 0) + dev_err(gpadc->dev, + "%s: read otp4 reg 0x%02x failed\n", + __func__, otp4_cal_regs[i]); + } + + /* Calculate gain and offset for IBAT if all reads succeeded */ + if (!(ret_otp4[0] < 0 || ret_otp4[1] < 0 || ret_otp4[2] < 0)) { + ibat_high = (((gpadc_otp4[0] & 0x07) << 7) | + ((gpadc_otp4[1] & 0xFE) >> 1)); + ibat_low = (((gpadc_otp4[1] & 0x01) << 5) | + ((gpadc_otp4[2] & 0xF8) >> 3)); + + gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi = + (u16)ibat_high; + gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo = + (u16)ibat_low; + + V_gain = ((IBAT_VDROP_H - IBAT_VDROP_L) + << CALIB_SHIFT_IBAT) / (ibat_high - ibat_low); + + V_offset = (IBAT_VDROP_H << CALIB_SHIFT_IBAT) - + (((IBAT_VDROP_H - IBAT_VDROP_L) << + CALIB_SHIFT_IBAT) / (ibat_high - ibat_low)) + * ibat_high; + /* + * Result obtained is in mV (at a scale factor), + * we need to calculate gain and offset to get mA + */ + V2A_gain = (ADC_CH_IBAT_MAX - ADC_CH_IBAT_MIN)/ + (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V); + V2A_offset = ((ADC_CH_IBAT_MAX_V * ADC_CH_IBAT_MIN - + ADC_CH_IBAT_MAX * ADC_CH_IBAT_MIN_V) + << CALIB_SHIFT_IBAT) + / (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V); + + gpadc->cal_data[ADC_INPUT_IBAT].gain = V_gain * V2A_gain; + gpadc->cal_data[ADC_INPUT_IBAT].offset = V_offset * + V2A_gain + V2A_offset; + } else { + gpadc->cal_data[ADC_INPUT_IBAT].gain = 0; + } + + dev_dbg(gpadc->dev, "IBAT gain %llu offset %llu\n", + gpadc->cal_data[ADC_INPUT_IBAT].gain, + gpadc->cal_data[ADC_INPUT_IBAT].offset); + } else { + /* Calculate gain and offset for VMAIN if all reads succeeded */ + if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[0] & 0x03) << 8) | + ((gpadc_cal[1] & 0x3F) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + + gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * + 19500 - (CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + } + } + + /* Calculate gain and offset for BTEMP if all reads succeeded */ + if (!(ret[2] < 0 || ret[3] < 0 || ret[4] < 0)) { + btemp_high = (((gpadc_cal[2] & 0x01) << 9) | + (gpadc_cal[3] << 1) | ((gpadc_cal[4] & 0x80) >> 7)); + btemp_low = ((gpadc_cal[4] & 0x7C) >> 2); + + gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi = (u16)btemp_high; + gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo = (u16)btemp_low; + + gpadc->cal_data[ADC_INPUT_BTEMP].gain = + CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low); + gpadc->cal_data[ADC_INPUT_BTEMP].offset = CALIB_SCALE * 1300 - + (CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low)) + * btemp_high; + } else { + gpadc->cal_data[ADC_INPUT_BTEMP].gain = 0; + } + + /* Calculate gain and offset for VBAT if all reads succeeded */ + if (!(ret[4] < 0 || ret[5] < 0 || ret[6] < 0)) { + vbat_high = (((gpadc_cal[4] & 0x03) << 8) | gpadc_cal[5]); + vbat_low = ((gpadc_cal[6] & 0xFC) >> 2); + + gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi = (u16)vbat_high; + gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo = (u16)vbat_low; + + gpadc->cal_data[ADC_INPUT_VBAT].gain = CALIB_SCALE * + (4700 - 2380) / (vbat_high - vbat_low); + gpadc->cal_data[ADC_INPUT_VBAT].offset = CALIB_SCALE * 4700 - + (CALIB_SCALE * (4700 - 2380) / + (vbat_high - vbat_low)) * vbat_high; + } else { + gpadc->cal_data[ADC_INPUT_VBAT].gain = 0; + } + + dev_dbg(gpadc->dev, "VMAIN gain %llu offset %llu\n", + gpadc->cal_data[ADC_INPUT_VMAIN].gain, + gpadc->cal_data[ADC_INPUT_VMAIN].offset); + + dev_dbg(gpadc->dev, "BTEMP gain %llu offset %llu\n", + gpadc->cal_data[ADC_INPUT_BTEMP].gain, + gpadc->cal_data[ADC_INPUT_BTEMP].offset); + + dev_dbg(gpadc->dev, "VBAT gain %llu offset %llu\n", + gpadc->cal_data[ADC_INPUT_VBAT].gain, + gpadc->cal_data[ADC_INPUT_VBAT].offset); +} + +#ifdef CONFIG_PM +static int ab8500_gpadc_runtime_suspend(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + + regulator_disable(gpadc->regu); + return 0; +} + +static int ab8500_gpadc_runtime_resume(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(gpadc->regu); + if (ret) + dev_err(dev, "Failed to enable vtvout LDO: %d\n", ret); + return ret; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int ab8500_gpadc_suspend(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + + mutex_lock(&gpadc->ab8500_gpadc_lock); + + pm_runtime_get_sync(dev); + + regulator_disable(gpadc->regu); + return 0; +} + +static int ab8500_gpadc_resume(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(gpadc->regu); + if (ret) + dev_err(dev, "Failed to enable vtvout LDO: %d\n", ret); + + pm_runtime_mark_last_busy(gpadc->dev); + pm_runtime_put_autosuspend(gpadc->dev); + + mutex_unlock(&gpadc->ab8500_gpadc_lock); + return ret; +} +#endif + +static int ab8500_gpadc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_gpadc *gpadc; + + gpadc = devm_kzalloc(&pdev->dev, sizeof(struct ab8500_gpadc), GFP_KERNEL); + if (!gpadc) { + dev_err(&pdev->dev, "Error: No memory\n"); + return -ENOMEM; + } + + gpadc->irq_sw = platform_get_irq_byname(pdev, "SW_CONV_END"); + if (gpadc->irq_sw < 0) + dev_err(gpadc->dev, "failed to get platform sw_conv_end irq\n"); + + gpadc->irq_hw = platform_get_irq_byname(pdev, "HW_CONV_END"); + if (gpadc->irq_hw < 0) + dev_err(gpadc->dev, "failed to get platform hw_conv_end irq\n"); + + gpadc->dev = &pdev->dev; + gpadc->parent = dev_get_drvdata(pdev->dev.parent); + mutex_init(&gpadc->ab8500_gpadc_lock); + + /* Initialize completion used to notify completion of conversion */ + init_completion(&gpadc->ab8500_gpadc_complete); + + /* Register interrupts */ + if (gpadc->irq_sw >= 0) { + ret = request_threaded_irq(gpadc->irq_sw, NULL, + ab8500_bm_gpadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-sw", + gpadc); + if (ret < 0) { + dev_err(gpadc->dev, + "Failed to register interrupt irq: %d\n", + gpadc->irq_sw); + goto fail; + } + } + + if (gpadc->irq_hw >= 0) { + ret = request_threaded_irq(gpadc->irq_hw, NULL, + ab8500_bm_gpadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-hw", + gpadc); + if (ret < 0) { + dev_err(gpadc->dev, + "Failed to register interrupt irq: %d\n", + gpadc->irq_hw); + goto fail_irq; + } + } + + /* VTVout LDO used to power up ab8500-GPADC */ + gpadc->regu = devm_regulator_get(&pdev->dev, "vddadc"); + if (IS_ERR(gpadc->regu)) { + ret = PTR_ERR(gpadc->regu); + dev_err(gpadc->dev, "failed to get vtvout LDO\n"); + goto fail_irq; + } + + platform_set_drvdata(pdev, gpadc); + + ret = regulator_enable(gpadc->regu); + if (ret) { + dev_err(gpadc->dev, "Failed to enable vtvout LDO: %d\n", ret); + goto fail_enable; + } + + pm_runtime_set_autosuspend_delay(gpadc->dev, GPADC_AUDOSUSPEND_DELAY); + pm_runtime_use_autosuspend(gpadc->dev); + pm_runtime_set_active(gpadc->dev); + pm_runtime_enable(gpadc->dev); + + ab8500_gpadc_read_calibration_data(gpadc); + list_add_tail(&gpadc->node, &ab8500_gpadc_list); + dev_dbg(gpadc->dev, "probe success\n"); + + return 0; + +fail_enable: +fail_irq: + free_irq(gpadc->irq_sw, gpadc); + free_irq(gpadc->irq_hw, gpadc); +fail: + return ret; +} + +static int ab8500_gpadc_remove(struct platform_device *pdev) +{ + struct ab8500_gpadc *gpadc = platform_get_drvdata(pdev); + + /* remove this gpadc entry from the list */ + list_del(&gpadc->node); + /* remove interrupt - completion of Sw ADC conversion */ + if (gpadc->irq_sw >= 0) + free_irq(gpadc->irq_sw, gpadc); + if (gpadc->irq_hw >= 0) + free_irq(gpadc->irq_hw, gpadc); + + pm_runtime_get_sync(gpadc->dev); + pm_runtime_disable(gpadc->dev); + + regulator_disable(gpadc->regu); + + pm_runtime_set_suspended(gpadc->dev); + + pm_runtime_put_noidle(gpadc->dev); + + return 0; +} + +static const struct dev_pm_ops ab8500_gpadc_pm_ops = { + SET_RUNTIME_PM_OPS(ab8500_gpadc_runtime_suspend, + ab8500_gpadc_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(ab8500_gpadc_suspend, + ab8500_gpadc_resume) + +}; + +static struct platform_driver ab8500_gpadc_driver = { + .probe = ab8500_gpadc_probe, + .remove = ab8500_gpadc_remove, + .driver = { + .name = "ab8500-gpadc", + .pm = &ab8500_gpadc_pm_ops, + }, +}; + +static int __init ab8500_gpadc_init(void) +{ + return platform_driver_register(&ab8500_gpadc_driver); +} + +static void __exit ab8500_gpadc_exit(void) +{ + platform_driver_unregister(&ab8500_gpadc_driver); +} + +/** + * ab8540_gpadc_get_otp() - returns OTP values + * + */ +void ab8540_gpadc_get_otp(struct ab8500_gpadc *gpadc, + u16 *vmain_l, u16 *vmain_h, u16 *btemp_l, u16 *btemp_h, + u16 *vbat_l, u16 *vbat_h, u16 *ibat_l, u16 *ibat_h) +{ + *vmain_l = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo; + *vmain_h = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi; + *btemp_l = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo; + *btemp_h = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi; + *vbat_l = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo; + *vbat_h = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi; + *ibat_l = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo; + *ibat_h = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi; + return ; +} + +subsys_initcall_sync(ab8500_gpadc_init); +module_exit(ab8500_gpadc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson," + "M'boumba Cedric Madianga"); +MODULE_ALIAS("platform:ab8500_gpadc"); +MODULE_DESCRIPTION("AB8500 GPADC driver"); diff --git a/kernel/drivers/mfd/ab8500-sysctrl.c b/kernel/drivers/mfd/ab8500-sysctrl.c new file mode 100644 index 000000000..0d1825696 --- /dev/null +++ b/kernel/drivers/mfd/ab8500-sysctrl.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> for ST Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/reboot.h> +#include <linux/signal.h> +#include <linux/power_supply.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-sysctrl.h> + +/* RtcCtrl bits */ +#define AB8500_ALARM_MIN_LOW 0x08 +#define AB8500_ALARM_MIN_MID 0x09 +#define RTC_CTRL 0x0B +#define RTC_ALARM_ENABLE 0x4 + +static struct device *sysctrl_dev; + +static void ab8500_power_off(void) +{ + sigset_t old; + sigset_t all; + static char *pss[] = {"ab8500_ac", "pm2301", "ab8500_usb"}; + int i; + bool charger_present = false; + union power_supply_propval val; + struct power_supply *psy; + int ret; + + if (sysctrl_dev == NULL) { + pr_err("%s: sysctrl not initialized\n", __func__); + return; + } + + /* + * If we have a charger connected and we're powering off, + * reboot into charge-only mode. + */ + + for (i = 0; i < ARRAY_SIZE(pss); i++) { + psy = power_supply_get_by_name(pss[i]); + if (!psy) + continue; + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, + &val); + power_supply_put(psy); + + if (!ret && val.intval) { + charger_present = true; + break; + } + } + + if (!charger_present) + goto shutdown; + + /* Check if battery is known */ + psy = power_supply_get_by_name("ab8500_btemp"); + if (psy) { + ret = power_supply_get_property(psy, + POWER_SUPPLY_PROP_TECHNOLOGY, &val); + if (!ret && val.intval != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) { + printk(KERN_INFO + "Charger \"%s\" is connected with known battery." + " Rebooting.\n", + pss[i]); + machine_restart("charging"); + } + power_supply_put(psy); + } + +shutdown: + sigfillset(&all); + + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } +} + +static inline bool valid_bank(u8 bank) +{ + return ((bank == AB8500_SYS_CTRL1_BLOCK) || + (bank == AB8500_SYS_CTRL2_BLOCK)); +} + +int ab8500_sysctrl_read(u16 reg, u8 *value) +{ + u8 bank; + + if (sysctrl_dev == NULL) + return -EINVAL; + + bank = (reg >> 8); + if (!valid_bank(bank)) + return -EINVAL; + + return abx500_get_register_interruptible(sysctrl_dev, bank, + (u8)(reg & 0xFF), value); +} +EXPORT_SYMBOL(ab8500_sysctrl_read); + +int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) +{ + u8 bank; + + if (sysctrl_dev == NULL) + return -EINVAL; + + bank = (reg >> 8); + if (!valid_bank(bank)) + return -EINVAL; + + return abx500_mask_and_set_register_interruptible(sysctrl_dev, bank, + (u8)(reg & 0xFF), mask, value); +} +EXPORT_SYMBOL(ab8500_sysctrl_write); + +static int ab8500_sysctrl_probe(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_platform_data *plat; + struct ab8500_sysctrl_platform_data *pdata; + + plat = dev_get_platdata(pdev->dev.parent); + + if (!plat) + return -EINVAL; + + sysctrl_dev = &pdev->dev; + + if (!pm_power_off) + pm_power_off = ab8500_power_off; + + pdata = plat->sysctrl; + if (pdata) { + int last, ret, i, j; + + if (is_ab8505(ab8500)) + last = AB8500_SYSCLKREQ4RFCLKBUF; + else + last = AB8500_SYSCLKREQ8RFCLKBUF; + + for (i = AB8500_SYSCLKREQ1RFCLKBUF; i <= last; i++) { + j = i - AB8500_SYSCLKREQ1RFCLKBUF; + ret = ab8500_sysctrl_write(i, 0xff, + pdata->initial_req_buf_config[j]); + dev_dbg(&pdev->dev, + "Setting SysClkReq%dRfClkBuf 0x%X\n", + j + 1, + pdata->initial_req_buf_config[j]); + if (ret < 0) { + dev_err(&pdev->dev, + "unable to set sysClkReq%dRfClkBuf: " + "%d\n", j + 1, ret); + } + } + } + + return 0; +} + +static int ab8500_sysctrl_remove(struct platform_device *pdev) +{ + sysctrl_dev = NULL; + + if (pm_power_off == ab8500_power_off) + pm_power_off = NULL; + + return 0; +} + +static struct platform_driver ab8500_sysctrl_driver = { + .driver = { + .name = "ab8500-sysctrl", + }, + .probe = ab8500_sysctrl_probe, + .remove = ab8500_sysctrl_remove, +}; + +static int __init ab8500_sysctrl_init(void) +{ + return platform_driver_register(&ab8500_sysctrl_driver); +} +arch_initcall(ab8500_sysctrl_init); + +MODULE_AUTHOR("Mattias Nilsson <mattias.i.nilsson@stericsson.com"); +MODULE_DESCRIPTION("AB8500 system control driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/abx500-core.c b/kernel/drivers/mfd/abx500-core.c new file mode 100644 index 000000000..fe4189951 --- /dev/null +++ b/kernel/drivers/mfd/abx500-core.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2007-2010 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Register access functions for the ABX500 Mixed Signal IC family. + * Author: Mattias Wallin <mattias.wallin@stericsson.com> + */ + +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/mfd/abx500.h> + +static LIST_HEAD(abx500_list); + +struct abx500_device_entry { + struct list_head list; + struct abx500_ops ops; + struct device *dev; +}; + +static void lookup_ops(struct device *dev, struct abx500_ops **ops) +{ + struct abx500_device_entry *dev_entry; + + *ops = NULL; + list_for_each_entry(dev_entry, &abx500_list, list) { + if (dev_entry->dev == dev) { + *ops = &dev_entry->ops; + return; + } + } +} + +int abx500_register_ops(struct device *dev, struct abx500_ops *ops) +{ + struct abx500_device_entry *dev_entry; + + dev_entry = devm_kzalloc(dev, + sizeof(struct abx500_device_entry), + GFP_KERNEL); + if (!dev_entry) { + dev_err(dev, "register_ops kzalloc failed"); + return -ENOMEM; + } + dev_entry->dev = dev; + memcpy(&dev_entry->ops, ops, sizeof(struct abx500_ops)); + + list_add_tail(&dev_entry->list, &abx500_list); + return 0; +} +EXPORT_SYMBOL(abx500_register_ops); + +void abx500_remove_ops(struct device *dev) +{ + struct abx500_device_entry *dev_entry, *tmp; + + list_for_each_entry_safe(dev_entry, tmp, &abx500_list, list) + if (dev_entry->dev == dev) + list_del(&dev_entry->list); +} +EXPORT_SYMBOL(abx500_remove_ops); + +int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 value) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->set_register != NULL)) + return ops->set_register(dev, bank, reg, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_set_register_interruptible); + +int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->get_register != NULL)) + return ops->get_register(dev, bank, reg, value); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_get_register_interruptible); + +int abx500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->get_register_page != NULL)) + return ops->get_register_page(dev, bank, + first_reg, regvals, numregs); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_get_register_page_interruptible); + +int abx500_mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->mask_and_set_register != NULL)) + return ops->mask_and_set_register(dev, bank, + reg, bitmask, bitvalues); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_mask_and_set_register_interruptible); + +int abx500_get_chip_id(struct device *dev) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->get_chip_id != NULL)) + return ops->get_chip_id(dev); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_get_chip_id); + +int abx500_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->event_registers_startup_state_get != NULL)) + return ops->event_registers_startup_state_get(dev, event); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_event_registers_startup_state_get); + +int abx500_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct abx500_ops *ops; + + lookup_ops(dev->parent, &ops); + if ((ops != NULL) && (ops->startup_irq_enabled != NULL)) + return ops->startup_irq_enabled(dev, irq); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL(abx500_startup_irq_enabled); + +MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>"); +MODULE_DESCRIPTION("ABX500 core driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/adp5520.c b/kernel/drivers/mfd/adp5520.c new file mode 100644 index 000000000..f495b8b57 --- /dev/null +++ b/kernel/drivers/mfd/adp5520.c @@ -0,0 +1,366 @@ +/* + * Base driver for Analog Devices ADP5520/ADP5501 MFD PMICs + * LCD Backlight: drivers/video/backlight/adp5520_bl + * LEDs : drivers/led/leds-adp5520 + * GPIO : drivers/gpio/adp5520-gpio (ADP5520 only) + * Keys : drivers/input/keyboard/adp5520-keys (ADP5520 only) + * + * Copyright 2009 Analog Devices Inc. + * + * Derived from da903x: + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.com> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/i2c.h> + +#include <linux/mfd/adp5520.h> + +struct adp5520_chip { + struct i2c_client *client; + struct device *dev; + struct mutex lock; + struct blocking_notifier_head notifier_list; + int irq; + unsigned long id; + uint8_t mode; +}; + +static int __adp5520_read(struct i2c_client *client, + int reg, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "failed reading at 0x%02x\n", reg); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static int __adp5520_write(struct i2c_client *client, + int reg, uint8_t val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", + val, reg); + return ret; + } + return 0; +} + +static int __adp5520_ack_bits(struct i2c_client *client, int reg, + uint8_t bit_mask) +{ + struct adp5520_chip *chip = i2c_get_clientdata(client); + uint8_t reg_val; + int ret; + + mutex_lock(&chip->lock); + + ret = __adp5520_read(client, reg, ®_val); + + if (!ret) { + reg_val |= bit_mask; + ret = __adp5520_write(client, reg, reg_val); + } + + mutex_unlock(&chip->lock); + return ret; +} + +int adp5520_write(struct device *dev, int reg, uint8_t val) +{ + return __adp5520_write(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(adp5520_write); + +int adp5520_read(struct device *dev, int reg, uint8_t *val) +{ + return __adp5520_read(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(adp5520_read); + +int adp5520_set_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret; + + mutex_lock(&chip->lock); + + ret = __adp5520_read(chip->client, reg, ®_val); + + if (!ret && ((reg_val & bit_mask) != bit_mask)) { + reg_val |= bit_mask; + ret = __adp5520_write(chip->client, reg, reg_val); + } + + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(adp5520_set_bits); + +int adp5520_clr_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret; + + mutex_lock(&chip->lock); + + ret = __adp5520_read(chip->client, reg, ®_val); + + if (!ret && (reg_val & bit_mask)) { + reg_val &= ~bit_mask; + ret = __adp5520_write(chip->client, reg, reg_val); + } + + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(adp5520_clr_bits); + +int adp5520_register_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + + if (chip->irq) { + adp5520_set_bits(chip->dev, ADP5520_INTERRUPT_ENABLE, + events & (ADP5520_KP_IEN | ADP5520_KR_IEN | + ADP5520_OVP_IEN | ADP5520_CMPR_IEN)); + + return blocking_notifier_chain_register(&chip->notifier_list, + nb); + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(adp5520_register_notifier); + +int adp5520_unregister_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct adp5520_chip *chip = dev_get_drvdata(dev); + + adp5520_clr_bits(chip->dev, ADP5520_INTERRUPT_ENABLE, + events & (ADP5520_KP_IEN | ADP5520_KR_IEN | + ADP5520_OVP_IEN | ADP5520_CMPR_IEN)); + + return blocking_notifier_chain_unregister(&chip->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(adp5520_unregister_notifier); + +static irqreturn_t adp5520_irq_thread(int irq, void *data) +{ + struct adp5520_chip *chip = data; + unsigned int events; + uint8_t reg_val; + int ret; + + ret = __adp5520_read(chip->client, ADP5520_MODE_STATUS, ®_val); + if (ret) + goto out; + + events = reg_val & (ADP5520_OVP_INT | ADP5520_CMPR_INT | + ADP5520_GPI_INT | ADP5520_KR_INT | ADP5520_KP_INT); + + blocking_notifier_call_chain(&chip->notifier_list, events, NULL); + /* ACK, Sticky bits are W1C */ + __adp5520_ack_bits(chip->client, ADP5520_MODE_STATUS, events); + +out: + return IRQ_HANDLED; +} + +static int __remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int adp5520_remove_subdevs(struct adp5520_chip *chip) +{ + return device_for_each_child(chip->dev, NULL, __remove_subdev); +} + +static int adp5520_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adp5520_platform_data *pdata = dev_get_platdata(&client->dev); + struct platform_device *pdev; + struct adp5520_chip *chip; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "SMBUS Word Data not Supported\n"); + return -EIO; + } + + if (pdata == NULL) { + dev_err(&client->dev, "missing platform data\n"); + return -ENODEV; + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + chip->dev = &client->dev; + chip->irq = client->irq; + chip->id = id->driver_data; + mutex_init(&chip->lock); + + if (chip->irq) { + BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list); + + ret = request_threaded_irq(chip->irq, NULL, adp5520_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "adp5520", chip); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + chip->irq); + return ret; + } + } + + ret = adp5520_write(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); + if (ret) { + dev_err(&client->dev, "failed to write\n"); + goto out_free_irq; + } + + if (pdata->keys) { + pdev = platform_device_register_data(chip->dev, "adp5520-keys", + chip->id, pdata->keys, sizeof(*pdata->keys)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + if (pdata->gpio) { + pdev = platform_device_register_data(chip->dev, "adp5520-gpio", + chip->id, pdata->gpio, sizeof(*pdata->gpio)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + if (pdata->leds) { + pdev = platform_device_register_data(chip->dev, "adp5520-led", + chip->id, pdata->leds, sizeof(*pdata->leds)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + if (pdata->backlight) { + pdev = platform_device_register_data(chip->dev, + "adp5520-backlight", + chip->id, + pdata->backlight, + sizeof(*pdata->backlight)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_remove_subdevs; + } + } + + return 0; + +out_remove_subdevs: + adp5520_remove_subdevs(chip); + +out_free_irq: + if (chip->irq) + free_irq(chip->irq, chip); + + return ret; +} + +static int adp5520_remove(struct i2c_client *client) +{ + struct adp5520_chip *chip = dev_get_drvdata(&client->dev); + + if (chip->irq) + free_irq(chip->irq, chip); + + adp5520_remove_subdevs(chip); + adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int adp5520_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adp5520_chip *chip = dev_get_drvdata(&client->dev); + + adp5520_read(chip->dev, ADP5520_MODE_STATUS, &chip->mode); + /* All other bits are W1C */ + chip->mode &= ADP5520_BL_EN | ADP5520_DIM_EN | ADP5520_nSTNBY; + adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0); + return 0; +} + +static int adp5520_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adp5520_chip *chip = dev_get_drvdata(&client->dev); + + adp5520_write(chip->dev, ADP5520_MODE_STATUS, chip->mode); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(adp5520_pm, adp5520_suspend, adp5520_resume); + +static const struct i2c_device_id adp5520_id[] = { + { "pmic-adp5520", ID_ADP5520 }, + { "pmic-adp5501", ID_ADP5501 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp5520_id); + +static struct i2c_driver adp5520_driver = { + .driver = { + .name = "adp5520", + .owner = THIS_MODULE, + .pm = &adp5520_pm, + }, + .probe = adp5520_probe, + .remove = adp5520_remove, + .id_table = adp5520_id, +}; + +module_i2c_driver(adp5520_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("ADP5520(01) PMIC-MFD Driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/arizona-core.c b/kernel/drivers/mfd/arizona-core.c new file mode 100644 index 000000000..6ca6dfab5 --- /dev/null +++ b/kernel/drivers/mfd/arizona-core.c @@ -0,0 +1,1092 @@ +/* + * Arizona core driver + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/machine.h> +#include <linux/slab.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/registers.h> + +#include "arizona.h" + +static const char *wm5102_core_supplies[] = { + "AVDD", + "DBVDD1", +}; + +int arizona_clk32k_enable(struct arizona *arizona) +{ + int ret = 0; + + mutex_lock(&arizona->clk_lock); + + arizona->clk32k_ref++; + + if (arizona->clk32k_ref == 1) { + switch (arizona->pdata.clk32k_src) { + case ARIZONA_32KZ_MCLK1: + ret = pm_runtime_get_sync(arizona->dev); + if (ret != 0) + goto out; + break; + } + + ret = regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, + ARIZONA_CLK_32K_ENA, + ARIZONA_CLK_32K_ENA); + } + +out: + if (ret != 0) + arizona->clk32k_ref--; + + mutex_unlock(&arizona->clk_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(arizona_clk32k_enable); + +int arizona_clk32k_disable(struct arizona *arizona) +{ + int ret = 0; + + mutex_lock(&arizona->clk_lock); + + BUG_ON(arizona->clk32k_ref <= 0); + + arizona->clk32k_ref--; + + if (arizona->clk32k_ref == 0) { + regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, + ARIZONA_CLK_32K_ENA, 0); + + switch (arizona->pdata.clk32k_src) { + case ARIZONA_32KZ_MCLK1: + pm_runtime_put_sync(arizona->dev); + break; + } + } + + mutex_unlock(&arizona->clk_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(arizona_clk32k_disable); + +static irqreturn_t arizona_clkgen_err(int irq, void *data) +{ + struct arizona *arizona = data; + + dev_err(arizona->dev, "CLKGEN error\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t arizona_underclocked(int irq, void *data) +{ + struct arizona *arizona = data; + unsigned int val; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_8, + &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read underclock status: %d\n", + ret); + return IRQ_NONE; + } + + if (val & ARIZONA_AIF3_UNDERCLOCKED_STS) + dev_err(arizona->dev, "AIF3 underclocked\n"); + if (val & ARIZONA_AIF2_UNDERCLOCKED_STS) + dev_err(arizona->dev, "AIF2 underclocked\n"); + if (val & ARIZONA_AIF1_UNDERCLOCKED_STS) + dev_err(arizona->dev, "AIF1 underclocked\n"); + if (val & ARIZONA_ISRC3_UNDERCLOCKED_STS) + dev_err(arizona->dev, "ISRC3 underclocked\n"); + if (val & ARIZONA_ISRC2_UNDERCLOCKED_STS) + dev_err(arizona->dev, "ISRC2 underclocked\n"); + if (val & ARIZONA_ISRC1_UNDERCLOCKED_STS) + dev_err(arizona->dev, "ISRC1 underclocked\n"); + if (val & ARIZONA_FX_UNDERCLOCKED_STS) + dev_err(arizona->dev, "FX underclocked\n"); + if (val & ARIZONA_ASRC_UNDERCLOCKED_STS) + dev_err(arizona->dev, "ASRC underclocked\n"); + if (val & ARIZONA_DAC_UNDERCLOCKED_STS) + dev_err(arizona->dev, "DAC underclocked\n"); + if (val & ARIZONA_ADC_UNDERCLOCKED_STS) + dev_err(arizona->dev, "ADC underclocked\n"); + if (val & ARIZONA_MIXER_UNDERCLOCKED_STS) + dev_err(arizona->dev, "Mixer dropped sample\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t arizona_overclocked(int irq, void *data) +{ + struct arizona *arizona = data; + unsigned int val[2]; + int ret; + + ret = regmap_bulk_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_6, + &val[0], 2); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read overclock status: %d\n", + ret); + return IRQ_NONE; + } + + if (val[0] & ARIZONA_PWM_OVERCLOCKED_STS) + dev_err(arizona->dev, "PWM overclocked\n"); + if (val[0] & ARIZONA_FX_CORE_OVERCLOCKED_STS) + dev_err(arizona->dev, "FX core overclocked\n"); + if (val[0] & ARIZONA_DAC_SYS_OVERCLOCKED_STS) + dev_err(arizona->dev, "DAC SYS overclocked\n"); + if (val[0] & ARIZONA_DAC_WARP_OVERCLOCKED_STS) + dev_err(arizona->dev, "DAC WARP overclocked\n"); + if (val[0] & ARIZONA_ADC_OVERCLOCKED_STS) + dev_err(arizona->dev, "ADC overclocked\n"); + if (val[0] & ARIZONA_MIXER_OVERCLOCKED_STS) + dev_err(arizona->dev, "Mixer overclocked\n"); + if (val[0] & ARIZONA_AIF3_SYNC_OVERCLOCKED_STS) + dev_err(arizona->dev, "AIF3 overclocked\n"); + if (val[0] & ARIZONA_AIF2_SYNC_OVERCLOCKED_STS) + dev_err(arizona->dev, "AIF2 overclocked\n"); + if (val[0] & ARIZONA_AIF1_SYNC_OVERCLOCKED_STS) + dev_err(arizona->dev, "AIF1 overclocked\n"); + if (val[0] & ARIZONA_PAD_CTRL_OVERCLOCKED_STS) + dev_err(arizona->dev, "Pad control overclocked\n"); + + if (val[1] & ARIZONA_SLIMBUS_SUBSYS_OVERCLOCKED_STS) + dev_err(arizona->dev, "Slimbus subsystem overclocked\n"); + if (val[1] & ARIZONA_SLIMBUS_ASYNC_OVERCLOCKED_STS) + dev_err(arizona->dev, "Slimbus async overclocked\n"); + if (val[1] & ARIZONA_SLIMBUS_SYNC_OVERCLOCKED_STS) + dev_err(arizona->dev, "Slimbus sync overclocked\n"); + if (val[1] & ARIZONA_ASRC_ASYNC_SYS_OVERCLOCKED_STS) + dev_err(arizona->dev, "ASRC async system overclocked\n"); + if (val[1] & ARIZONA_ASRC_ASYNC_WARP_OVERCLOCKED_STS) + dev_err(arizona->dev, "ASRC async WARP overclocked\n"); + if (val[1] & ARIZONA_ASRC_SYNC_SYS_OVERCLOCKED_STS) + dev_err(arizona->dev, "ASRC sync system overclocked\n"); + if (val[1] & ARIZONA_ASRC_SYNC_WARP_OVERCLOCKED_STS) + dev_err(arizona->dev, "ASRC sync WARP overclocked\n"); + if (val[1] & ARIZONA_ADSP2_1_OVERCLOCKED_STS) + dev_err(arizona->dev, "DSP1 overclocked\n"); + if (val[1] & ARIZONA_ISRC3_OVERCLOCKED_STS) + dev_err(arizona->dev, "ISRC3 overclocked\n"); + if (val[1] & ARIZONA_ISRC2_OVERCLOCKED_STS) + dev_err(arizona->dev, "ISRC2 overclocked\n"); + if (val[1] & ARIZONA_ISRC1_OVERCLOCKED_STS) + dev_err(arizona->dev, "ISRC1 overclocked\n"); + + return IRQ_HANDLED; +} + +static int arizona_poll_reg(struct arizona *arizona, + int timeout, unsigned int reg, + unsigned int mask, unsigned int target) +{ + unsigned int val = 0; + int ret, i; + + for (i = 0; i < timeout; i++) { + ret = regmap_read(arizona->regmap, reg, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read reg %u: %d\n", + reg, ret); + continue; + } + + if ((val & mask) == target) + return 0; + + msleep(1); + } + + dev_err(arizona->dev, "Polling reg %u timed out: %x\n", reg, val); + return -ETIMEDOUT; +} + +static int arizona_wait_for_boot(struct arizona *arizona) +{ + int ret; + + /* + * We can't use an interrupt as we need to runtime resume to do so, + * we won't race with the interrupt handler as it'll be blocked on + * runtime resume. + */ + ret = arizona_poll_reg(arizona, 5, ARIZONA_INTERRUPT_RAW_STATUS_5, + ARIZONA_BOOT_DONE_STS, ARIZONA_BOOT_DONE_STS); + + if (!ret) + regmap_write(arizona->regmap, ARIZONA_INTERRUPT_STATUS_5, + ARIZONA_BOOT_DONE_STS); + + pm_runtime_mark_last_busy(arizona->dev); + + return ret; +} + +static int arizona_apply_hardware_patch(struct arizona* arizona) +{ + unsigned int fll, sysclk; + int ret, err; + + /* Cache existing FLL and SYSCLK settings */ + ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &fll); + if (ret != 0) { + dev_err(arizona->dev, "Failed to cache FLL settings: %d\n", + ret); + return ret; + } + ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &sysclk); + if (ret != 0) { + dev_err(arizona->dev, "Failed to cache SYSCLK settings: %d\n", + ret); + return ret; + } + + /* Start up SYSCLK using the FLL in free running mode */ + ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, + ARIZONA_FLL1_ENA | ARIZONA_FLL1_FREERUN); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to start FLL in freerunning mode: %d\n", + ret); + return ret; + } + ret = arizona_poll_reg(arizona, 25, ARIZONA_INTERRUPT_RAW_STATUS_5, + ARIZONA_FLL1_CLOCK_OK_STS, + ARIZONA_FLL1_CLOCK_OK_STS); + if (ret != 0) { + ret = -ETIMEDOUT; + goto err_fll; + } + + ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, 0x0144); + if (ret != 0) { + dev_err(arizona->dev, "Failed to start SYSCLK: %d\n", ret); + goto err_fll; + } + + /* Start the write sequencer and wait for it to finish */ + ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, + ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160); + if (ret != 0) { + dev_err(arizona->dev, "Failed to start write sequencer: %d\n", + ret); + goto err_sysclk; + } + ret = arizona_poll_reg(arizona, 5, ARIZONA_WRITE_SEQUENCER_CTRL_1, + ARIZONA_WSEQ_BUSY, 0); + if (ret != 0) { + regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, + ARIZONA_WSEQ_ABORT); + ret = -ETIMEDOUT; + } + +err_sysclk: + err = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, sysclk); + if (err != 0) { + dev_err(arizona->dev, + "Failed to re-apply old SYSCLK settings: %d\n", + err); + } + +err_fll: + err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, fll); + if (err != 0) { + dev_err(arizona->dev, + "Failed to re-apply old FLL settings: %d\n", + err); + } + + if (ret != 0) + return ret; + else + return err; +} + +#ifdef CONFIG_PM +static int arizona_runtime_resume(struct device *dev) +{ + struct arizona *arizona = dev_get_drvdata(dev); + int ret; + + dev_dbg(arizona->dev, "Leaving AoD mode\n"); + + ret = regulator_enable(arizona->dcvdd); + if (ret != 0) { + dev_err(arizona->dev, "Failed to enable DCVDD: %d\n", ret); + return ret; + } + + regcache_cache_only(arizona->regmap, false); + + switch (arizona->type) { + case WM5102: + if (arizona->external_dcvdd) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ISOLATION_CONTROL, + ARIZONA_ISOLATE_DCVDD1, 0); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to connect DCVDD: %d\n", ret); + goto err; + } + } + + ret = wm5102_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, "Failed to apply patch: %d\n", + ret); + goto err; + } + + ret = arizona_apply_hardware_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to apply hardware patch: %d\n", + ret); + goto err; + } + break; + default: + ret = arizona_wait_for_boot(arizona); + if (ret != 0) { + goto err; + } + + if (arizona->external_dcvdd) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ISOLATION_CONTROL, + ARIZONA_ISOLATE_DCVDD1, 0); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to connect DCVDD: %d\n", ret); + goto err; + } + } + break; + } + + ret = regcache_sync(arizona->regmap); + if (ret != 0) { + dev_err(arizona->dev, "Failed to restore register cache\n"); + goto err; + } + + return 0; + +err: + regcache_cache_only(arizona->regmap, true); + regulator_disable(arizona->dcvdd); + return ret; +} + +static int arizona_runtime_suspend(struct device *dev) +{ + struct arizona *arizona = dev_get_drvdata(dev); + int ret; + + dev_dbg(arizona->dev, "Entering AoD mode\n"); + + if (arizona->external_dcvdd) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ISOLATION_CONTROL, + ARIZONA_ISOLATE_DCVDD1, + ARIZONA_ISOLATE_DCVDD1); + if (ret != 0) { + dev_err(arizona->dev, "Failed to isolate DCVDD: %d\n", + ret); + return ret; + } + } + + regcache_cache_only(arizona->regmap, true); + regcache_mark_dirty(arizona->regmap); + regulator_disable(arizona->dcvdd); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int arizona_suspend(struct device *dev) +{ + struct arizona *arizona = dev_get_drvdata(dev); + + dev_dbg(arizona->dev, "Suspend, disabling IRQ\n"); + disable_irq(arizona->irq); + + return 0; +} + +static int arizona_suspend_late(struct device *dev) +{ + struct arizona *arizona = dev_get_drvdata(dev); + + dev_dbg(arizona->dev, "Late suspend, reenabling IRQ\n"); + enable_irq(arizona->irq); + + return 0; +} + +static int arizona_resume_noirq(struct device *dev) +{ + struct arizona *arizona = dev_get_drvdata(dev); + + dev_dbg(arizona->dev, "Early resume, disabling IRQ\n"); + disable_irq(arizona->irq); + + return 0; +} + +static int arizona_resume(struct device *dev) +{ + struct arizona *arizona = dev_get_drvdata(dev); + + dev_dbg(arizona->dev, "Late resume, reenabling IRQ\n"); + enable_irq(arizona->irq); + + return 0; +} +#endif + +const struct dev_pm_ops arizona_pm_ops = { + SET_RUNTIME_PM_OPS(arizona_runtime_suspend, + arizona_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(arizona_suspend, arizona_resume) +#ifdef CONFIG_PM_SLEEP + .suspend_late = arizona_suspend_late, + .resume_noirq = arizona_resume_noirq, +#endif +}; +EXPORT_SYMBOL_GPL(arizona_pm_ops); + +#ifdef CONFIG_OF +unsigned long arizona_of_get_type(struct device *dev) +{ + const struct of_device_id *id = of_match_device(arizona_of_match, dev); + + if (id) + return (unsigned long)id->data; + else + return 0; +} +EXPORT_SYMBOL_GPL(arizona_of_get_type); + +int arizona_of_get_named_gpio(struct arizona *arizona, const char *prop, + bool mandatory) +{ + int gpio; + + gpio = of_get_named_gpio(arizona->dev->of_node, prop, 0); + if (gpio < 0) { + if (mandatory) + dev_err(arizona->dev, + "Mandatory DT gpio %s missing/malformed: %d\n", + prop, gpio); + + gpio = 0; + } + + return gpio; +} +EXPORT_SYMBOL_GPL(arizona_of_get_named_gpio); + +static int arizona_of_get_core_pdata(struct arizona *arizona) +{ + struct arizona_pdata *pdata = &arizona->pdata; + struct property *prop; + const __be32 *cur; + u32 val; + int ret, i; + int count = 0; + + pdata->reset = arizona_of_get_named_gpio(arizona, "wlf,reset", true); + + ret = of_property_read_u32_array(arizona->dev->of_node, + "wlf,gpio-defaults", + arizona->pdata.gpio_defaults, + ARRAY_SIZE(arizona->pdata.gpio_defaults)); + if (ret >= 0) { + /* + * All values are literal except out of range values + * which are chip default, translate into platform + * data which uses 0 as chip default and out of range + * as zero. + */ + for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) { + if (arizona->pdata.gpio_defaults[i] > 0xffff) + arizona->pdata.gpio_defaults[i] = 0; + else if (arizona->pdata.gpio_defaults[i] == 0) + arizona->pdata.gpio_defaults[i] = 0x10000; + } + } else { + dev_err(arizona->dev, "Failed to parse GPIO defaults: %d\n", + ret); + } + + of_property_for_each_u32(arizona->dev->of_node, "wlf,inmode", prop, + cur, val) { + if (count == ARRAY_SIZE(arizona->pdata.inmode)) + break; + + arizona->pdata.inmode[count] = val; + count++; + } + + count = 0; + of_property_for_each_u32(arizona->dev->of_node, "wlf,dmic-ref", prop, + cur, val) { + if (count == ARRAY_SIZE(arizona->pdata.dmic_ref)) + break; + + arizona->pdata.dmic_ref[count] = val; + count++; + } + + return 0; +} + +const struct of_device_id arizona_of_match[] = { + { .compatible = "wlf,wm5102", .data = (void *)WM5102 }, + { .compatible = "wlf,wm5110", .data = (void *)WM5110 }, + { .compatible = "wlf,wm8280", .data = (void *)WM8280 }, + { .compatible = "wlf,wm8997", .data = (void *)WM8997 }, + {}, +}; +EXPORT_SYMBOL_GPL(arizona_of_match); +#else +static inline int arizona_of_get_core_pdata(struct arizona *arizona) +{ + return 0; +} +#endif + +static const struct mfd_cell early_devs[] = { + { .name = "arizona-ldo1" }, +}; + +static const char *wm5102_supplies[] = { + "MICVDD", + "DBVDD2", + "DBVDD3", + "CPVDD", + "SPKVDDL", + "SPKVDDR", +}; + +static const struct mfd_cell wm5102_devs[] = { + { .name = "arizona-micsupp" }, + { + .name = "arizona-extcon", + .parent_supplies = wm5102_supplies, + .num_parent_supplies = 1, /* We only need MICVDD */ + }, + { .name = "arizona-gpio" }, + { .name = "arizona-haptics" }, + { .name = "arizona-pwm" }, + { + .name = "wm5102-codec", + .parent_supplies = wm5102_supplies, + .num_parent_supplies = ARRAY_SIZE(wm5102_supplies), + }, +}; + +static const struct mfd_cell wm5110_devs[] = { + { .name = "arizona-micsupp" }, + { + .name = "arizona-extcon", + .parent_supplies = wm5102_supplies, + .num_parent_supplies = 1, /* We only need MICVDD */ + }, + { .name = "arizona-gpio" }, + { .name = "arizona-haptics" }, + { .name = "arizona-pwm" }, + { + .name = "wm5110-codec", + .parent_supplies = wm5102_supplies, + .num_parent_supplies = ARRAY_SIZE(wm5102_supplies), + }, +}; + +static const char *wm8997_supplies[] = { + "MICVDD", + "DBVDD2", + "CPVDD", + "SPKVDD", +}; + +static const struct mfd_cell wm8997_devs[] = { + { .name = "arizona-micsupp" }, + { + .name = "arizona-extcon", + .parent_supplies = wm8997_supplies, + .num_parent_supplies = 1, /* We only need MICVDD */ + }, + { .name = "arizona-gpio" }, + { .name = "arizona-haptics" }, + { .name = "arizona-pwm" }, + { + .name = "wm8997-codec", + .parent_supplies = wm8997_supplies, + .num_parent_supplies = ARRAY_SIZE(wm8997_supplies), + }, +}; + +int arizona_dev_init(struct arizona *arizona) +{ + struct device *dev = arizona->dev; + const char *type_name; + unsigned int reg, val; + int (*apply_patch)(struct arizona *) = NULL; + int ret, i; + + dev_set_drvdata(arizona->dev, arizona); + mutex_init(&arizona->clk_lock); + + if (dev_get_platdata(arizona->dev)) + memcpy(&arizona->pdata, dev_get_platdata(arizona->dev), + sizeof(arizona->pdata)); + else + arizona_of_get_core_pdata(arizona); + + regcache_cache_only(arizona->regmap, true); + + switch (arizona->type) { + case WM5102: + case WM5110: + case WM8280: + case WM8997: + for (i = 0; i < ARRAY_SIZE(wm5102_core_supplies); i++) + arizona->core_supplies[i].supply + = wm5102_core_supplies[i]; + arizona->num_core_supplies = ARRAY_SIZE(wm5102_core_supplies); + break; + default: + dev_err(arizona->dev, "Unknown device type %d\n", + arizona->type); + return -EINVAL; + } + + /* Mark DCVDD as external, LDO1 driver will clear if internal */ + arizona->external_dcvdd = true; + + ret = mfd_add_devices(arizona->dev, -1, early_devs, + ARRAY_SIZE(early_devs), NULL, 0, NULL); + if (ret != 0) { + dev_err(dev, "Failed to add early children: %d\n", ret); + return ret; + } + + ret = devm_regulator_bulk_get(dev, arizona->num_core_supplies, + arizona->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to request core supplies: %d\n", + ret); + goto err_early; + } + + /** + * Don't use devres here because the only device we have to get + * against is the MFD device and DCVDD will likely be supplied by + * one of its children. Meaning that the regulator will be + * destroyed by the time devres calls regulator put. + */ + arizona->dcvdd = regulator_get(arizona->dev, "DCVDD"); + if (IS_ERR(arizona->dcvdd)) { + ret = PTR_ERR(arizona->dcvdd); + dev_err(dev, "Failed to request DCVDD: %d\n", ret); + goto err_early; + } + + if (arizona->pdata.reset) { + /* Start out with /RESET low to put the chip into reset */ + ret = gpio_request_one(arizona->pdata.reset, + GPIOF_DIR_OUT | GPIOF_INIT_LOW, + "arizona /RESET"); + if (ret != 0) { + dev_err(dev, "Failed to request /RESET: %d\n", ret); + goto err_dcvdd; + } + } + + ret = regulator_bulk_enable(arizona->num_core_supplies, + arizona->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", + ret); + goto err_dcvdd; + } + + ret = regulator_enable(arizona->dcvdd); + if (ret != 0) { + dev_err(dev, "Failed to enable DCVDD: %d\n", ret); + goto err_enable; + } + + if (arizona->pdata.reset) { + gpio_set_value_cansleep(arizona->pdata.reset, 1); + msleep(1); + } + + regcache_cache_only(arizona->regmap, false); + + /* Verify that this is a chip we know about */ + ret = regmap_read(arizona->regmap, ARIZONA_SOFTWARE_RESET, ®); + if (ret != 0) { + dev_err(dev, "Failed to read ID register: %d\n", ret); + goto err_reset; + } + + switch (reg) { + case 0x5102: + case 0x5110: + case 0x8997: + break; + default: + dev_err(arizona->dev, "Unknown device ID: %x\n", reg); + goto err_reset; + } + + /* If we have a /RESET GPIO we'll already be reset */ + if (!arizona->pdata.reset) { + regcache_mark_dirty(arizona->regmap); + + ret = regmap_write(arizona->regmap, ARIZONA_SOFTWARE_RESET, 0); + if (ret != 0) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_reset; + } + + msleep(1); + + ret = regcache_sync(arizona->regmap); + if (ret != 0) { + dev_err(dev, "Failed to sync device: %d\n", ret); + goto err_reset; + } + } + + /* Ensure device startup is complete */ + switch (arizona->type) { + case WM5102: + ret = regmap_read(arizona->regmap, + ARIZONA_WRITE_SEQUENCER_CTRL_3, &val); + if (ret != 0) + dev_err(dev, + "Failed to check write sequencer state: %d\n", + ret); + else if (val & 0x01) + break; + /* Fall through */ + default: + ret = arizona_wait_for_boot(arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Device failed initial boot: %d\n", ret); + goto err_reset; + } + break; + } + + /* Read the device ID information & do device specific stuff */ + ret = regmap_read(arizona->regmap, ARIZONA_SOFTWARE_RESET, ®); + if (ret != 0) { + dev_err(dev, "Failed to read ID register: %d\n", ret); + goto err_reset; + } + + ret = regmap_read(arizona->regmap, ARIZONA_DEVICE_REVISION, + &arizona->rev); + if (ret != 0) { + dev_err(dev, "Failed to read revision register: %d\n", ret); + goto err_reset; + } + arizona->rev &= ARIZONA_DEVICE_REVISION_MASK; + + switch (reg) { +#ifdef CONFIG_MFD_WM5102 + case 0x5102: + type_name = "WM5102"; + if (arizona->type != WM5102) { + dev_err(arizona->dev, "WM5102 registered as %d\n", + arizona->type); + arizona->type = WM5102; + } + apply_patch = wm5102_patch; + arizona->rev &= 0x7; + break; +#endif +#ifdef CONFIG_MFD_WM5110 + case 0x5110: + switch (arizona->type) { + case WM5110: + type_name = "WM5110"; + break; + case WM8280: + type_name = "WM8280"; + break; + default: + type_name = "WM5110"; + dev_err(arizona->dev, "WM5110 registered as %d\n", + arizona->type); + arizona->type = WM5110; + break; + } + apply_patch = wm5110_patch; + break; +#endif +#ifdef CONFIG_MFD_WM8997 + case 0x8997: + type_name = "WM8997"; + if (arizona->type != WM8997) { + dev_err(arizona->dev, "WM8997 registered as %d\n", + arizona->type); + arizona->type = WM8997; + } + apply_patch = wm8997_patch; + break; +#endif + default: + dev_err(arizona->dev, "Unknown device ID %x\n", reg); + goto err_reset; + } + + dev_info(dev, "%s revision %c\n", type_name, arizona->rev + 'A'); + + if (apply_patch) { + ret = apply_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, "Failed to apply patch: %d\n", + ret); + goto err_reset; + } + + switch (arizona->type) { + case WM5102: + ret = arizona_apply_hardware_patch(arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to apply hardware patch: %d\n", + ret); + goto err_reset; + } + break; + default: + break; + } + } + + for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) { + if (!arizona->pdata.gpio_defaults[i]) + continue; + + regmap_write(arizona->regmap, ARIZONA_GPIO1_CTRL + i, + arizona->pdata.gpio_defaults[i]); + } + + pm_runtime_set_autosuspend_delay(arizona->dev, 100); + pm_runtime_use_autosuspend(arizona->dev); + pm_runtime_enable(arizona->dev); + + /* Chip default */ + if (!arizona->pdata.clk32k_src) + arizona->pdata.clk32k_src = ARIZONA_32KZ_MCLK2; + + switch (arizona->pdata.clk32k_src) { + case ARIZONA_32KZ_MCLK1: + case ARIZONA_32KZ_MCLK2: + regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, + ARIZONA_CLK_32K_SRC_MASK, + arizona->pdata.clk32k_src - 1); + arizona_clk32k_enable(arizona); + break; + case ARIZONA_32KZ_NONE: + regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1, + ARIZONA_CLK_32K_SRC_MASK, 2); + break; + default: + dev_err(arizona->dev, "Invalid 32kHz clock source: %d\n", + arizona->pdata.clk32k_src); + ret = -EINVAL; + goto err_reset; + } + + for (i = 0; i < ARIZONA_MAX_MICBIAS; i++) { + if (!arizona->pdata.micbias[i].mV && + !arizona->pdata.micbias[i].bypass) + continue; + + /* Apply default for bypass mode */ + if (!arizona->pdata.micbias[i].mV) + arizona->pdata.micbias[i].mV = 2800; + + val = (arizona->pdata.micbias[i].mV - 1500) / 100; + + val <<= ARIZONA_MICB1_LVL_SHIFT; + + if (arizona->pdata.micbias[i].ext_cap) + val |= ARIZONA_MICB1_EXT_CAP; + + if (arizona->pdata.micbias[i].discharge) + val |= ARIZONA_MICB1_DISCH; + + if (arizona->pdata.micbias[i].soft_start) + val |= ARIZONA_MICB1_RATE; + + if (arizona->pdata.micbias[i].bypass) + val |= ARIZONA_MICB1_BYPASS; + + regmap_update_bits(arizona->regmap, + ARIZONA_MIC_BIAS_CTRL_1 + i, + ARIZONA_MICB1_LVL_MASK | + ARIZONA_MICB1_EXT_CAP | + ARIZONA_MICB1_DISCH | + ARIZONA_MICB1_BYPASS | + ARIZONA_MICB1_RATE, val); + } + + for (i = 0; i < ARIZONA_MAX_INPUT; i++) { + /* Default for both is 0 so noop with defaults */ + val = arizona->pdata.dmic_ref[i] + << ARIZONA_IN1_DMIC_SUP_SHIFT; + val |= arizona->pdata.inmode[i] << ARIZONA_IN1_MODE_SHIFT; + + regmap_update_bits(arizona->regmap, + ARIZONA_IN1L_CONTROL + (i * 8), + ARIZONA_IN1_DMIC_SUP_MASK | + ARIZONA_IN1_MODE_MASK, val); + } + + for (i = 0; i < ARIZONA_MAX_OUTPUT; i++) { + /* Default is 0 so noop with defaults */ + if (arizona->pdata.out_mono[i]) + val = ARIZONA_OUT1_MONO; + else + val = 0; + + regmap_update_bits(arizona->regmap, + ARIZONA_OUTPUT_PATH_CONFIG_1L + (i * 8), + ARIZONA_OUT1_MONO, val); + } + + for (i = 0; i < ARIZONA_MAX_PDM_SPK; i++) { + if (arizona->pdata.spk_mute[i]) + regmap_update_bits(arizona->regmap, + ARIZONA_PDM_SPK1_CTRL_1 + (i * 2), + ARIZONA_SPK1_MUTE_ENDIAN_MASK | + ARIZONA_SPK1_MUTE_SEQ1_MASK, + arizona->pdata.spk_mute[i]); + + if (arizona->pdata.spk_fmt[i]) + regmap_update_bits(arizona->regmap, + ARIZONA_PDM_SPK1_CTRL_2 + (i * 2), + ARIZONA_SPK1_FMT_MASK, + arizona->pdata.spk_fmt[i]); + } + + /* Set up for interrupts */ + ret = arizona_irq_init(arizona); + if (ret != 0) + goto err_reset; + + arizona_request_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, "CLKGEN error", + arizona_clkgen_err, arizona); + arizona_request_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, "Overclocked", + arizona_overclocked, arizona); + arizona_request_irq(arizona, ARIZONA_IRQ_UNDERCLOCKED, "Underclocked", + arizona_underclocked, arizona); + + switch (arizona->type) { + case WM5102: + ret = mfd_add_devices(arizona->dev, -1, wm5102_devs, + ARRAY_SIZE(wm5102_devs), NULL, 0, NULL); + break; + case WM5110: + case WM8280: + ret = mfd_add_devices(arizona->dev, -1, wm5110_devs, + ARRAY_SIZE(wm5110_devs), NULL, 0, NULL); + break; + case WM8997: + ret = mfd_add_devices(arizona->dev, -1, wm8997_devs, + ARRAY_SIZE(wm8997_devs), NULL, 0, NULL); + break; + } + + if (ret != 0) { + dev_err(arizona->dev, "Failed to add subdevices: %d\n", ret); + goto err_irq; + } + +#ifdef CONFIG_PM + regulator_disable(arizona->dcvdd); +#endif + + return 0; + +err_irq: + arizona_irq_exit(arizona); +err_reset: + if (arizona->pdata.reset) { + gpio_set_value_cansleep(arizona->pdata.reset, 0); + gpio_free(arizona->pdata.reset); + } + regulator_disable(arizona->dcvdd); +err_enable: + regulator_bulk_disable(arizona->num_core_supplies, + arizona->core_supplies); +err_dcvdd: + regulator_put(arizona->dcvdd); +err_early: + mfd_remove_devices(dev); + return ret; +} +EXPORT_SYMBOL_GPL(arizona_dev_init); + +int arizona_dev_exit(struct arizona *arizona) +{ + pm_runtime_disable(arizona->dev); + + regulator_disable(arizona->dcvdd); + regulator_put(arizona->dcvdd); + + mfd_remove_devices(arizona->dev); + arizona_free_irq(arizona, ARIZONA_IRQ_UNDERCLOCKED, arizona); + arizona_free_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, arizona); + arizona_free_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, arizona); + arizona_irq_exit(arizona); + if (arizona->pdata.reset) + gpio_set_value_cansleep(arizona->pdata.reset, 0); + + regulator_bulk_disable(arizona->num_core_supplies, + arizona->core_supplies); + return 0; +} +EXPORT_SYMBOL_GPL(arizona_dev_exit); diff --git a/kernel/drivers/mfd/arizona-i2c.c b/kernel/drivers/mfd/arizona-i2c.c new file mode 100644 index 000000000..ff782a5de --- /dev/null +++ b/kernel/drivers/mfd/arizona-i2c.c @@ -0,0 +1,113 @@ +/* + * Arizona-i2c.c -- Arizona I2C bus interface + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/of.h> + +#include <linux/mfd/arizona/core.h> + +#include "arizona.h" + +static int arizona_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct arizona *arizona; + const struct regmap_config *regmap_config; + unsigned long type; + int ret; + + if (i2c->dev.of_node) + type = arizona_of_get_type(&i2c->dev); + else + type = id->driver_data; + + switch (type) { +#ifdef CONFIG_MFD_WM5102 + case WM5102: + regmap_config = &wm5102_i2c_regmap; + break; +#endif +#ifdef CONFIG_MFD_WM5110 + case WM5110: + case WM8280: + regmap_config = &wm5110_i2c_regmap; + break; +#endif +#ifdef CONFIG_MFD_WM8997 + case WM8997: + regmap_config = &wm8997_i2c_regmap; + break; +#endif + default: + dev_err(&i2c->dev, "Unknown device type %ld\n", + id->driver_data); + return -EINVAL; + } + + arizona = devm_kzalloc(&i2c->dev, sizeof(*arizona), GFP_KERNEL); + if (arizona == NULL) + return -ENOMEM; + + arizona->regmap = devm_regmap_init_i2c(i2c, regmap_config); + if (IS_ERR(arizona->regmap)) { + ret = PTR_ERR(arizona->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + arizona->type = id->driver_data; + arizona->dev = &i2c->dev; + arizona->irq = i2c->irq; + + return arizona_dev_init(arizona); +} + +static int arizona_i2c_remove(struct i2c_client *i2c) +{ + struct arizona *arizona = dev_get_drvdata(&i2c->dev); + arizona_dev_exit(arizona); + return 0; +} + +static const struct i2c_device_id arizona_i2c_id[] = { + { "wm5102", WM5102 }, + { "wm5110", WM5110 }, + { "wm8280", WM8280 }, + { "wm8997", WM8997 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, arizona_i2c_id); + +static struct i2c_driver arizona_i2c_driver = { + .driver = { + .name = "arizona", + .owner = THIS_MODULE, + .pm = &arizona_pm_ops, + .of_match_table = of_match_ptr(arizona_of_match), + }, + .probe = arizona_i2c_probe, + .remove = arizona_i2c_remove, + .id_table = arizona_i2c_id, +}; + +module_i2c_driver(arizona_i2c_driver); + +MODULE_DESCRIPTION("Arizona I2C bus interface"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/arizona-irq.c b/kernel/drivers/mfd/arizona-irq.c new file mode 100644 index 000000000..d063b94b9 --- /dev/null +++ b/kernel/drivers/mfd/arizona-irq.c @@ -0,0 +1,395 @@ +/* + * Arizona interrupt support + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/registers.h> + +#include "arizona.h" + +static int arizona_map_irq(struct arizona *arizona, int irq) +{ + int ret; + + ret = regmap_irq_get_virq(arizona->aod_irq_chip, irq); + if (ret < 0) + ret = regmap_irq_get_virq(arizona->irq_chip, irq); + + return ret; +} + +int arizona_request_irq(struct arizona *arizona, int irq, char *name, + irq_handler_t handler, void *data) +{ + irq = arizona_map_irq(arizona, irq); + if (irq < 0) + return irq; + + return request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, + name, data); +} +EXPORT_SYMBOL_GPL(arizona_request_irq); + +void arizona_free_irq(struct arizona *arizona, int irq, void *data) +{ + irq = arizona_map_irq(arizona, irq); + if (irq < 0) + return; + + free_irq(irq, data); +} +EXPORT_SYMBOL_GPL(arizona_free_irq); + +int arizona_set_irq_wake(struct arizona *arizona, int irq, int on) +{ + irq = arizona_map_irq(arizona, irq); + if (irq < 0) + return irq; + + return irq_set_irq_wake(irq, on); +} +EXPORT_SYMBOL_GPL(arizona_set_irq_wake); + +static irqreturn_t arizona_boot_done(int irq, void *data) +{ + struct arizona *arizona = data; + + dev_dbg(arizona->dev, "Boot done\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t arizona_ctrlif_err(int irq, void *data) +{ + struct arizona *arizona = data; + + /* + * For pretty much all potential sources a register cache sync + * won't help, we've just got a software bug somewhere. + */ + dev_err(arizona->dev, "Control interface error\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t arizona_irq_thread(int irq, void *data) +{ + struct arizona *arizona = data; + bool poll; + unsigned int val; + int ret; + + ret = pm_runtime_get_sync(arizona->dev); + if (ret < 0) { + dev_err(arizona->dev, "Failed to resume device: %d\n", ret); + return IRQ_NONE; + } + + do { + poll = false; + + /* Always handle the AoD domain */ + handle_nested_irq(irq_find_mapping(arizona->virq, 0)); + + /* + * Check if one of the main interrupts is asserted and only + * check that domain if it is. + */ + ret = regmap_read(arizona->regmap, ARIZONA_IRQ_PIN_STATUS, + &val); + if (ret == 0 && val & ARIZONA_IRQ1_STS) { + handle_nested_irq(irq_find_mapping(arizona->virq, 1)); + } else if (ret != 0) { + dev_err(arizona->dev, + "Failed to read main IRQ status: %d\n", ret); + } + + /* + * Poll the IRQ pin status to see if we're really done + * if the interrupt controller can't do it for us. + */ + if (!arizona->pdata.irq_gpio) { + break; + } else if (arizona->pdata.irq_flags & IRQF_TRIGGER_RISING && + gpio_get_value_cansleep(arizona->pdata.irq_gpio)) { + poll = true; + } else if (arizona->pdata.irq_flags & IRQF_TRIGGER_FALLING && + !gpio_get_value_cansleep(arizona->pdata.irq_gpio)) { + poll = true; + } + } while (poll); + + pm_runtime_mark_last_busy(arizona->dev); + pm_runtime_put_autosuspend(arizona->dev); + + return IRQ_HANDLED; +} + +static void arizona_irq_enable(struct irq_data *data) +{ +} + +static void arizona_irq_disable(struct irq_data *data) +{ +} + +static int arizona_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct arizona *arizona = irq_data_get_irq_chip_data(data); + + return irq_set_irq_wake(arizona->irq, on); +} + +static struct irq_chip arizona_irq_chip = { + .name = "arizona", + .irq_disable = arizona_irq_disable, + .irq_enable = arizona_irq_enable, + .irq_set_wake = arizona_irq_set_wake, +}; + +static int arizona_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct regmap_irq_chip_data *data = h->host_data; + + irq_set_chip_data(virq, data); + irq_set_chip_and_handler(virq, &arizona_irq_chip, handle_simple_irq); + irq_set_nested_thread(virq, 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(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static struct irq_domain_ops arizona_domain_ops = { + .map = arizona_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +int arizona_irq_init(struct arizona *arizona) +{ + int flags = IRQF_ONESHOT; + int ret, i; + const struct regmap_irq_chip *aod, *irq; + struct irq_data *irq_data; + + arizona->ctrlif_error = true; + + switch (arizona->type) { +#ifdef CONFIG_MFD_WM5102 + case WM5102: + aod = &wm5102_aod; + irq = &wm5102_irq; + + arizona->ctrlif_error = false; + break; +#endif +#ifdef CONFIG_MFD_WM5110 + case WM5110: + case WM8280: + aod = &wm5110_aod; + + switch (arizona->rev) { + case 0 ... 2: + irq = &wm5110_irq; + break; + default: + irq = &wm5110_revd_irq; + break; + } + + arizona->ctrlif_error = false; + break; +#endif +#ifdef CONFIG_MFD_WM8997 + case WM8997: + aod = &wm8997_aod; + irq = &wm8997_irq; + + arizona->ctrlif_error = false; + break; +#endif + default: + BUG_ON("Unknown Arizona class device" == NULL); + return -EINVAL; + } + + /* Disable all wake sources by default */ + regmap_write(arizona->regmap, ARIZONA_WAKE_CONTROL, 0); + + /* Read the flags from the interrupt controller if not specified */ + if (!arizona->pdata.irq_flags) { + irq_data = irq_get_irq_data(arizona->irq); + if (!irq_data) { + dev_err(arizona->dev, "Invalid IRQ: %d\n", + arizona->irq); + return -EINVAL; + } + + arizona->pdata.irq_flags = irqd_get_trigger_type(irq_data); + switch (arizona->pdata.irq_flags) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_FALLING: + break; + + case IRQ_TYPE_NONE: + default: + /* Device default */ + arizona->pdata.irq_flags = IRQF_TRIGGER_LOW; + break; + } + } + + if (arizona->pdata.irq_flags & (IRQF_TRIGGER_HIGH | + IRQF_TRIGGER_RISING)) { + ret = regmap_update_bits(arizona->regmap, ARIZONA_IRQ_CTRL_1, + ARIZONA_IRQ_POL, 0); + if (ret != 0) { + dev_err(arizona->dev, "Couldn't set IRQ polarity: %d\n", + ret); + goto err; + } + } + + flags |= arizona->pdata.irq_flags; + + /* Allocate a virtual IRQ domain to distribute to the regmap domains */ + arizona->virq = irq_domain_add_linear(NULL, 2, &arizona_domain_ops, + arizona); + if (!arizona->virq) { + dev_err(arizona->dev, "Failed to add core IRQ domain\n"); + ret = -EINVAL; + goto err; + } + + ret = regmap_add_irq_chip(arizona->regmap, + irq_create_mapping(arizona->virq, 0), + IRQF_ONESHOT, 0, aod, + &arizona->aod_irq_chip); + if (ret != 0) { + dev_err(arizona->dev, "Failed to add AOD IRQs: %d\n", ret); + goto err_domain; + } + + ret = regmap_add_irq_chip(arizona->regmap, + irq_create_mapping(arizona->virq, 1), + IRQF_ONESHOT, 0, irq, + &arizona->irq_chip); + if (ret != 0) { + dev_err(arizona->dev, "Failed to add main IRQs: %d\n", ret); + goto err_aod; + } + + /* Make sure the boot done IRQ is unmasked for resumes */ + i = arizona_map_irq(arizona, ARIZONA_IRQ_BOOT_DONE); + ret = request_threaded_irq(i, NULL, arizona_boot_done, IRQF_ONESHOT, + "Boot done", arizona); + if (ret != 0) { + dev_err(arizona->dev, "Failed to request boot done %d: %d\n", + arizona->irq, ret); + goto err_boot_done; + } + + /* Handle control interface errors in the core */ + if (arizona->ctrlif_error) { + i = arizona_map_irq(arizona, ARIZONA_IRQ_CTRLIF_ERR); + ret = request_threaded_irq(i, NULL, arizona_ctrlif_err, + IRQF_ONESHOT, + "Control interface error", arizona); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to request CTRLIF_ERR %d: %d\n", + arizona->irq, ret); + goto err_ctrlif; + } + } + + /* Used to emulate edge trigger and to work around broken pinmux */ + if (arizona->pdata.irq_gpio) { + if (gpio_to_irq(arizona->pdata.irq_gpio) != arizona->irq) { + dev_warn(arizona->dev, "IRQ %d is not GPIO %d (%d)\n", + arizona->irq, arizona->pdata.irq_gpio, + gpio_to_irq(arizona->pdata.irq_gpio)); + arizona->irq = gpio_to_irq(arizona->pdata.irq_gpio); + } + + ret = devm_gpio_request_one(arizona->dev, + arizona->pdata.irq_gpio, + GPIOF_IN, "arizona IRQ"); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to request IRQ GPIO %d:: %d\n", + arizona->pdata.irq_gpio, ret); + arizona->pdata.irq_gpio = 0; + } + } + + ret = request_threaded_irq(arizona->irq, NULL, arizona_irq_thread, + flags, "arizona", arizona); + + if (ret != 0) { + dev_err(arizona->dev, "Failed to request primary IRQ %d: %d\n", + arizona->irq, ret); + goto err_main_irq; + } + + return 0; + +err_main_irq: + if (arizona->ctrlif_error) + free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_CTRLIF_ERR), + arizona); +err_ctrlif: + free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_BOOT_DONE), arizona); +err_boot_done: + regmap_del_irq_chip(irq_create_mapping(arizona->virq, 1), + arizona->irq_chip); +err_aod: + regmap_del_irq_chip(irq_create_mapping(arizona->virq, 0), + arizona->aod_irq_chip); +err_domain: +err: + return ret; +} + +int arizona_irq_exit(struct arizona *arizona) +{ + if (arizona->ctrlif_error) + free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_CTRLIF_ERR), + arizona); + free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_BOOT_DONE), arizona); + regmap_del_irq_chip(irq_create_mapping(arizona->virq, 1), + arizona->irq_chip); + regmap_del_irq_chip(irq_create_mapping(arizona->virq, 0), + arizona->aod_irq_chip); + free_irq(arizona->irq, arizona); + + return 0; +} diff --git a/kernel/drivers/mfd/arizona-spi.c b/kernel/drivers/mfd/arizona-spi.c new file mode 100644 index 000000000..1e845f6d4 --- /dev/null +++ b/kernel/drivers/mfd/arizona-spi.c @@ -0,0 +1,109 @@ +/* + * arizona-spi.c -- Arizona SPI bus interface + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/of.h> + +#include <linux/mfd/arizona/core.h> + +#include "arizona.h" + +static int arizona_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct arizona *arizona; + const struct regmap_config *regmap_config; + unsigned long type; + int ret; + + if (spi->dev.of_node) + type = arizona_of_get_type(&spi->dev); + else + type = id->driver_data; + + switch (type) { +#ifdef CONFIG_MFD_WM5102 + case WM5102: + regmap_config = &wm5102_spi_regmap; + break; +#endif +#ifdef CONFIG_MFD_WM5110 + case WM5110: + case WM8280: + regmap_config = &wm5110_spi_regmap; + break; +#endif + default: + dev_err(&spi->dev, "Unknown device type %ld\n", + id->driver_data); + return -EINVAL; + } + + arizona = devm_kzalloc(&spi->dev, sizeof(*arizona), GFP_KERNEL); + if (arizona == NULL) + return -ENOMEM; + + arizona->regmap = devm_regmap_init_spi(spi, regmap_config); + if (IS_ERR(arizona->regmap)) { + ret = PTR_ERR(arizona->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + arizona->type = id->driver_data; + arizona->dev = &spi->dev; + arizona->irq = spi->irq; + + return arizona_dev_init(arizona); +} + +static int arizona_spi_remove(struct spi_device *spi) +{ + struct arizona *arizona = spi_get_drvdata(spi); + + arizona_dev_exit(arizona); + + return 0; +} + +static const struct spi_device_id arizona_spi_ids[] = { + { "wm5102", WM5102 }, + { "wm5110", WM5110 }, + { "wm8280", WM8280 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, arizona_spi_ids); + +static struct spi_driver arizona_spi_driver = { + .driver = { + .name = "arizona", + .owner = THIS_MODULE, + .pm = &arizona_pm_ops, + .of_match_table = of_match_ptr(arizona_of_match), + }, + .probe = arizona_spi_probe, + .remove = arizona_spi_remove, + .id_table = arizona_spi_ids, +}; + +module_spi_driver(arizona_spi_driver); + +MODULE_DESCRIPTION("Arizona SPI bus interface"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/arizona.h b/kernel/drivers/mfd/arizona.h new file mode 100644 index 000000000..fbe284327 --- /dev/null +++ b/kernel/drivers/mfd/arizona.h @@ -0,0 +1,58 @@ +/* + * wm5102.h -- WM5102 MFD internals + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM5102_H +#define _WM5102_H + +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/pm.h> + +struct wm_arizona; + +extern const struct regmap_config wm5102_i2c_regmap; +extern const struct regmap_config wm5102_spi_regmap; + +extern const struct regmap_config wm5110_i2c_regmap; +extern const struct regmap_config wm5110_spi_regmap; + +extern const struct regmap_config wm8997_i2c_regmap; + +extern const struct dev_pm_ops arizona_pm_ops; + +extern const struct of_device_id arizona_of_match[]; + +extern const struct regmap_irq_chip wm5102_aod; +extern const struct regmap_irq_chip wm5102_irq; + +extern const struct regmap_irq_chip wm5110_aod; +extern const struct regmap_irq_chip wm5110_irq; +extern const struct regmap_irq_chip wm5110_revd_irq; + +extern const struct regmap_irq_chip wm8997_aod; +extern const struct regmap_irq_chip wm8997_irq; + +int arizona_dev_init(struct arizona *arizona); +int arizona_dev_exit(struct arizona *arizona); +int arizona_irq_init(struct arizona *arizona); +int arizona_irq_exit(struct arizona *arizona); + +#ifdef CONFIG_OF +unsigned long arizona_of_get_type(struct device *dev); +#else +static inline unsigned long arizona_of_get_type(struct device *dev) +{ + return 0; +} +#endif + +#endif diff --git a/kernel/drivers/mfd/as3711.c b/kernel/drivers/mfd/as3711.c new file mode 100644 index 000000000..d9706ede8 --- /dev/null +++ b/kernel/drivers/mfd/as3711.c @@ -0,0 +1,237 @@ +/* + * AS3711 PMIC MFC driver + * + * Copyright (C) 2012 Renesas Electronics Corporation + * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License as + * published by the Free Software Foundation + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mfd/as3711.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +enum { + AS3711_REGULATOR, + AS3711_BACKLIGHT, +}; + +/* + * Ok to have it static: it is only used during probing and multiple I2C devices + * cannot be probed simultaneously. Just make sure to avoid stale data. + */ +static struct mfd_cell as3711_subdevs[] = { + [AS3711_REGULATOR] = {.name = "as3711-regulator",}, + [AS3711_BACKLIGHT] = {.name = "as3711-backlight",}, +}; + +static bool as3711_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AS3711_GPIO_SIGNAL_IN: + case AS3711_INTERRUPT_STATUS_1: + case AS3711_INTERRUPT_STATUS_2: + case AS3711_INTERRUPT_STATUS_3: + case AS3711_CHARGER_STATUS_1: + case AS3711_CHARGER_STATUS_2: + case AS3711_REG_STATUS: + return true; + } + return false; +} + +static bool as3711_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AS3711_INTERRUPT_STATUS_1: + case AS3711_INTERRUPT_STATUS_2: + case AS3711_INTERRUPT_STATUS_3: + return true; + } + return false; +} + +static bool as3711_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AS3711_SD_1_VOLTAGE: + case AS3711_SD_2_VOLTAGE: + case AS3711_SD_3_VOLTAGE: + case AS3711_SD_4_VOLTAGE: + case AS3711_LDO_1_VOLTAGE: + case AS3711_LDO_2_VOLTAGE: + case AS3711_LDO_3_VOLTAGE: + case AS3711_LDO_4_VOLTAGE: + case AS3711_LDO_5_VOLTAGE: + case AS3711_LDO_6_VOLTAGE: + case AS3711_LDO_7_VOLTAGE: + case AS3711_LDO_8_VOLTAGE: + case AS3711_SD_CONTROL: + case AS3711_GPIO_SIGNAL_OUT: + case AS3711_GPIO_SIGNAL_IN: + case AS3711_SD_CONTROL_1: + case AS3711_SD_CONTROL_2: + case AS3711_CURR_CONTROL: + case AS3711_CURR1_VALUE: + case AS3711_CURR2_VALUE: + case AS3711_CURR3_VALUE: + case AS3711_STEPUP_CONTROL_1: + case AS3711_STEPUP_CONTROL_2: + case AS3711_STEPUP_CONTROL_4: + case AS3711_STEPUP_CONTROL_5: + case AS3711_REG_STATUS: + case AS3711_INTERRUPT_STATUS_1: + case AS3711_INTERRUPT_STATUS_2: + case AS3711_INTERRUPT_STATUS_3: + case AS3711_CHARGER_STATUS_1: + case AS3711_CHARGER_STATUS_2: + case AS3711_ASIC_ID_1: + case AS3711_ASIC_ID_2: + return true; + } + return false; +} + +static const struct regmap_config as3711_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = as3711_volatile_reg, + .readable_reg = as3711_readable_reg, + .precious_reg = as3711_precious_reg, + .max_register = AS3711_MAX_REGS, + .num_reg_defaults_raw = AS3711_MAX_REGS, + .cache_type = REGCACHE_RBTREE, +}; + +#ifdef CONFIG_OF +static const struct of_device_id as3711_of_match[] = { + {.compatible = "ams,as3711",}, + {} +}; +MODULE_DEVICE_TABLE(of, as3711_of_match); +#endif + +static int as3711_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct as3711 *as3711; + struct as3711_platform_data *pdata; + unsigned int id1, id2; + int ret; + + if (!client->dev.of_node) { + pdata = dev_get_platdata(&client->dev); + if (!pdata) + dev_dbg(&client->dev, "Platform data not found\n"); + } else { + pdata = devm_kzalloc(&client->dev, + sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Failed to allocate pdata\n"); + return -ENOMEM; + } + } + + as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL); + if (!as3711) { + dev_err(&client->dev, "Memory allocation failed\n"); + return -ENOMEM; + } + + as3711->dev = &client->dev; + i2c_set_clientdata(client, as3711); + + if (client->irq) + dev_notice(&client->dev, "IRQ not supported yet\n"); + + as3711->regmap = devm_regmap_init_i2c(client, &as3711_regmap_config); + if (IS_ERR(as3711->regmap)) { + ret = PTR_ERR(as3711->regmap); + dev_err(&client->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + ret = regmap_read(as3711->regmap, AS3711_ASIC_ID_1, &id1); + if (!ret) + ret = regmap_read(as3711->regmap, AS3711_ASIC_ID_2, &id2); + if (ret < 0) { + dev_err(&client->dev, "regmap_read() failed: %d\n", ret); + return ret; + } + if (id1 != 0x8b) + return -ENODEV; + dev_info(as3711->dev, "AS3711 detected: %x:%x\n", id1, id2); + + /* We can reuse as3711_subdevs[], it will be copied in mfd_add_devices() */ + if (pdata) { + as3711_subdevs[AS3711_REGULATOR].platform_data = &pdata->regulator; + as3711_subdevs[AS3711_REGULATOR].pdata_size = sizeof(pdata->regulator); + as3711_subdevs[AS3711_BACKLIGHT].platform_data = &pdata->backlight; + as3711_subdevs[AS3711_BACKLIGHT].pdata_size = sizeof(pdata->backlight); + } else { + as3711_subdevs[AS3711_REGULATOR].platform_data = NULL; + as3711_subdevs[AS3711_REGULATOR].pdata_size = 0; + as3711_subdevs[AS3711_BACKLIGHT].platform_data = NULL; + as3711_subdevs[AS3711_BACKLIGHT].pdata_size = 0; + } + + ret = mfd_add_devices(as3711->dev, -1, as3711_subdevs, + ARRAY_SIZE(as3711_subdevs), NULL, 0, NULL); + if (ret < 0) + dev_err(&client->dev, "add mfd devices failed: %d\n", ret); + + return ret; +} + +static int as3711_i2c_remove(struct i2c_client *client) +{ + struct as3711 *as3711 = i2c_get_clientdata(client); + + mfd_remove_devices(as3711->dev); + return 0; +} + +static const struct i2c_device_id as3711_i2c_id[] = { + {.name = "as3711", .driver_data = 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, as3711_i2c_id); + +static struct i2c_driver as3711_i2c_driver = { + .driver = { + .name = "as3711", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(as3711_of_match), + }, + .probe = as3711_i2c_probe, + .remove = as3711_i2c_remove, + .id_table = as3711_i2c_id, +}; + +static int __init as3711_i2c_init(void) +{ + return i2c_add_driver(&as3711_i2c_driver); +} +/* Initialise early */ +subsys_initcall(as3711_i2c_init); + +static void __exit as3711_i2c_exit(void) +{ + i2c_del_driver(&as3711_i2c_driver); +} +module_exit(as3711_i2c_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("AS3711 PMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/as3722.c b/kernel/drivers/mfd/as3722.c new file mode 100644 index 000000000..39fa554f1 --- /dev/null +++ b/kernel/drivers/mfd/as3722.c @@ -0,0 +1,453 @@ +/* + * Core driver for ams AS3722 PMICs + * + * Copyright (C) 2013 AMS AG + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. + * + * Author: Florian Lobmaier <florian.lobmaier@ams.com> + * Author: Laxman Dewangan <ldewangan@nvidia.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/mfd/as3722.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define AS3722_DEVICE_ID 0x0C + +static const struct resource as3722_rtc_resource[] = { + { + .name = "as3722-rtc-alarm", + .start = AS3722_IRQ_RTC_ALARM, + .end = AS3722_IRQ_RTC_ALARM, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource as3722_adc_resource[] = { + { + .name = "as3722-adc", + .start = AS3722_IRQ_ADC, + .end = AS3722_IRQ_ADC, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell as3722_devs[] = { + { + .name = "as3722-pinctrl", + }, + { + .name = "as3722-regulator", + }, + { + .name = "as3722-rtc", + .num_resources = ARRAY_SIZE(as3722_rtc_resource), + .resources = as3722_rtc_resource, + }, + { + .name = "as3722-adc", + .num_resources = ARRAY_SIZE(as3722_adc_resource), + .resources = as3722_adc_resource, + }, + { + .name = "as3722-power-off", + }, + { + .name = "as3722-wdt", + }, +}; + +static const struct regmap_irq as3722_irqs[] = { + /* INT1 IRQs */ + [AS3722_IRQ_LID] = { + .mask = AS3722_INTERRUPT_MASK1_LID, + }, + [AS3722_IRQ_ACOK] = { + .mask = AS3722_INTERRUPT_MASK1_ACOK, + }, + [AS3722_IRQ_ENABLE1] = { + .mask = AS3722_INTERRUPT_MASK1_ENABLE1, + }, + [AS3722_IRQ_OCCUR_ALARM_SD0] = { + .mask = AS3722_INTERRUPT_MASK1_OCURR_ALARM_SD0, + }, + [AS3722_IRQ_ONKEY_LONG_PRESS] = { + .mask = AS3722_INTERRUPT_MASK1_ONKEY_LONG, + }, + [AS3722_IRQ_ONKEY] = { + .mask = AS3722_INTERRUPT_MASK1_ONKEY, + }, + [AS3722_IRQ_OVTMP] = { + .mask = AS3722_INTERRUPT_MASK1_OVTMP, + }, + [AS3722_IRQ_LOWBAT] = { + .mask = AS3722_INTERRUPT_MASK1_LOWBAT, + }, + + /* INT2 IRQs */ + [AS3722_IRQ_SD0_LV] = { + .mask = AS3722_INTERRUPT_MASK2_SD0_LV, + .reg_offset = 1, + }, + [AS3722_IRQ_SD1_LV] = { + .mask = AS3722_INTERRUPT_MASK2_SD1_LV, + .reg_offset = 1, + }, + [AS3722_IRQ_SD2_LV] = { + .mask = AS3722_INTERRUPT_MASK2_SD2345_LV, + .reg_offset = 1, + }, + [AS3722_IRQ_PWM1_OV_PROT] = { + .mask = AS3722_INTERRUPT_MASK2_PWM1_OV_PROT, + .reg_offset = 1, + }, + [AS3722_IRQ_PWM2_OV_PROT] = { + .mask = AS3722_INTERRUPT_MASK2_PWM2_OV_PROT, + .reg_offset = 1, + }, + [AS3722_IRQ_ENABLE2] = { + .mask = AS3722_INTERRUPT_MASK2_ENABLE2, + .reg_offset = 1, + }, + [AS3722_IRQ_SD6_LV] = { + .mask = AS3722_INTERRUPT_MASK2_SD6_LV, + .reg_offset = 1, + }, + [AS3722_IRQ_RTC_REP] = { + .mask = AS3722_INTERRUPT_MASK2_RTC_REP, + .reg_offset = 1, + }, + + /* INT3 IRQs */ + [AS3722_IRQ_RTC_ALARM] = { + .mask = AS3722_INTERRUPT_MASK3_RTC_ALARM, + .reg_offset = 2, + }, + [AS3722_IRQ_GPIO1] = { + .mask = AS3722_INTERRUPT_MASK3_GPIO1, + .reg_offset = 2, + }, + [AS3722_IRQ_GPIO2] = { + .mask = AS3722_INTERRUPT_MASK3_GPIO2, + .reg_offset = 2, + }, + [AS3722_IRQ_GPIO3] = { + .mask = AS3722_INTERRUPT_MASK3_GPIO3, + .reg_offset = 2, + }, + [AS3722_IRQ_GPIO4] = { + .mask = AS3722_INTERRUPT_MASK3_GPIO4, + .reg_offset = 2, + }, + [AS3722_IRQ_GPIO5] = { + .mask = AS3722_INTERRUPT_MASK3_GPIO5, + .reg_offset = 2, + }, + [AS3722_IRQ_WATCHDOG] = { + .mask = AS3722_INTERRUPT_MASK3_WATCHDOG, + .reg_offset = 2, + }, + [AS3722_IRQ_ENABLE3] = { + .mask = AS3722_INTERRUPT_MASK3_ENABLE3, + .reg_offset = 2, + }, + + /* INT4 IRQs */ + [AS3722_IRQ_TEMP_SD0_SHUTDOWN] = { + .mask = AS3722_INTERRUPT_MASK4_TEMP_SD0_SHUTDOWN, + .reg_offset = 3, + }, + [AS3722_IRQ_TEMP_SD1_SHUTDOWN] = { + .mask = AS3722_INTERRUPT_MASK4_TEMP_SD1_SHUTDOWN, + .reg_offset = 3, + }, + [AS3722_IRQ_TEMP_SD2_SHUTDOWN] = { + .mask = AS3722_INTERRUPT_MASK4_TEMP_SD6_SHUTDOWN, + .reg_offset = 3, + }, + [AS3722_IRQ_TEMP_SD0_ALARM] = { + .mask = AS3722_INTERRUPT_MASK4_TEMP_SD0_ALARM, + .reg_offset = 3, + }, + [AS3722_IRQ_TEMP_SD1_ALARM] = { + .mask = AS3722_INTERRUPT_MASK4_TEMP_SD1_ALARM, + .reg_offset = 3, + }, + [AS3722_IRQ_TEMP_SD6_ALARM] = { + .mask = AS3722_INTERRUPT_MASK4_TEMP_SD6_ALARM, + .reg_offset = 3, + }, + [AS3722_IRQ_OCCUR_ALARM_SD6] = { + .mask = AS3722_INTERRUPT_MASK4_OCCUR_ALARM_SD6, + .reg_offset = 3, + }, + [AS3722_IRQ_ADC] = { + .mask = AS3722_INTERRUPT_MASK4_ADC, + .reg_offset = 3, + }, +}; + +static const struct regmap_irq_chip as3722_irq_chip = { + .name = "as3722", + .irqs = as3722_irqs, + .num_irqs = ARRAY_SIZE(as3722_irqs), + .num_regs = 4, + .status_base = AS3722_INTERRUPT_STATUS1_REG, + .mask_base = AS3722_INTERRUPT_MASK1_REG, +}; + +static int as3722_check_device_id(struct as3722 *as3722) +{ + u32 val; + int ret; + + /* Check that this is actually a AS3722 */ + ret = as3722_read(as3722, AS3722_ASIC_ID1_REG, &val); + if (ret < 0) { + dev_err(as3722->dev, "ASIC_ID1 read failed: %d\n", ret); + return ret; + } + + if (val != AS3722_DEVICE_ID) { + dev_err(as3722->dev, "Device is not AS3722, ID is 0x%x\n", val); + return -ENODEV; + } + + ret = as3722_read(as3722, AS3722_ASIC_ID2_REG, &val); + if (ret < 0) { + dev_err(as3722->dev, "ASIC_ID2 read failed: %d\n", ret); + return ret; + } + + dev_info(as3722->dev, "AS3722 with revision 0x%x found\n", val); + return 0; +} + +static int as3722_configure_pullups(struct as3722 *as3722) +{ + int ret; + u32 val = 0; + + if (as3722->en_intern_int_pullup) + val |= AS3722_INT_PULL_UP; + if (as3722->en_intern_i2c_pullup) + val |= AS3722_I2C_PULL_UP; + + ret = as3722_update_bits(as3722, AS3722_IOVOLTAGE_REG, + AS3722_INT_PULL_UP | AS3722_I2C_PULL_UP, val); + if (ret < 0) + dev_err(as3722->dev, "IOVOLTAGE_REG update failed: %d\n", ret); + return ret; +} + +static const struct regmap_range as3722_readable_ranges[] = { + regmap_reg_range(AS3722_SD0_VOLTAGE_REG, AS3722_SD6_VOLTAGE_REG), + regmap_reg_range(AS3722_GPIO0_CONTROL_REG, AS3722_LDO7_VOLTAGE_REG), + regmap_reg_range(AS3722_LDO9_VOLTAGE_REG, AS3722_REG_SEQU_MOD3_REG), + regmap_reg_range(AS3722_SD_PHSW_CTRL_REG, AS3722_PWM_CONTROL_H_REG), + regmap_reg_range(AS3722_WATCHDOG_TIMER_REG, AS3722_WATCHDOG_TIMER_REG), + regmap_reg_range(AS3722_WATCHDOG_SOFTWARE_SIGNAL_REG, + AS3722_BATTERY_VOLTAGE_MONITOR2_REG), + regmap_reg_range(AS3722_SD_CONTROL_REG, AS3722_PWM_VCONTROL4_REG), + regmap_reg_range(AS3722_BB_CHARGER_REG, AS3722_SRAM_REG), + regmap_reg_range(AS3722_RTC_ACCESS_REG, AS3722_RTC_ACCESS_REG), + regmap_reg_range(AS3722_RTC_STATUS_REG, AS3722_TEMP_STATUS_REG), + regmap_reg_range(AS3722_ADC0_CONTROL_REG, AS3722_ADC_CONFIGURATION_REG), + regmap_reg_range(AS3722_ASIC_ID1_REG, AS3722_ASIC_ID2_REG), + regmap_reg_range(AS3722_LOCK_REG, AS3722_LOCK_REG), + regmap_reg_range(AS3722_FUSE7_REG, AS3722_FUSE7_REG), +}; + +static const struct regmap_access_table as3722_readable_table = { + .yes_ranges = as3722_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(as3722_readable_ranges), +}; + +static const struct regmap_range as3722_writable_ranges[] = { + regmap_reg_range(AS3722_SD0_VOLTAGE_REG, AS3722_SD6_VOLTAGE_REG), + regmap_reg_range(AS3722_GPIO0_CONTROL_REG, AS3722_LDO7_VOLTAGE_REG), + regmap_reg_range(AS3722_LDO9_VOLTAGE_REG, AS3722_GPIO_SIGNAL_OUT_REG), + regmap_reg_range(AS3722_REG_SEQU_MOD1_REG, AS3722_REG_SEQU_MOD3_REG), + regmap_reg_range(AS3722_SD_PHSW_CTRL_REG, AS3722_PWM_CONTROL_H_REG), + regmap_reg_range(AS3722_WATCHDOG_TIMER_REG, AS3722_WATCHDOG_TIMER_REG), + regmap_reg_range(AS3722_WATCHDOG_SOFTWARE_SIGNAL_REG, + AS3722_BATTERY_VOLTAGE_MONITOR2_REG), + regmap_reg_range(AS3722_SD_CONTROL_REG, AS3722_PWM_VCONTROL4_REG), + regmap_reg_range(AS3722_BB_CHARGER_REG, AS3722_SRAM_REG), + regmap_reg_range(AS3722_INTERRUPT_MASK1_REG, AS3722_TEMP_STATUS_REG), + regmap_reg_range(AS3722_ADC0_CONTROL_REG, AS3722_ADC1_CONTROL_REG), + regmap_reg_range(AS3722_ADC1_THRESHOLD_HI_MSB_REG, + AS3722_ADC_CONFIGURATION_REG), + regmap_reg_range(AS3722_LOCK_REG, AS3722_LOCK_REG), +}; + +static const struct regmap_access_table as3722_writable_table = { + .yes_ranges = as3722_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(as3722_writable_ranges), +}; + +static const struct regmap_range as3722_cacheable_ranges[] = { + regmap_reg_range(AS3722_SD0_VOLTAGE_REG, AS3722_LDO11_VOLTAGE_REG), + regmap_reg_range(AS3722_SD_CONTROL_REG, AS3722_LDOCONTROL1_REG), +}; + +static const struct regmap_access_table as3722_volatile_table = { + .no_ranges = as3722_cacheable_ranges, + .n_no_ranges = ARRAY_SIZE(as3722_cacheable_ranges), +}; + +static const struct regmap_config as3722_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AS3722_MAX_REGISTER, + .cache_type = REGCACHE_RBTREE, + .rd_table = &as3722_readable_table, + .wr_table = &as3722_writable_table, + .volatile_table = &as3722_volatile_table, +}; + +static int as3722_i2c_of_probe(struct i2c_client *i2c, + struct as3722 *as3722) +{ + struct device_node *np = i2c->dev.of_node; + struct irq_data *irq_data; + + if (!np) { + dev_err(&i2c->dev, "Device Tree not found\n"); + return -EINVAL; + } + + irq_data = irq_get_irq_data(i2c->irq); + if (!irq_data) { + dev_err(&i2c->dev, "Invalid IRQ: %d\n", i2c->irq); + return -EINVAL; + } + + as3722->en_intern_int_pullup = of_property_read_bool(np, + "ams,enable-internal-int-pullup"); + as3722->en_intern_i2c_pullup = of_property_read_bool(np, + "ams,enable-internal-i2c-pullup"); + as3722->irq_flags = irqd_get_trigger_type(irq_data); + dev_dbg(&i2c->dev, "IRQ flags are 0x%08lx\n", as3722->irq_flags); + return 0; +} + +static int as3722_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct as3722 *as3722; + unsigned long irq_flags; + int ret; + + as3722 = devm_kzalloc(&i2c->dev, sizeof(struct as3722), GFP_KERNEL); + if (!as3722) + return -ENOMEM; + + as3722->dev = &i2c->dev; + as3722->chip_irq = i2c->irq; + i2c_set_clientdata(i2c, as3722); + + ret = as3722_i2c_of_probe(i2c, as3722); + if (ret < 0) + return ret; + + as3722->regmap = devm_regmap_init_i2c(i2c, &as3722_regmap_config); + if (IS_ERR(as3722->regmap)) { + ret = PTR_ERR(as3722->regmap); + dev_err(&i2c->dev, "regmap init failed: %d\n", ret); + return ret; + } + + ret = as3722_check_device_id(as3722); + if (ret < 0) + return ret; + + irq_flags = as3722->irq_flags | IRQF_ONESHOT; + ret = regmap_add_irq_chip(as3722->regmap, as3722->chip_irq, + irq_flags, -1, &as3722_irq_chip, + &as3722->irq_data); + if (ret < 0) { + dev_err(as3722->dev, "Failed to add regmap irq: %d\n", ret); + return ret; + } + + ret = as3722_configure_pullups(as3722); + if (ret < 0) + goto scrub; + + ret = mfd_add_devices(&i2c->dev, -1, as3722_devs, + ARRAY_SIZE(as3722_devs), NULL, 0, + regmap_irq_get_domain(as3722->irq_data)); + if (ret) { + dev_err(as3722->dev, "Failed to add MFD devices: %d\n", ret); + goto scrub; + } + + dev_dbg(as3722->dev, "AS3722 core driver initialized successfully\n"); + return 0; + +scrub: + regmap_del_irq_chip(as3722->chip_irq, as3722->irq_data); + return ret; +} + +static int as3722_i2c_remove(struct i2c_client *i2c) +{ + struct as3722 *as3722 = i2c_get_clientdata(i2c); + + mfd_remove_devices(as3722->dev); + regmap_del_irq_chip(as3722->chip_irq, as3722->irq_data); + return 0; +} + +static const struct of_device_id as3722_of_match[] = { + { .compatible = "ams,as3722", }, + {}, +}; +MODULE_DEVICE_TABLE(of, as3722_of_match); + +static const struct i2c_device_id as3722_i2c_id[] = { + { "as3722", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, as3722_i2c_id); + +static struct i2c_driver as3722_i2c_driver = { + .driver = { + .name = "as3722", + .owner = THIS_MODULE, + .of_match_table = as3722_of_match, + }, + .probe = as3722_i2c_probe, + .remove = as3722_i2c_remove, + .id_table = as3722_i2c_id, +}; + +module_i2c_driver(as3722_i2c_driver); + +MODULE_DESCRIPTION("I2C support for AS3722 PMICs"); +MODULE_AUTHOR("Florian Lobmaier <florian.lobmaier@ams.com>"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/asic3.c b/kernel/drivers/mfd/asic3.c new file mode 100644 index 000000000..977bd3a3e --- /dev/null +++ b/kernel/drivers/mfd/asic3.c @@ -0,0 +1,1082 @@ +/* + * driver/mfd/asic3.c + * + * Compaq ASIC3 support. + * + * 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. + * + * Copyright 2001 Compaq Computer Corporation. + * Copyright 2004-2005 Phil Blundell + * Copyright 2007-2008 OpenedHand Ltd. + * + * Authors: Phil Blundell <pb@handhelds.org>, + * Samuel Ortiz <sameo@openedhand.com> + * + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> + +#include <linux/mfd/asic3.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ds1wm.h> +#include <linux/mfd/tmio.h> + +enum { + ASIC3_CLOCK_SPI, + ASIC3_CLOCK_OWM, + ASIC3_CLOCK_PWM0, + ASIC3_CLOCK_PWM1, + ASIC3_CLOCK_LED0, + ASIC3_CLOCK_LED1, + ASIC3_CLOCK_LED2, + ASIC3_CLOCK_SD_HOST, + ASIC3_CLOCK_SD_BUS, + ASIC3_CLOCK_SMBUS, + ASIC3_CLOCK_EX0, + ASIC3_CLOCK_EX1, +}; + +struct asic3_clk { + int enabled; + unsigned int cdex; + unsigned long rate; +}; + +#define INIT_CDEX(_name, _rate) \ + [ASIC3_CLOCK_##_name] = { \ + .cdex = CLOCK_CDEX_##_name, \ + .rate = _rate, \ + } + +static struct asic3_clk asic3_clk_init[] __initdata = { + INIT_CDEX(SPI, 0), + INIT_CDEX(OWM, 5000000), + INIT_CDEX(PWM0, 0), + INIT_CDEX(PWM1, 0), + INIT_CDEX(LED0, 0), + INIT_CDEX(LED1, 0), + INIT_CDEX(LED2, 0), + INIT_CDEX(SD_HOST, 24576000), + INIT_CDEX(SD_BUS, 12288000), + INIT_CDEX(SMBUS, 0), + INIT_CDEX(EX0, 32768), + INIT_CDEX(EX1, 24576000), +}; + +struct asic3 { + void __iomem *mapping; + unsigned int bus_shift; + unsigned int irq_nr; + unsigned int irq_base; + spinlock_t lock; + u16 irq_bothedge[4]; + struct gpio_chip gpio; + struct device *dev; + void __iomem *tmio_cnf; + + struct asic3_clk clocks[ARRAY_SIZE(asic3_clk_init)]; +}; + +static int asic3_gpio_get(struct gpio_chip *chip, unsigned offset); + +void asic3_write_register(struct asic3 *asic, unsigned int reg, u32 value) +{ + iowrite16(value, asic->mapping + + (reg >> asic->bus_shift)); +} +EXPORT_SYMBOL_GPL(asic3_write_register); + +u32 asic3_read_register(struct asic3 *asic, unsigned int reg) +{ + return ioread16(asic->mapping + + (reg >> asic->bus_shift)); +} +EXPORT_SYMBOL_GPL(asic3_read_register); + +static void asic3_set_register(struct asic3 *asic, u32 reg, u32 bits, bool set) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&asic->lock, flags); + val = asic3_read_register(asic, reg); + if (set) + val |= bits; + else + val &= ~bits; + asic3_write_register(asic, reg, val); + spin_unlock_irqrestore(&asic->lock, flags); +} + +/* IRQs */ +#define MAX_ASIC_ISR_LOOPS 20 +#define ASIC3_GPIO_BASE_INCR \ + (ASIC3_GPIO_B_BASE - ASIC3_GPIO_A_BASE) + +static void asic3_irq_flip_edge(struct asic3 *asic, + u32 base, int bit) +{ + u16 edge; + unsigned long flags; + + spin_lock_irqsave(&asic->lock, flags); + edge = asic3_read_register(asic, + base + ASIC3_GPIO_EDGE_TRIGGER); + edge ^= bit; + asic3_write_register(asic, + base + ASIC3_GPIO_EDGE_TRIGGER, edge); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_irq_demux(unsigned int irq, struct irq_desc *desc) +{ + struct asic3 *asic = irq_desc_get_handler_data(desc); + struct irq_data *data = irq_desc_get_irq_data(desc); + int iter, i; + unsigned long flags; + + data->chip->irq_ack(data); + + for (iter = 0 ; iter < MAX_ASIC_ISR_LOOPS; iter++) { + u32 status; + int bank; + + spin_lock_irqsave(&asic->lock, flags); + status = asic3_read_register(asic, + ASIC3_OFFSET(INTR, P_INT_STAT)); + spin_unlock_irqrestore(&asic->lock, flags); + + /* Check all ten register bits */ + if ((status & 0x3ff) == 0) + break; + + /* Handle GPIO IRQs */ + for (bank = 0; bank < ASIC3_NUM_GPIO_BANKS; bank++) { + if (status & (1 << bank)) { + unsigned long base, istat; + + base = ASIC3_GPIO_A_BASE + + bank * ASIC3_GPIO_BASE_INCR; + + spin_lock_irqsave(&asic->lock, flags); + istat = asic3_read_register(asic, + base + + ASIC3_GPIO_INT_STATUS); + /* Clearing IntStatus */ + asic3_write_register(asic, + base + + ASIC3_GPIO_INT_STATUS, 0); + spin_unlock_irqrestore(&asic->lock, flags); + + for (i = 0; i < ASIC3_GPIOS_PER_BANK; i++) { + int bit = (1 << i); + unsigned int irqnr; + + if (!(istat & bit)) + continue; + + irqnr = asic->irq_base + + (ASIC3_GPIOS_PER_BANK * bank) + + i; + generic_handle_irq(irqnr); + if (asic->irq_bothedge[bank] & bit) + asic3_irq_flip_edge(asic, base, + bit); + } + } + } + + /* Handle remaining IRQs in the status register */ + for (i = ASIC3_NUM_GPIOS; i < ASIC3_NR_IRQS; i++) { + /* They start at bit 4 and go up */ + if (status & (1 << (i - ASIC3_NUM_GPIOS + 4))) + generic_handle_irq(asic->irq_base + i); + } + } + + if (iter >= MAX_ASIC_ISR_LOOPS) + dev_err(asic->dev, "interrupt processing overrun\n"); +} + +static inline int asic3_irq_to_bank(struct asic3 *asic, int irq) +{ + int n; + + n = (irq - asic->irq_base) >> 4; + + return (n * (ASIC3_GPIO_B_BASE - ASIC3_GPIO_A_BASE)); +} + +static inline int asic3_irq_to_index(struct asic3 *asic, int irq) +{ + return (irq - asic->irq_base) & 0xf; +} + +static void asic3_mask_gpio_irq(struct irq_data *data) +{ + struct asic3 *asic = irq_data_get_irq_chip_data(data); + u32 val, bank, index; + unsigned long flags; + + bank = asic3_irq_to_bank(asic, data->irq); + index = asic3_irq_to_index(asic, data->irq); + + spin_lock_irqsave(&asic->lock, flags); + val = asic3_read_register(asic, bank + ASIC3_GPIO_MASK); + val |= 1 << index; + asic3_write_register(asic, bank + ASIC3_GPIO_MASK, val); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_mask_irq(struct irq_data *data) +{ + struct asic3 *asic = irq_data_get_irq_chip_data(data); + int regval; + unsigned long flags; + + spin_lock_irqsave(&asic->lock, flags); + regval = asic3_read_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK); + + regval &= ~(ASIC3_INTMASK_MASK0 << + (data->irq - (asic->irq_base + ASIC3_NUM_GPIOS))); + + asic3_write_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK, + regval); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_unmask_gpio_irq(struct irq_data *data) +{ + struct asic3 *asic = irq_data_get_irq_chip_data(data); + u32 val, bank, index; + unsigned long flags; + + bank = asic3_irq_to_bank(asic, data->irq); + index = asic3_irq_to_index(asic, data->irq); + + spin_lock_irqsave(&asic->lock, flags); + val = asic3_read_register(asic, bank + ASIC3_GPIO_MASK); + val &= ~(1 << index); + asic3_write_register(asic, bank + ASIC3_GPIO_MASK, val); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_unmask_irq(struct irq_data *data) +{ + struct asic3 *asic = irq_data_get_irq_chip_data(data); + int regval; + unsigned long flags; + + spin_lock_irqsave(&asic->lock, flags); + regval = asic3_read_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK); + + regval |= (ASIC3_INTMASK_MASK0 << + (data->irq - (asic->irq_base + ASIC3_NUM_GPIOS))); + + asic3_write_register(asic, + ASIC3_INTR_BASE + + ASIC3_INTR_INT_MASK, + regval); + spin_unlock_irqrestore(&asic->lock, flags); +} + +static int asic3_gpio_irq_type(struct irq_data *data, unsigned int type) +{ + struct asic3 *asic = irq_data_get_irq_chip_data(data); + u32 bank, index; + u16 trigger, level, edge, bit; + unsigned long flags; + + bank = asic3_irq_to_bank(asic, data->irq); + index = asic3_irq_to_index(asic, data->irq); + bit = 1<<index; + + spin_lock_irqsave(&asic->lock, flags); + level = asic3_read_register(asic, + bank + ASIC3_GPIO_LEVEL_TRIGGER); + edge = asic3_read_register(asic, + bank + ASIC3_GPIO_EDGE_TRIGGER); + trigger = asic3_read_register(asic, + bank + ASIC3_GPIO_TRIGGER_TYPE); + asic->irq_bothedge[(data->irq - asic->irq_base) >> 4] &= ~bit; + + if (type == IRQ_TYPE_EDGE_RISING) { + trigger |= bit; + edge |= bit; + } else if (type == IRQ_TYPE_EDGE_FALLING) { + trigger |= bit; + edge &= ~bit; + } else if (type == IRQ_TYPE_EDGE_BOTH) { + trigger |= bit; + if (asic3_gpio_get(&asic->gpio, data->irq - asic->irq_base)) + edge &= ~bit; + else + edge |= bit; + asic->irq_bothedge[(data->irq - asic->irq_base) >> 4] |= bit; + } else if (type == IRQ_TYPE_LEVEL_LOW) { + trigger &= ~bit; + level &= ~bit; + } else if (type == IRQ_TYPE_LEVEL_HIGH) { + trigger &= ~bit; + level |= bit; + } else { + /* + * if type == IRQ_TYPE_NONE, we should mask interrupts, but + * be careful to not unmask them if mask was also called. + * Probably need internal state for mask. + */ + dev_notice(asic->dev, "irq type not changed\n"); + } + asic3_write_register(asic, bank + ASIC3_GPIO_LEVEL_TRIGGER, + level); + asic3_write_register(asic, bank + ASIC3_GPIO_EDGE_TRIGGER, + edge); + asic3_write_register(asic, bank + ASIC3_GPIO_TRIGGER_TYPE, + trigger); + spin_unlock_irqrestore(&asic->lock, flags); + return 0; +} + +static int asic3_gpio_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct asic3 *asic = irq_data_get_irq_chip_data(data); + u32 bank, index; + u16 bit; + + bank = asic3_irq_to_bank(asic, data->irq); + index = asic3_irq_to_index(asic, data->irq); + bit = 1<<index; + + asic3_set_register(asic, bank + ASIC3_GPIO_SLEEP_MASK, bit, !on); + + return 0; +} + +static struct irq_chip asic3_gpio_irq_chip = { + .name = "ASIC3-GPIO", + .irq_ack = asic3_mask_gpio_irq, + .irq_mask = asic3_mask_gpio_irq, + .irq_unmask = asic3_unmask_gpio_irq, + .irq_set_type = asic3_gpio_irq_type, + .irq_set_wake = asic3_gpio_irq_set_wake, +}; + +static struct irq_chip asic3_irq_chip = { + .name = "ASIC3", + .irq_ack = asic3_mask_irq, + .irq_mask = asic3_mask_irq, + .irq_unmask = asic3_unmask_irq, +}; + +static int __init asic3_irq_probe(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + unsigned long clksel = 0; + unsigned int irq, irq_base; + int ret; + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + asic->irq_nr = ret; + + /* turn on clock to IRQ controller */ + clksel |= CLOCK_SEL_CX; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), + clksel); + + irq_base = asic->irq_base; + + for (irq = irq_base; irq < irq_base + ASIC3_NR_IRQS; irq++) { + if (irq < asic->irq_base + ASIC3_NUM_GPIOS) + irq_set_chip(irq, &asic3_gpio_irq_chip); + else + irq_set_chip(irq, &asic3_irq_chip); + + irq_set_chip_data(irq, asic); + irq_set_handler(irq, handle_level_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + + asic3_write_register(asic, ASIC3_OFFSET(INTR, INT_MASK), + ASIC3_INTMASK_GINTMASK); + + irq_set_chained_handler(asic->irq_nr, asic3_irq_demux); + irq_set_irq_type(asic->irq_nr, IRQ_TYPE_EDGE_RISING); + irq_set_handler_data(asic->irq_nr, asic); + + return 0; +} + +static void asic3_irq_remove(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + unsigned int irq, irq_base; + + irq_base = asic->irq_base; + + for (irq = irq_base; irq < irq_base + ASIC3_NR_IRQS; irq++) { + set_irq_flags(irq, 0); + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); + } + irq_set_chained_handler(asic->irq_nr, NULL); +} + +/* GPIOs */ +static int asic3_gpio_direction(struct gpio_chip *chip, + unsigned offset, int out) +{ + u32 mask = ASIC3_GPIO_TO_MASK(offset), out_reg; + unsigned int gpio_base; + unsigned long flags; + struct asic3 *asic; + + asic = container_of(chip, struct asic3, gpio); + gpio_base = ASIC3_GPIO_TO_BASE(offset); + + if (gpio_base > ASIC3_GPIO_D_BASE) { + dev_err(asic->dev, "Invalid base (0x%x) for gpio %d\n", + gpio_base, offset); + return -EINVAL; + } + + spin_lock_irqsave(&asic->lock, flags); + + out_reg = asic3_read_register(asic, gpio_base + ASIC3_GPIO_DIRECTION); + + /* Input is 0, Output is 1 */ + if (out) + out_reg |= mask; + else + out_reg &= ~mask; + + asic3_write_register(asic, gpio_base + ASIC3_GPIO_DIRECTION, out_reg); + + spin_unlock_irqrestore(&asic->lock, flags); + + return 0; + +} + +static int asic3_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + return asic3_gpio_direction(chip, offset, 0); +} + +static int asic3_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + return asic3_gpio_direction(chip, offset, 1); +} + +static int asic3_gpio_get(struct gpio_chip *chip, + unsigned offset) +{ + unsigned int gpio_base; + u32 mask = ASIC3_GPIO_TO_MASK(offset); + struct asic3 *asic; + + asic = container_of(chip, struct asic3, gpio); + gpio_base = ASIC3_GPIO_TO_BASE(offset); + + if (gpio_base > ASIC3_GPIO_D_BASE) { + dev_err(asic->dev, "Invalid base (0x%x) for gpio %d\n", + gpio_base, offset); + return -EINVAL; + } + + return asic3_read_register(asic, gpio_base + ASIC3_GPIO_STATUS) & mask; +} + +static void asic3_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + u32 mask, out_reg; + unsigned int gpio_base; + unsigned long flags; + struct asic3 *asic; + + asic = container_of(chip, struct asic3, gpio); + gpio_base = ASIC3_GPIO_TO_BASE(offset); + + if (gpio_base > ASIC3_GPIO_D_BASE) { + dev_err(asic->dev, "Invalid base (0x%x) for gpio %d\n", + gpio_base, offset); + return; + } + + mask = ASIC3_GPIO_TO_MASK(offset); + + spin_lock_irqsave(&asic->lock, flags); + + out_reg = asic3_read_register(asic, gpio_base + ASIC3_GPIO_OUT); + + if (value) + out_reg |= mask; + else + out_reg &= ~mask; + + asic3_write_register(asic, gpio_base + ASIC3_GPIO_OUT, out_reg); + + spin_unlock_irqrestore(&asic->lock, flags); + + return; +} + +static int asic3_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct asic3 *asic = container_of(chip, struct asic3, gpio); + + return asic->irq_base + offset; +} + +static __init int asic3_gpio_probe(struct platform_device *pdev, + u16 *gpio_config, int num) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + u16 alt_reg[ASIC3_NUM_GPIO_BANKS]; + u16 out_reg[ASIC3_NUM_GPIO_BANKS]; + u16 dir_reg[ASIC3_NUM_GPIO_BANKS]; + int i; + + memset(alt_reg, 0, ASIC3_NUM_GPIO_BANKS * sizeof(u16)); + memset(out_reg, 0, ASIC3_NUM_GPIO_BANKS * sizeof(u16)); + memset(dir_reg, 0, ASIC3_NUM_GPIO_BANKS * sizeof(u16)); + + /* Enable all GPIOs */ + asic3_write_register(asic, ASIC3_GPIO_OFFSET(A, MASK), 0xffff); + asic3_write_register(asic, ASIC3_GPIO_OFFSET(B, MASK), 0xffff); + asic3_write_register(asic, ASIC3_GPIO_OFFSET(C, MASK), 0xffff); + asic3_write_register(asic, ASIC3_GPIO_OFFSET(D, MASK), 0xffff); + + for (i = 0; i < num; i++) { + u8 alt, pin, dir, init, bank_num, bit_num; + u16 config = gpio_config[i]; + + pin = ASIC3_CONFIG_GPIO_PIN(config); + alt = ASIC3_CONFIG_GPIO_ALT(config); + dir = ASIC3_CONFIG_GPIO_DIR(config); + init = ASIC3_CONFIG_GPIO_INIT(config); + + bank_num = ASIC3_GPIO_TO_BANK(pin); + bit_num = ASIC3_GPIO_TO_BIT(pin); + + alt_reg[bank_num] |= (alt << bit_num); + out_reg[bank_num] |= (init << bit_num); + dir_reg[bank_num] |= (dir << bit_num); + } + + for (i = 0; i < ASIC3_NUM_GPIO_BANKS; i++) { + asic3_write_register(asic, + ASIC3_BANK_TO_BASE(i) + + ASIC3_GPIO_DIRECTION, + dir_reg[i]); + asic3_write_register(asic, + ASIC3_BANK_TO_BASE(i) + ASIC3_GPIO_OUT, + out_reg[i]); + asic3_write_register(asic, + ASIC3_BANK_TO_BASE(i) + + ASIC3_GPIO_ALT_FUNCTION, + alt_reg[i]); + } + + return gpiochip_add(&asic->gpio); +} + +static int asic3_gpio_remove(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + + gpiochip_remove(&asic->gpio); + return 0; +} + +static void asic3_clk_enable(struct asic3 *asic, struct asic3_clk *clk) +{ + unsigned long flags; + u32 cdex; + + spin_lock_irqsave(&asic->lock, flags); + if (clk->enabled++ == 0) { + cdex = asic3_read_register(asic, ASIC3_OFFSET(CLOCK, CDEX)); + cdex |= clk->cdex; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, CDEX), cdex); + } + spin_unlock_irqrestore(&asic->lock, flags); +} + +static void asic3_clk_disable(struct asic3 *asic, struct asic3_clk *clk) +{ + unsigned long flags; + u32 cdex; + + WARN_ON(clk->enabled == 0); + + spin_lock_irqsave(&asic->lock, flags); + if (--clk->enabled == 0) { + cdex = asic3_read_register(asic, ASIC3_OFFSET(CLOCK, CDEX)); + cdex &= ~clk->cdex; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, CDEX), cdex); + } + spin_unlock_irqrestore(&asic->lock, flags); +} + +/* 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[] = { + { + .start = ASIC3_OWM_BASE, + .end = ASIC3_OWM_BASE + 0x13, + .flags = IORESOURCE_MEM, + }, + { + .start = ASIC3_IRQ_OWM, + .end = ASIC3_IRQ_OWM, + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE, + }, +}; + +static int ds1wm_enable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + /* Turn on external clocks and the OWM clock */ + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_OWM]); + msleep(1); + + /* Reset and enable DS1WM */ + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, RESET), + ASIC3_EXTCF_OWM_RESET, 1); + msleep(1); + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, RESET), + ASIC3_EXTCF_OWM_RESET, 0); + msleep(1); + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_OWM_EN, 1); + msleep(1); + + return 0; +} + +static int ds1wm_disable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_OWM_EN, 0); + + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_OWM]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + + return 0; +} + +static const struct mfd_cell asic3_cell_ds1wm = { + .name = "ds1wm", + .enable = ds1wm_enable, + .disable = ds1wm_disable, + .platform_data = &ds1wm_pdata, + .pdata_size = sizeof(ds1wm_pdata), + .num_resources = ARRAY_SIZE(ds1wm_resources), + .resources = ds1wm_resources, +}; + +static void asic3_mmc_pwr(struct platform_device *pdev, int state) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + tmio_core_mmc_pwr(asic->tmio_cnf, 1 - asic->bus_shift, state); +} + +static void asic3_mmc_clk_div(struct platform_device *pdev, int state) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + tmio_core_mmc_clk_div(asic->tmio_cnf, 1 - asic->bus_shift, state); +} + +static struct tmio_mmc_data asic3_mmc_data = { + .hclk = 24576000, + .set_pwr = asic3_mmc_pwr, + .set_clk_div = asic3_mmc_clk_div, +}; + +static struct resource asic3_mmc_resources[] = { + { + .start = ASIC3_SD_CTRL_BASE, + .end = ASIC3_SD_CTRL_BASE + 0x3ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static int asic3_mmc_enable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + /* Not sure if it must be done bit by bit, but leaving as-is */ + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_LEVCD, 1); + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_LEVWP, 1); + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_SUSPEND, 0); + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_PCLR, 0); + + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + /* CLK32 used for card detection and for interruption detection + * when HCLK is stopped. + */ + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + msleep(1); + + /* HCLK 24.576 MHz, BCLK 12.288 MHz: */ + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), + CLOCK_SEL_CX | CLOCK_SEL_SD_HCLK_SEL); + + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_SD_HOST]); + asic3_clk_enable(asic, &asic->clocks[ASIC3_CLOCK_SD_BUS]); + msleep(1); + + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_SD_MEM_ENABLE, 1); + + /* Enable SD card slot 3.3V power supply */ + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_SDPWR, 1); + + /* ASIC3_SD_CTRL_BASE assumes 32-bit addressing, TMIO is 16-bit */ + tmio_core_mmc_enable(asic->tmio_cnf, 1 - asic->bus_shift, + ASIC3_SD_CTRL_BASE >> 1); + + return 0; +} + +static int asic3_mmc_disable(struct platform_device *pdev) +{ + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + /* Put in suspend mode */ + asic3_set_register(asic, ASIC3_OFFSET(SDHWCTRL, SDCONF), + ASIC3_SDHWCTRL_SUSPEND, 1); + + /* Disable clocks */ + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_SD_HOST]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_SD_BUS]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX0]); + asic3_clk_disable(asic, &asic->clocks[ASIC3_CLOCK_EX1]); + return 0; +} + +static const struct mfd_cell asic3_cell_mmc = { + .name = "tmio-mmc", + .enable = asic3_mmc_enable, + .disable = asic3_mmc_disable, + .suspend = asic3_mmc_disable, + .resume = asic3_mmc_enable, + .platform_data = &asic3_mmc_data, + .pdata_size = sizeof(asic3_mmc_data), + .num_resources = ARRAY_SIZE(asic3_mmc_resources), + .resources = asic3_mmc_resources, +}; + +static const int clock_ledn[ASIC3_NUM_LEDS] = { + [0] = ASIC3_CLOCK_LED0, + [1] = ASIC3_CLOCK_LED1, + [2] = ASIC3_CLOCK_LED2, +}; + +static int asic3_leds_enable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + asic3_clk_enable(asic, &asic->clocks[clock_ledn[cell->id]]); + + return 0; +} + +static int asic3_leds_disable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + asic3_clk_disable(asic, &asic->clocks[clock_ledn[cell->id]]); + + return 0; +} + +static int asic3_leds_suspend(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + while (asic3_gpio_get(&asic->gpio, ASIC3_GPIO(C, cell->id)) != 0) + msleep(1); + + asic3_clk_disable(asic, &asic->clocks[clock_ledn[cell->id]]); + + return 0; +} + +static struct mfd_cell asic3_cell_leds[ASIC3_NUM_LEDS] = { + [0] = { + .name = "leds-asic3", + .id = 0, + .enable = asic3_leds_enable, + .disable = asic3_leds_disable, + .suspend = asic3_leds_suspend, + .resume = asic3_leds_enable, + }, + [1] = { + .name = "leds-asic3", + .id = 1, + .enable = asic3_leds_enable, + .disable = asic3_leds_disable, + .suspend = asic3_leds_suspend, + .resume = asic3_leds_enable, + }, + [2] = { + .name = "leds-asic3", + .id = 2, + .enable = asic3_leds_enable, + .disable = asic3_leds_disable, + .suspend = asic3_leds_suspend, + .resume = asic3_leds_enable, + }, +}; + +static int __init asic3_mfd_probe(struct platform_device *pdev, + struct asic3_platform_data *pdata, + struct resource *mem) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + struct resource *mem_sdio; + int irq, ret; + + mem_sdio = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!mem_sdio) + dev_dbg(asic->dev, "no SDIO MEM resource\n"); + + irq = platform_get_irq(pdev, 1); + if (irq < 0) + dev_dbg(asic->dev, "no SDIO IRQ resource\n"); + + /* DS1WM */ + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + ASIC3_EXTCF_OWM_SMB, 0); + + ds1wm_resources[0].start >>= asic->bus_shift; + ds1wm_resources[0].end >>= asic->bus_shift; + + /* MMC */ + if (mem_sdio) { + asic->tmio_cnf = ioremap((ASIC3_SD_CONFIG_BASE >> asic->bus_shift) + + mem_sdio->start, + ASIC3_SD_CONFIG_SIZE >> asic->bus_shift); + if (!asic->tmio_cnf) { + ret = -ENOMEM; + dev_dbg(asic->dev, "Couldn't ioremap SD_CONFIG\n"); + goto out; + } + } + asic3_mmc_resources[0].start >>= asic->bus_shift; + asic3_mmc_resources[0].end >>= asic->bus_shift; + + if (pdata->clock_rate) { + ds1wm_pdata.clock_rate = pdata->clock_rate; + ret = mfd_add_devices(&pdev->dev, pdev->id, + &asic3_cell_ds1wm, 1, mem, asic->irq_base, NULL); + if (ret < 0) + goto out; + } + + if (mem_sdio && (irq >= 0)) { + ret = mfd_add_devices(&pdev->dev, pdev->id, + &asic3_cell_mmc, 1, mem_sdio, irq, NULL); + if (ret < 0) + goto out; + } + + ret = 0; + if (pdata->leds) { + int i; + + for (i = 0; i < ASIC3_NUM_LEDS; ++i) { + asic3_cell_leds[i].platform_data = &pdata->leds[i]; + asic3_cell_leds[i].pdata_size = sizeof(pdata->leds[i]); + } + ret = mfd_add_devices(&pdev->dev, 0, + asic3_cell_leds, ASIC3_NUM_LEDS, NULL, 0, NULL); + } + + out: + return ret; +} + +static void asic3_mfd_remove(struct platform_device *pdev) +{ + struct asic3 *asic = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + iounmap(asic->tmio_cnf); +} + +/* Core */ +static int __init asic3_probe(struct platform_device *pdev) +{ + struct asic3_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct asic3 *asic; + struct resource *mem; + unsigned long clksel; + int ret = 0; + + asic = devm_kzalloc(&pdev->dev, + sizeof(struct asic3), GFP_KERNEL); + if (asic == NULL) { + printk(KERN_ERR "kzalloc failed\n"); + return -ENOMEM; + } + + spin_lock_init(&asic->lock); + platform_set_drvdata(pdev, asic); + asic->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(asic->dev, "no MEM resource\n"); + return -ENOMEM; + } + + asic->mapping = ioremap(mem->start, resource_size(mem)); + if (!asic->mapping) { + dev_err(asic->dev, "Couldn't ioremap\n"); + return -ENOMEM; + } + + asic->irq_base = pdata->irq_base; + + /* calculate bus shift from mem resource */ + asic->bus_shift = 2 - (resource_size(mem) >> 12); + + clksel = 0; + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), clksel); + + ret = asic3_irq_probe(pdev); + if (ret < 0) { + dev_err(asic->dev, "Couldn't probe IRQs\n"); + goto out_unmap; + } + + asic->gpio.label = "asic3"; + asic->gpio.base = pdata->gpio_base; + asic->gpio.ngpio = ASIC3_NUM_GPIOS; + asic->gpio.get = asic3_gpio_get; + asic->gpio.set = asic3_gpio_set; + asic->gpio.direction_input = asic3_gpio_direction_input; + asic->gpio.direction_output = asic3_gpio_direction_output; + asic->gpio.to_irq = asic3_gpio_to_irq; + + ret = asic3_gpio_probe(pdev, + pdata->gpio_config, + pdata->gpio_config_num); + if (ret < 0) { + dev_err(asic->dev, "GPIO probe failed\n"); + goto out_irq; + } + + /* Making a per-device copy is only needed for the + * theoretical case of multiple ASIC3s on one board: + */ + memcpy(asic->clocks, asic3_clk_init, sizeof(asic3_clk_init)); + + asic3_mfd_probe(pdev, pdata, mem); + + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + (ASIC3_EXTCF_CF0_BUF_EN|ASIC3_EXTCF_CF0_PWAIT_EN), 1); + + dev_info(asic->dev, "ASIC3 Core driver\n"); + + return 0; + + out_irq: + asic3_irq_remove(pdev); + + out_unmap: + iounmap(asic->mapping); + + return ret; +} + +static int asic3_remove(struct platform_device *pdev) +{ + int ret; + struct asic3 *asic = platform_get_drvdata(pdev); + + asic3_set_register(asic, ASIC3_OFFSET(EXTCF, SELECT), + (ASIC3_EXTCF_CF0_BUF_EN|ASIC3_EXTCF_CF0_PWAIT_EN), 0); + + asic3_mfd_remove(pdev); + + ret = asic3_gpio_remove(pdev); + if (ret < 0) + return ret; + asic3_irq_remove(pdev); + + asic3_write_register(asic, ASIC3_OFFSET(CLOCK, SEL), 0); + + iounmap(asic->mapping); + + return 0; +} + +static void asic3_shutdown(struct platform_device *pdev) +{ +} + +static struct platform_driver asic3_device_driver = { + .driver = { + .name = "asic3", + }, + .remove = asic3_remove, + .shutdown = asic3_shutdown, +}; + +static int __init asic3_init(void) +{ + int retval = 0; + retval = platform_driver_probe(&asic3_device_driver, asic3_probe); + return retval; +} + +subsys_initcall(asic3_init); diff --git a/kernel/drivers/mfd/atmel-hlcdc.c b/kernel/drivers/mfd/atmel-hlcdc.c new file mode 100644 index 000000000..cfd58f4cc --- /dev/null +++ b/kernel/drivers/mfd/atmel-hlcdc.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Boris BREZILLON <boris.brezillon@free-electrons.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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/mfd/atmel-hlcdc.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define ATMEL_HLCDC_REG_MAX (0x4000 - 0x4) + +static const struct mfd_cell atmel_hlcdc_cells[] = { + { + .name = "atmel-hlcdc-pwm", + .of_compatible = "atmel,hlcdc-pwm", + }, + { + .name = "atmel-hlcdc-dc", + .of_compatible = "atmel,hlcdc-display-controller", + }, +}; + +static const struct regmap_config atmel_hlcdc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = ATMEL_HLCDC_REG_MAX, +}; + +static int atmel_hlcdc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atmel_hlcdc *hlcdc; + struct resource *res; + void __iomem *regs; + + hlcdc = devm_kzalloc(dev, sizeof(*hlcdc), GFP_KERNEL); + if (!hlcdc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + hlcdc->irq = platform_get_irq(pdev, 0); + if (hlcdc->irq < 0) + return hlcdc->irq; + + hlcdc->periph_clk = devm_clk_get(dev, "periph_clk"); + if (IS_ERR(hlcdc->periph_clk)) { + dev_err(dev, "failed to get peripheral clock\n"); + return PTR_ERR(hlcdc->periph_clk); + } + + hlcdc->sys_clk = devm_clk_get(dev, "sys_clk"); + if (IS_ERR(hlcdc->sys_clk)) { + dev_err(dev, "failed to get system clock\n"); + return PTR_ERR(hlcdc->sys_clk); + } + + hlcdc->slow_clk = devm_clk_get(dev, "slow_clk"); + if (IS_ERR(hlcdc->slow_clk)) { + dev_err(dev, "failed to get slow clock\n"); + return PTR_ERR(hlcdc->slow_clk); + } + + hlcdc->regmap = devm_regmap_init_mmio(dev, regs, + &atmel_hlcdc_regmap_config); + if (IS_ERR(hlcdc->regmap)) + return PTR_ERR(hlcdc->regmap); + + dev_set_drvdata(dev, hlcdc); + + return mfd_add_devices(dev, -1, atmel_hlcdc_cells, + ARRAY_SIZE(atmel_hlcdc_cells), + NULL, 0, NULL); +} + +static int atmel_hlcdc_remove(struct platform_device *pdev) +{ + mfd_remove_devices(&pdev->dev); + + return 0; +} + +static const struct of_device_id atmel_hlcdc_match[] = { + { .compatible = "atmel,sama5d3-hlcdc" }, + { /* sentinel */ }, +}; + +static struct platform_driver atmel_hlcdc_driver = { + .probe = atmel_hlcdc_probe, + .remove = atmel_hlcdc_remove, + .driver = { + .name = "atmel-hlcdc", + .of_match_table = atmel_hlcdc_match, + }, +}; +module_platform_driver(atmel_hlcdc_driver); + +MODULE_ALIAS("platform:atmel-hlcdc"); +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Atmel HLCDC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/axp20x.c b/kernel/drivers/mfd/axp20x.c new file mode 100644 index 000000000..d18029be6 --- /dev/null +++ b/kernel/drivers/mfd/axp20x.c @@ -0,0 +1,529 @@ +/* + * axp20x.c - MFD core driver for the X-Powers' Power Management ICs + * + * AXP20x typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC + * converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature + * as well as configurable GPIOs. + * + * Author: Carlo Caione <carlo@caione.org> + * + * 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. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/axp20x.h> +#include <linux/mfd/core.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/acpi.h> + +#define AXP20X_OFF 0x80 + +static const char * const axp20x_model_names[] = { + "AXP202", + "AXP209", + "AXP288", +}; + +static const struct regmap_range axp20x_writeable_ranges[] = { + regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE), + regmap_reg_range(AXP20X_DCDC_MODE, AXP20X_FG_RES), +}; + +static const struct regmap_range axp20x_volatile_ranges[] = { + regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE), +}; + +static const struct regmap_access_table axp20x_writeable_table = { + .yes_ranges = axp20x_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(axp20x_writeable_ranges), +}; + +static const struct regmap_access_table axp20x_volatile_table = { + .yes_ranges = axp20x_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(axp20x_volatile_ranges), +}; + +static const struct regmap_range axp288_writeable_ranges[] = { + regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ6_STATE), + regmap_reg_range(AXP20X_DCDC_MODE, AXP288_FG_TUNE5), +}; + +static const struct regmap_range axp288_volatile_ranges[] = { + regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IPSOUT_V_HIGH_L), +}; + +static const struct regmap_access_table axp288_writeable_table = { + .yes_ranges = axp288_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(axp288_writeable_ranges), +}; + +static const struct regmap_access_table axp288_volatile_table = { + .yes_ranges = axp288_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(axp288_volatile_ranges), +}; + +static struct resource axp20x_pek_resources[] = { + { + .name = "PEK_DBR", + .start = AXP20X_IRQ_PEK_RIS_EDGE, + .end = AXP20X_IRQ_PEK_RIS_EDGE, + .flags = IORESOURCE_IRQ, + }, { + .name = "PEK_DBF", + .start = AXP20X_IRQ_PEK_FAL_EDGE, + .end = AXP20X_IRQ_PEK_FAL_EDGE, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource axp288_fuel_gauge_resources[] = { + { + .start = AXP288_IRQ_QWBTU, + .end = AXP288_IRQ_QWBTU, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_WBTU, + .end = AXP288_IRQ_WBTU, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_QWBTO, + .end = AXP288_IRQ_QWBTO, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_WBTO, + .end = AXP288_IRQ_WBTO, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_WL2, + .end = AXP288_IRQ_WL2, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_WL1, + .end = AXP288_IRQ_WL1, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct regmap_config axp20x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .wr_table = &axp20x_writeable_table, + .volatile_table = &axp20x_volatile_table, + .max_register = AXP20X_FG_RES, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config axp288_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .wr_table = &axp288_writeable_table, + .volatile_table = &axp288_volatile_table, + .max_register = AXP288_FG_TUNE5, + .cache_type = REGCACHE_RBTREE, +}; + +#define INIT_REGMAP_IRQ(_variant, _irq, _off, _mask) \ + [_variant##_IRQ_##_irq] = { .reg_offset = (_off), .mask = BIT(_mask) } + +static const struct regmap_irq axp20x_regmap_irqs[] = { + INIT_REGMAP_IRQ(AXP20X, ACIN_OVER_V, 0, 7), + INIT_REGMAP_IRQ(AXP20X, ACIN_PLUGIN, 0, 6), + INIT_REGMAP_IRQ(AXP20X, ACIN_REMOVAL, 0, 5), + INIT_REGMAP_IRQ(AXP20X, VBUS_OVER_V, 0, 4), + INIT_REGMAP_IRQ(AXP20X, VBUS_PLUGIN, 0, 3), + INIT_REGMAP_IRQ(AXP20X, VBUS_REMOVAL, 0, 2), + INIT_REGMAP_IRQ(AXP20X, VBUS_V_LOW, 0, 1), + INIT_REGMAP_IRQ(AXP20X, BATT_PLUGIN, 1, 7), + INIT_REGMAP_IRQ(AXP20X, BATT_REMOVAL, 1, 6), + INIT_REGMAP_IRQ(AXP20X, BATT_ENT_ACT_MODE, 1, 5), + INIT_REGMAP_IRQ(AXP20X, BATT_EXIT_ACT_MODE, 1, 4), + INIT_REGMAP_IRQ(AXP20X, CHARG, 1, 3), + INIT_REGMAP_IRQ(AXP20X, CHARG_DONE, 1, 2), + INIT_REGMAP_IRQ(AXP20X, BATT_TEMP_HIGH, 1, 1), + INIT_REGMAP_IRQ(AXP20X, BATT_TEMP_LOW, 1, 0), + INIT_REGMAP_IRQ(AXP20X, DIE_TEMP_HIGH, 2, 7), + INIT_REGMAP_IRQ(AXP20X, CHARG_I_LOW, 2, 6), + INIT_REGMAP_IRQ(AXP20X, DCDC1_V_LONG, 2, 5), + INIT_REGMAP_IRQ(AXP20X, DCDC2_V_LONG, 2, 4), + INIT_REGMAP_IRQ(AXP20X, DCDC3_V_LONG, 2, 3), + INIT_REGMAP_IRQ(AXP20X, PEK_SHORT, 2, 1), + INIT_REGMAP_IRQ(AXP20X, PEK_LONG, 2, 0), + INIT_REGMAP_IRQ(AXP20X, N_OE_PWR_ON, 3, 7), + INIT_REGMAP_IRQ(AXP20X, N_OE_PWR_OFF, 3, 6), + INIT_REGMAP_IRQ(AXP20X, VBUS_VALID, 3, 5), + INIT_REGMAP_IRQ(AXP20X, VBUS_NOT_VALID, 3, 4), + INIT_REGMAP_IRQ(AXP20X, VBUS_SESS_VALID, 3, 3), + INIT_REGMAP_IRQ(AXP20X, VBUS_SESS_END, 3, 2), + INIT_REGMAP_IRQ(AXP20X, LOW_PWR_LVL1, 3, 1), + INIT_REGMAP_IRQ(AXP20X, LOW_PWR_LVL2, 3, 0), + INIT_REGMAP_IRQ(AXP20X, TIMER, 4, 7), + INIT_REGMAP_IRQ(AXP20X, PEK_RIS_EDGE, 4, 6), + INIT_REGMAP_IRQ(AXP20X, PEK_FAL_EDGE, 4, 5), + INIT_REGMAP_IRQ(AXP20X, GPIO3_INPUT, 4, 3), + INIT_REGMAP_IRQ(AXP20X, GPIO2_INPUT, 4, 2), + INIT_REGMAP_IRQ(AXP20X, GPIO1_INPUT, 4, 1), + INIT_REGMAP_IRQ(AXP20X, GPIO0_INPUT, 4, 0), +}; + +/* some IRQs are compatible with axp20x models */ +static const struct regmap_irq axp288_regmap_irqs[] = { + INIT_REGMAP_IRQ(AXP288, VBUS_FALL, 0, 2), + INIT_REGMAP_IRQ(AXP288, VBUS_RISE, 0, 3), + INIT_REGMAP_IRQ(AXP288, OV, 0, 4), + + INIT_REGMAP_IRQ(AXP288, DONE, 1, 2), + INIT_REGMAP_IRQ(AXP288, CHARGING, 1, 3), + INIT_REGMAP_IRQ(AXP288, SAFE_QUIT, 1, 4), + INIT_REGMAP_IRQ(AXP288, SAFE_ENTER, 1, 5), + INIT_REGMAP_IRQ(AXP288, ABSENT, 1, 6), + INIT_REGMAP_IRQ(AXP288, APPEND, 1, 7), + + INIT_REGMAP_IRQ(AXP288, QWBTU, 2, 0), + INIT_REGMAP_IRQ(AXP288, WBTU, 2, 1), + INIT_REGMAP_IRQ(AXP288, QWBTO, 2, 2), + INIT_REGMAP_IRQ(AXP288, WBTO, 2, 3), + INIT_REGMAP_IRQ(AXP288, QCBTU, 2, 4), + INIT_REGMAP_IRQ(AXP288, CBTU, 2, 5), + INIT_REGMAP_IRQ(AXP288, QCBTO, 2, 6), + INIT_REGMAP_IRQ(AXP288, CBTO, 2, 7), + + INIT_REGMAP_IRQ(AXP288, WL2, 3, 0), + INIT_REGMAP_IRQ(AXP288, WL1, 3, 1), + INIT_REGMAP_IRQ(AXP288, GPADC, 3, 2), + INIT_REGMAP_IRQ(AXP288, OT, 3, 7), + + INIT_REGMAP_IRQ(AXP288, GPIO0, 4, 0), + INIT_REGMAP_IRQ(AXP288, GPIO1, 4, 1), + INIT_REGMAP_IRQ(AXP288, POKO, 4, 2), + INIT_REGMAP_IRQ(AXP288, POKL, 4, 3), + INIT_REGMAP_IRQ(AXP288, POKS, 4, 4), + INIT_REGMAP_IRQ(AXP288, POKN, 4, 5), + INIT_REGMAP_IRQ(AXP288, POKP, 4, 6), + INIT_REGMAP_IRQ(AXP288, TIMER, 4, 7), + + INIT_REGMAP_IRQ(AXP288, MV_CHNG, 5, 0), + INIT_REGMAP_IRQ(AXP288, BC_USB_CHNG, 5, 1), +}; + +static const struct of_device_id axp20x_of_match[] = { + { .compatible = "x-powers,axp202", .data = (void *) AXP202_ID }, + { .compatible = "x-powers,axp209", .data = (void *) AXP209_ID }, + { }, +}; +MODULE_DEVICE_TABLE(of, axp20x_of_match); + +/* + * This is useless for OF-enabled devices, but it is needed by I2C subsystem + */ +static const struct i2c_device_id axp20x_i2c_id[] = { + { }, +}; +MODULE_DEVICE_TABLE(i2c, axp20x_i2c_id); + +static const struct acpi_device_id axp20x_acpi_match[] = { + { + .id = "INT33F4", + .driver_data = AXP288_ID, + }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, axp20x_acpi_match); + +static const struct regmap_irq_chip axp20x_regmap_irq_chip = { + .name = "axp20x_irq_chip", + .status_base = AXP20X_IRQ1_STATE, + .ack_base = AXP20X_IRQ1_STATE, + .mask_base = AXP20X_IRQ1_EN, + .mask_invert = true, + .init_ack_masked = true, + .irqs = axp20x_regmap_irqs, + .num_irqs = ARRAY_SIZE(axp20x_regmap_irqs), + .num_regs = 5, + +}; + +static const struct regmap_irq_chip axp288_regmap_irq_chip = { + .name = "axp288_irq_chip", + .status_base = AXP20X_IRQ1_STATE, + .ack_base = AXP20X_IRQ1_STATE, + .mask_base = AXP20X_IRQ1_EN, + .mask_invert = true, + .init_ack_masked = true, + .irqs = axp288_regmap_irqs, + .num_irqs = ARRAY_SIZE(axp288_regmap_irqs), + .num_regs = 6, + +}; + +static struct mfd_cell axp20x_cells[] = { + { + .name = "axp20x-pek", + .num_resources = ARRAY_SIZE(axp20x_pek_resources), + .resources = axp20x_pek_resources, + }, { + .name = "axp20x-regulator", + }, +}; + +static struct resource axp288_adc_resources[] = { + { + .name = "GPADC", + .start = AXP288_IRQ_GPADC, + .end = AXP288_IRQ_GPADC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource axp288_extcon_resources[] = { + { + .start = AXP288_IRQ_VBUS_FALL, + .end = AXP288_IRQ_VBUS_FALL, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_VBUS_RISE, + .end = AXP288_IRQ_VBUS_RISE, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_MV_CHNG, + .end = AXP288_IRQ_MV_CHNG, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_BC_USB_CHNG, + .end = AXP288_IRQ_BC_USB_CHNG, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource axp288_charger_resources[] = { + { + .start = AXP288_IRQ_OV, + .end = AXP288_IRQ_OV, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_DONE, + .end = AXP288_IRQ_DONE, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_CHARGING, + .end = AXP288_IRQ_CHARGING, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_SAFE_QUIT, + .end = AXP288_IRQ_SAFE_QUIT, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_SAFE_ENTER, + .end = AXP288_IRQ_SAFE_ENTER, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_QCBTU, + .end = AXP288_IRQ_QCBTU, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_CBTU, + .end = AXP288_IRQ_CBTU, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_QCBTO, + .end = AXP288_IRQ_QCBTO, + .flags = IORESOURCE_IRQ, + }, + { + .start = AXP288_IRQ_CBTO, + .end = AXP288_IRQ_CBTO, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell axp288_cells[] = { + { + .name = "axp288_adc", + .num_resources = ARRAY_SIZE(axp288_adc_resources), + .resources = axp288_adc_resources, + }, + { + .name = "axp288_extcon", + .num_resources = ARRAY_SIZE(axp288_extcon_resources), + .resources = axp288_extcon_resources, + }, + { + .name = "axp288_charger", + .num_resources = ARRAY_SIZE(axp288_charger_resources), + .resources = axp288_charger_resources, + }, + { + .name = "axp288_fuel_gauge", + .num_resources = ARRAY_SIZE(axp288_fuel_gauge_resources), + .resources = axp288_fuel_gauge_resources, + }, + { + .name = "axp288_pmic_acpi", + }, +}; + +static struct axp20x_dev *axp20x_pm_power_off; +static void axp20x_power_off(void) +{ + if (axp20x_pm_power_off->variant == AXP288_ID) + return; + + regmap_write(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL, + AXP20X_OFF); +} + +static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev) +{ + const struct acpi_device_id *acpi_id; + const struct of_device_id *of_id; + + if (dev->of_node) { + of_id = of_match_device(axp20x_of_match, dev); + if (!of_id) { + dev_err(dev, "Unable to match OF ID\n"); + return -ENODEV; + } + axp20x->variant = (long) of_id->data; + } else { + acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!acpi_id || !acpi_id->driver_data) { + dev_err(dev, "Unable to match ACPI ID and data\n"); + return -ENODEV; + } + axp20x->variant = (long) acpi_id->driver_data; + } + + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + axp20x->nr_cells = ARRAY_SIZE(axp20x_cells); + axp20x->cells = axp20x_cells; + axp20x->regmap_cfg = &axp20x_regmap_config; + axp20x->regmap_irq_chip = &axp20x_regmap_irq_chip; + break; + case AXP288_ID: + axp20x->cells = axp288_cells; + axp20x->nr_cells = ARRAY_SIZE(axp288_cells); + axp20x->regmap_cfg = &axp288_regmap_config; + axp20x->regmap_irq_chip = &axp288_regmap_irq_chip; + break; + default: + dev_err(dev, "unsupported AXP20X ID %lu\n", axp20x->variant); + return -EINVAL; + } + dev_info(dev, "AXP20x variant %s found\n", + axp20x_model_names[axp20x->variant]); + + return 0; +} + +static int axp20x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct axp20x_dev *axp20x; + int ret; + + axp20x = devm_kzalloc(&i2c->dev, sizeof(*axp20x), GFP_KERNEL); + if (!axp20x) + return -ENOMEM; + + ret = axp20x_match_device(axp20x, &i2c->dev); + if (ret) + return ret; + + axp20x->i2c_client = i2c; + axp20x->dev = &i2c->dev; + dev_set_drvdata(axp20x->dev, axp20x); + + axp20x->regmap = devm_regmap_init_i2c(i2c, axp20x->regmap_cfg); + if (IS_ERR(axp20x->regmap)) { + ret = PTR_ERR(axp20x->regmap); + dev_err(&i2c->dev, "regmap init failed: %d\n", ret); + return ret; + } + + ret = regmap_add_irq_chip(axp20x->regmap, i2c->irq, + IRQF_ONESHOT | IRQF_SHARED, -1, + axp20x->regmap_irq_chip, + &axp20x->regmap_irqc); + if (ret) { + dev_err(&i2c->dev, "failed to add irq chip: %d\n", ret); + return ret; + } + + ret = mfd_add_devices(axp20x->dev, -1, axp20x->cells, + axp20x->nr_cells, NULL, 0, NULL); + + if (ret) { + dev_err(&i2c->dev, "failed to add MFD devices: %d\n", ret); + regmap_del_irq_chip(i2c->irq, axp20x->regmap_irqc); + return ret; + } + + if (!pm_power_off) { + axp20x_pm_power_off = axp20x; + pm_power_off = axp20x_power_off; + } + + dev_info(&i2c->dev, "AXP20X driver loaded\n"); + + return 0; +} + +static int axp20x_i2c_remove(struct i2c_client *i2c) +{ + struct axp20x_dev *axp20x = i2c_get_clientdata(i2c); + + if (axp20x == axp20x_pm_power_off) { + axp20x_pm_power_off = NULL; + pm_power_off = NULL; + } + + mfd_remove_devices(axp20x->dev); + regmap_del_irq_chip(axp20x->i2c_client->irq, axp20x->regmap_irqc); + + return 0; +} + +static struct i2c_driver axp20x_i2c_driver = { + .driver = { + .name = "axp20x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(axp20x_of_match), + .acpi_match_table = ACPI_PTR(axp20x_acpi_match), + }, + .probe = axp20x_i2c_probe, + .remove = axp20x_i2c_remove, + .id_table = axp20x_i2c_id, +}; + +module_i2c_driver(axp20x_i2c_driver); + +MODULE_DESCRIPTION("PMIC MFD core driver for AXP20X"); +MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/bcm590xx.c b/kernel/drivers/mfd/bcm590xx.c new file mode 100644 index 000000000..e334de000 --- /dev/null +++ b/kernel/drivers/mfd/bcm590xx.c @@ -0,0 +1,132 @@ +/* + * Broadcom BCM590xx PMU + * + * Copyright 2014 Linaro Limited + * Author: Matt Porter <mporter@linaro.org> + * + * 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/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/mfd/bcm590xx.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +static const struct mfd_cell bcm590xx_devs[] = { + { + .name = "bcm590xx-vregs", + }, +}; + +static const struct regmap_config bcm590xx_regmap_config_pri = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BCM590XX_MAX_REGISTER_PRI, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config bcm590xx_regmap_config_sec = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BCM590XX_MAX_REGISTER_SEC, + .cache_type = REGCACHE_RBTREE, +}; + +static int bcm590xx_i2c_probe(struct i2c_client *i2c_pri, + const struct i2c_device_id *id) +{ + struct bcm590xx *bcm590xx; + int ret; + + bcm590xx = devm_kzalloc(&i2c_pri->dev, sizeof(*bcm590xx), GFP_KERNEL); + if (!bcm590xx) + return -ENOMEM; + + i2c_set_clientdata(i2c_pri, bcm590xx); + bcm590xx->dev = &i2c_pri->dev; + bcm590xx->i2c_pri = i2c_pri; + + bcm590xx->regmap_pri = devm_regmap_init_i2c(i2c_pri, + &bcm590xx_regmap_config_pri); + if (IS_ERR(bcm590xx->regmap_pri)) { + ret = PTR_ERR(bcm590xx->regmap_pri); + dev_err(&i2c_pri->dev, "primary regmap init failed: %d\n", ret); + return ret; + } + + /* Secondary I2C slave address is the base address with A(2) asserted */ + bcm590xx->i2c_sec = i2c_new_dummy(i2c_pri->adapter, + i2c_pri->addr | BIT(2)); + if (IS_ERR_OR_NULL(bcm590xx->i2c_sec)) { + dev_err(&i2c_pri->dev, "failed to add secondary I2C device\n"); + return -ENODEV; + } + i2c_set_clientdata(bcm590xx->i2c_sec, bcm590xx); + + bcm590xx->regmap_sec = devm_regmap_init_i2c(bcm590xx->i2c_sec, + &bcm590xx_regmap_config_sec); + if (IS_ERR(bcm590xx->regmap_sec)) { + ret = PTR_ERR(bcm590xx->regmap_sec); + dev_err(&bcm590xx->i2c_sec->dev, + "secondary regmap init failed: %d\n", ret); + goto err; + } + + ret = mfd_add_devices(&i2c_pri->dev, -1, bcm590xx_devs, + ARRAY_SIZE(bcm590xx_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(&i2c_pri->dev, "failed to add sub-devices: %d\n", ret); + goto err; + } + + return 0; + +err: + i2c_unregister_device(bcm590xx->i2c_sec); + return ret; +} + +static int bcm590xx_i2c_remove(struct i2c_client *i2c) +{ + mfd_remove_devices(&i2c->dev); + return 0; +} + +static const struct of_device_id bcm590xx_of_match[] = { + { .compatible = "brcm,bcm59056" }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm590xx_of_match); + +static const struct i2c_device_id bcm590xx_i2c_id[] = { + { "bcm59056" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bcm590xx_i2c_id); + +static struct i2c_driver bcm590xx_i2c_driver = { + .driver = { + .name = "bcm590xx", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(bcm590xx_of_match), + }, + .probe = bcm590xx_i2c_probe, + .remove = bcm590xx_i2c_remove, + .id_table = bcm590xx_i2c_id, +}; +module_i2c_driver(bcm590xx_i2c_driver); + +MODULE_AUTHOR("Matt Porter <mporter@linaro.org>"); +MODULE_DESCRIPTION("BCM590xx multi-function driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:bcm590xx"); diff --git a/kernel/drivers/mfd/cros_ec.c b/kernel/drivers/mfd/cros_ec.c new file mode 100644 index 000000000..c4aecc6f8 --- /dev/null +++ b/kernel/drivers/mfd/cros_ec.c @@ -0,0 +1,198 @@ +/* + * ChromeOS EC multi-function device + * + * Copyright (C) 2012 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * The ChromeOS EC multi function device is used to mux all the requests + * to the EC device for its multiple features: keyboard controller, + * battery charging and regulator control, firmware update. + */ + +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/delay.h> + +#define EC_COMMAND_RETRIES 50 + +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + uint8_t *out; + int csum, i; + + BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); + out = ec_dev->dout; + out[0] = EC_CMD_VERSION0 + msg->version; + out[1] = msg->command; + out[2] = msg->outsize; + csum = out[0] + out[1] + out[2]; + for (i = 0; i < msg->outsize; i++) + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->outdata[i]; + out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff); + + return EC_MSG_TX_PROTO_BYTES + msg->outsize; +} +EXPORT_SYMBOL(cros_ec_prepare_tx); + +int cros_ec_check_result(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + switch (msg->result) { + case EC_RES_SUCCESS: + return 0; + case EC_RES_IN_PROGRESS: + dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", + msg->command); + return -EAGAIN; + default: + dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + return 0; + } +} +EXPORT_SYMBOL(cros_ec_check_result); + +int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + mutex_lock(&ec_dev->lock); + ret = ec_dev->cmd_xfer(ec_dev, msg); + if (msg->result == EC_RES_IN_PROGRESS) { + int i; + struct cros_ec_command status_msg = { }; + struct ec_response_get_comms_status *status; + + status_msg.command = EC_CMD_GET_COMMS_STATUS; + status_msg.insize = sizeof(*status); + + /* + * Query the EC's status until it's no longer busy or + * we encounter an error. + */ + for (i = 0; i < EC_COMMAND_RETRIES; i++) { + usleep_range(10000, 11000); + + ret = ec_dev->cmd_xfer(ec_dev, &status_msg); + if (ret < 0) + break; + + msg->result = status_msg.result; + if (status_msg.result != EC_RES_SUCCESS) + break; + + status = (struct ec_response_get_comms_status *) + status_msg.indata; + if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) + break; + } + } + mutex_unlock(&ec_dev->lock); + + return ret; +} +EXPORT_SYMBOL(cros_ec_cmd_xfer); + +static const struct mfd_cell cros_devs[] = { + { + .name = "cros-ec-keyb", + .id = 1, + .of_compatible = "google,cros-ec-keyb", + }, + { + .name = "cros-ec-i2c-tunnel", + .id = 2, + .of_compatible = "google,cros-ec-i2c-tunnel", + }, + { + .name = "cros-ec-ctl", + .id = 3, + }, +}; + +int cros_ec_register(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + int err = 0; + + if (ec_dev->din_size) { + ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) + return -ENOMEM; + } + if (ec_dev->dout_size) { + ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); + if (!ec_dev->dout) + return -ENOMEM; + } + + mutex_init(&ec_dev->lock); + + err = mfd_add_devices(dev, 0, cros_devs, + ARRAY_SIZE(cros_devs), + NULL, ec_dev->irq, NULL); + if (err) { + dev_err(dev, "failed to add mfd devices\n"); + return err; + } + + dev_info(dev, "Chrome EC device registered\n"); + + return 0; +} +EXPORT_SYMBOL(cros_ec_register); + +int cros_ec_remove(struct cros_ec_device *ec_dev) +{ + mfd_remove_devices(ec_dev->dev); + + return 0; +} +EXPORT_SYMBOL(cros_ec_remove); + +#ifdef CONFIG_PM_SLEEP +int cros_ec_suspend(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + + if (device_may_wakeup(dev)) + ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq); + + disable_irq(ec_dev->irq); + ec_dev->was_wake_device = ec_dev->wake_enabled; + + return 0; +} +EXPORT_SYMBOL(cros_ec_suspend); + +int cros_ec_resume(struct cros_ec_device *ec_dev) +{ + enable_irq(ec_dev->irq); + + if (ec_dev->wake_enabled) { + disable_irq_wake(ec_dev->irq); + ec_dev->wake_enabled = 0; + } + + return 0; +} +EXPORT_SYMBOL(cros_ec_resume); + +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC core driver"); diff --git a/kernel/drivers/mfd/cros_ec_i2c.c b/kernel/drivers/mfd/cros_ec_i2c.c new file mode 100644 index 000000000..c0c30f4f9 --- /dev/null +++ b/kernel/drivers/mfd/cros_ec_i2c.c @@ -0,0 +1,207 @@ +/* + * ChromeOS EC multi-function device (I2C) + * + * Copyright (C) 2012 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +static inline struct cros_ec_device *to_ec_dev(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_get_clientdata(client); +} + +static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + struct i2c_client *client = ec_dev->priv; + int ret = -ENOMEM; + int i; + int len; + int packet_len; + u8 *out_buf = NULL; + u8 *in_buf = NULL; + u8 sum; + struct i2c_msg i2c_msg[2]; + + i2c_msg[0].addr = client->addr; + i2c_msg[0].flags = 0; + i2c_msg[1].addr = client->addr; + i2c_msg[1].flags = I2C_M_RD; + + /* + * allocate larger packet (one byte for checksum, one byte for + * length, and one for result code) + */ + packet_len = msg->insize + 3; + in_buf = kzalloc(packet_len, GFP_KERNEL); + if (!in_buf) + goto done; + i2c_msg[1].len = packet_len; + i2c_msg[1].buf = (char *)in_buf; + + /* + * allocate larger packet (one byte for checksum, one for + * command code, one for length, and one for command version) + */ + packet_len = msg->outsize + 4; + out_buf = kzalloc(packet_len, GFP_KERNEL); + if (!out_buf) + goto done; + i2c_msg[0].len = packet_len; + i2c_msg[0].buf = (char *)out_buf; + + out_buf[0] = EC_CMD_VERSION0 + msg->version; + out_buf[1] = msg->command; + out_buf[2] = msg->outsize; + + /* copy message payload and compute checksum */ + sum = out_buf[0] + out_buf[1] + out_buf[2]; + for (i = 0; i < msg->outsize; i++) { + out_buf[3 + i] = msg->outdata[i]; + sum += out_buf[3 + i]; + } + out_buf[3 + msg->outsize] = sum; + + /* send command to EC and read answer */ + ret = i2c_transfer(client->adapter, i2c_msg, 2); + if (ret < 0) { + dev_err(ec_dev->dev, "i2c transfer failed: %d\n", ret); + goto done; + } else if (ret != 2) { + dev_err(ec_dev->dev, "failed to get response: %d\n", ret); + ret = -EIO; + goto done; + } + + /* check response error code */ + msg->result = i2c_msg[1].buf[0]; + ret = cros_ec_check_result(ec_dev, msg); + if (ret) + goto done; + + len = in_buf[1]; + if (len > msg->insize) { + dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)", + len, msg->insize); + ret = -ENOSPC; + goto done; + } + + /* copy response packet payload and compute checksum */ + sum = in_buf[0] + in_buf[1]; + for (i = 0; i < len; i++) { + msg->indata[i] = in_buf[2 + i]; + sum += in_buf[2 + i]; + } + dev_dbg(ec_dev->dev, "packet: %*ph, sum = %02x\n", + i2c_msg[1].len, in_buf, sum); + if (sum != in_buf[2 + len]) { + dev_err(ec_dev->dev, "bad packet checksum\n"); + ret = -EBADMSG; + goto done; + } + + ret = len; + done: + kfree(in_buf); + kfree(out_buf); + return ret; +} + +static int cros_ec_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct cros_ec_device *ec_dev = NULL; + int err; + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + i2c_set_clientdata(client, ec_dev); + ec_dev->dev = dev; + ec_dev->priv = client; + ec_dev->irq = client->irq; + ec_dev->cmd_xfer = cros_ec_cmd_xfer_i2c; + ec_dev->ec_name = client->name; + ec_dev->phys_name = client->adapter->name; + ec_dev->parent = &client->dev; + + err = cros_ec_register(ec_dev); + if (err) { + dev_err(dev, "cannot register EC\n"); + return err; + } + + return 0; +} + +static int cros_ec_i2c_remove(struct i2c_client *client) +{ + struct cros_ec_device *ec_dev = i2c_get_clientdata(client); + + cros_ec_remove(ec_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cros_ec_i2c_suspend(struct device *dev) +{ + struct cros_ec_device *ec_dev = to_ec_dev(dev); + + return cros_ec_suspend(ec_dev); +} + +static int cros_ec_i2c_resume(struct device *dev) +{ + struct cros_ec_device *ec_dev = to_ec_dev(dev); + + return cros_ec_resume(ec_dev); +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_ec_i2c_pm_ops, cros_ec_i2c_suspend, + cros_ec_i2c_resume); + +static const struct i2c_device_id cros_ec_i2c_id[] = { + { "cros-ec-i2c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cros_ec_i2c_id); + +static struct i2c_driver cros_ec_driver = { + .driver = { + .name = "cros-ec-i2c", + .owner = THIS_MODULE, + .pm = &cros_ec_i2c_pm_ops, + }, + .probe = cros_ec_i2c_probe, + .remove = cros_ec_i2c_remove, + .id_table = cros_ec_i2c_id, +}; + +module_i2c_driver(cros_ec_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC multi function device"); diff --git a/kernel/drivers/mfd/cros_ec_spi.c b/kernel/drivers/mfd/cros_ec_spi.c new file mode 100644 index 000000000..bf6e08e80 --- /dev/null +++ b/kernel/drivers/mfd/cros_ec_spi.c @@ -0,0 +1,430 @@ +/* + * ChromeOS EC multi-function device (SPI) + * + * Copyright (C) 2012 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> + + +/* The header byte, which follows the preamble */ +#define EC_MSG_HEADER 0xec + +/* + * Number of EC preamble bytes we read at a time. Since it takes + * about 400-500us for the EC to respond there is not a lot of + * point in tuning this. If the EC could respond faster then + * we could increase this so that might expect the preamble and + * message to occur in a single transaction. However, the maximum + * SPI transfer size is 256 bytes, so at 5MHz we need a response + * time of perhaps <320us (200 bytes / 1600 bits). + */ +#define EC_MSG_PREAMBLE_COUNT 32 + +/* + * Allow for a long time for the EC to respond. We support i2c + * tunneling and support fairly long messages for the tunnel (249 + * bytes long at the moment). If we're talking to a 100 kHz device + * on the other end and need to transfer ~256 bytes, then we need: + * 10 us/bit * ~10 bits/byte * ~256 bytes = ~25ms + * + * We'll wait 4 times that to handle clock stretching and other + * paranoia. + * + * It's pretty unlikely that we'll really see a 249 byte tunnel in + * anything other than testing. If this was more common we might + * consider having slow commands like this require a GET_STATUS + * wait loop. The 'flash write' command would be another candidate + * for this, clocking in at 2-3ms. + */ +#define EC_MSG_DEADLINE_MS 100 + +/* + * Time between raising the SPI chip select (for the end of a + * transaction) and dropping it again (for the next transaction). + * If we go too fast, the EC will miss the transaction. We know that we + * need at least 70 us with the 16 MHz STM32 EC, so go with 200 us to be + * safe. + */ +#define EC_SPI_RECOVERY_TIME_NS (200 * 1000) + +/* + * The EC is unresponsive for a time after a reboot command. Add a + * simple delay to make sure that the bus stays locked. + */ +#define EC_REBOOT_DELAY_MS 50 + +/** + * struct cros_ec_spi - information about a SPI-connected EC + * + * @spi: SPI device we are connected to + * @last_transfer_ns: time that we last finished a transfer, or 0 if there + * if no record + * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that + * is sent when we want to turn off CS at the end of a transaction. + */ +struct cros_ec_spi { + struct spi_device *spi; + s64 last_transfer_ns; + unsigned int end_of_msg_delay; +}; + +static void debug_packet(struct device *dev, const char *name, u8 *ptr, + int len) +{ +#ifdef DEBUG + int i; + + dev_dbg(dev, "%s: ", name); + for (i = 0; i < len; i++) + pr_cont(" %02x", ptr[i]); + + pr_cont("\n"); +#endif +} + +/** + * cros_ec_spi_receive_response - Receive a response from the EC. + * + * This function has two phases: reading the preamble bytes (since if we read + * data from the EC before it is ready to send, we just get preamble) and + * reading the actual message. + * + * The received data is placed into ec_dev->din. + * + * @ec_dev: ChromeOS EC device + * @need_len: Number of message bytes we need to read + */ +static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, + int need_len) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + u8 *ptr, *end; + int ret; + unsigned long deadline; + int todo; + + /* Receive data until we see the header byte */ + deadline = jiffies + msecs_to_jiffies(EC_MSG_DEADLINE_MS); + while (true) { + unsigned long start_jiffies = jiffies; + + memset(&trans, 0, sizeof(trans)); + trans.cs_change = 1; + trans.rx_buf = ptr = ec_dev->din; + trans.len = EC_MSG_PREAMBLE_COUNT; + + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + if (ret < 0) { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + return ret; + } + + for (end = ptr + EC_MSG_PREAMBLE_COUNT; ptr != end; ptr++) { + if (*ptr == EC_MSG_HEADER) { + dev_dbg(ec_dev->dev, "msg found at %zd\n", + ptr - ec_dev->din); + break; + } + } + if (ptr != end) + break; + + /* + * Use the time at the start of the loop as a timeout. This + * gives us one last shot at getting the transfer and is useful + * in case we got context switched out for a while. + */ + if (time_after(start_jiffies, deadline)) { + dev_warn(ec_dev->dev, "EC failed to respond in time\n"); + return -ETIMEDOUT; + } + } + + /* + * ptr now points to the header byte. Copy any valid data to the + * start of our buffer + */ + todo = end - ++ptr; + BUG_ON(todo < 0 || todo > ec_dev->din_size); + todo = min(todo, need_len); + memmove(ec_dev->din, ptr, todo); + ptr = ec_dev->din + todo; + dev_dbg(ec_dev->dev, "need %d, got %d bytes from preamble\n", + need_len, todo); + need_len -= todo; + + /* Receive data until we have it all */ + while (need_len > 0) { + /* + * We can't support transfers larger than the SPI FIFO size + * unless we have DMA. We don't have DMA on the ISP SPI ports + * for Exynos. We need a way of asking SPI driver for + * maximum-supported transfer size. + */ + todo = min(need_len, 256); + dev_dbg(ec_dev->dev, "loop, todo=%d, need_len=%d, ptr=%zd\n", + todo, need_len, ptr - ec_dev->din); + + memset(&trans, 0, sizeof(trans)); + trans.cs_change = 1; + trans.rx_buf = ptr; + trans.len = todo; + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + + /* send command to EC and read answer */ + BUG_ON((u8 *)trans.rx_buf - ec_dev->din + todo > + ec_dev->din_size); + ret = spi_sync(ec_spi->spi, &msg); + if (ret < 0) { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + return ret; + } + + debug_packet(ec_dev->dev, "interim", ptr, todo); + ptr += todo; + need_len -= todo; + } + + dev_dbg(ec_dev->dev, "loop done, ptr=%zd\n", ptr - ec_dev->din); + + return 0; +} + +/** + * cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply + * + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + */ +static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + int i, len; + u8 *ptr; + int sum; + int ret = 0, final_ret; + + len = cros_ec_prepare_tx(ec_dev, ec_msg); + dev_dbg(ec_dev->dev, "prepared, len=%d\n", len); + + /* If it's too soon to do another transaction, wait */ + if (ec_spi->last_transfer_ns) { + unsigned long delay; /* The delay completed so far */ + + delay = ktime_get_ns() - ec_spi->last_transfer_ns; + if (delay < EC_SPI_RECOVERY_TIME_NS) + ndelay(EC_SPI_RECOVERY_TIME_NS - delay); + } + + /* Transmit phase - send our message */ + debug_packet(ec_dev->dev, "out", ec_dev->dout, len); + memset(&trans, 0, sizeof(trans)); + trans.tx_buf = ec_dev->dout; + trans.len = len; + trans.cs_change = 1; + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + + /* Get the response */ + if (!ret) { + ret = cros_ec_spi_receive_response(ec_dev, + ec_msg->insize + EC_MSG_TX_PROTO_BYTES); + } else { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + } + + /* + * Turn off CS, possibly adding a delay to ensure the rising edge + * doesn't come too soon after the end of the data. + */ + spi_message_init(&msg); + memset(&trans, 0, sizeof(trans)); + trans.delay_usecs = ec_spi->end_of_msg_delay; + spi_message_add_tail(&trans, &msg); + + final_ret = spi_sync(ec_spi->spi, &msg); + ec_spi->last_transfer_ns = ktime_get_ns(); + if (!ret) + ret = final_ret; + if (ret < 0) { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + goto exit; + } + + ptr = ec_dev->din; + + /* check response error code */ + ec_msg->result = ptr[0]; + ret = cros_ec_check_result(ec_dev, ec_msg); + if (ret) + goto exit; + + len = ptr[1]; + sum = ptr[0] + ptr[1]; + if (len > ec_msg->insize) { + dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)", + len, ec_msg->insize); + ret = -ENOSPC; + goto exit; + } + + /* copy response packet payload and compute checksum */ + for (i = 0; i < len; i++) { + sum += ptr[i + 2]; + if (ec_msg->insize) + ec_msg->indata[i] = ptr[i + 2]; + } + sum &= 0xff; + + debug_packet(ec_dev->dev, "in", ptr, len + 3); + + if (sum != ptr[len + 2]) { + dev_err(ec_dev->dev, + "bad packet checksum, expected %02x, got %02x\n", + sum, ptr[len + 2]); + ret = -EBADMSG; + goto exit; + } + + ret = len; +exit: + if (ec_msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + return ret; +} + +static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) +{ + struct device_node *np = dev->of_node; + u32 val; + int ret; + + ret = of_property_read_u32(np, "google,cros-ec-spi-msg-delay", &val); + if (!ret) + ec_spi->end_of_msg_delay = val; +} + +static int cros_ec_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct cros_ec_device *ec_dev; + struct cros_ec_spi *ec_spi; + int err; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + err = spi_setup(spi); + if (err < 0) + return err; + + ec_spi = devm_kzalloc(dev, sizeof(*ec_spi), GFP_KERNEL); + if (ec_spi == NULL) + return -ENOMEM; + ec_spi->spi = spi; + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + /* Check for any DT properties */ + cros_ec_spi_dt_probe(ec_spi, dev); + + spi_set_drvdata(spi, ec_dev); + ec_dev->dev = dev; + ec_dev->priv = ec_spi; + ec_dev->irq = spi->irq; + ec_dev->cmd_xfer = cros_ec_cmd_xfer_spi; + ec_dev->ec_name = ec_spi->spi->modalias; + ec_dev->phys_name = dev_name(&ec_spi->spi->dev); + ec_dev->parent = &ec_spi->spi->dev; + ec_dev->din_size = EC_MSG_BYTES + EC_MSG_PREAMBLE_COUNT; + ec_dev->dout_size = EC_MSG_BYTES; + + err = cros_ec_register(ec_dev); + if (err) { + dev_err(dev, "cannot register EC\n"); + return err; + } + + device_init_wakeup(&spi->dev, true); + + return 0; +} + +static int cros_ec_spi_remove(struct spi_device *spi) +{ + struct cros_ec_device *ec_dev; + + ec_dev = spi_get_drvdata(spi); + cros_ec_remove(ec_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cros_ec_spi_suspend(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_suspend(ec_dev); +} + +static int cros_ec_spi_resume(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_resume(ec_dev); +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_ec_spi_pm_ops, cros_ec_spi_suspend, + cros_ec_spi_resume); + +static const struct spi_device_id cros_ec_spi_id[] = { + { "cros-ec-spi", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, cros_ec_spi_id); + +static struct spi_driver cros_ec_driver_spi = { + .driver = { + .name = "cros-ec-spi", + .owner = THIS_MODULE, + .pm = &cros_ec_spi_pm_ops, + }, + .probe = cros_ec_spi_probe, + .remove = cros_ec_spi_remove, + .id_table = cros_ec_spi_id, +}; + +module_spi_driver(cros_ec_driver_spi); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ChromeOS EC multi function device (SPI)"); diff --git a/kernel/drivers/mfd/cs5535-mfd.c b/kernel/drivers/mfd/cs5535-mfd.c new file mode 100644 index 000000000..be91cb5d6 --- /dev/null +++ b/kernel/drivers/mfd/cs5535-mfd.c @@ -0,0 +1,192 @@ +/* + * cs5535-mfd.c - core MFD driver for CS5535/CS5536 southbridges + * + * The CS5535 and CS5536 has an ISA bridge on the PCI bus that is + * used for accessing GPIOs, MFGPTs, ACPI, etc. Each subdevice has + * an IO range that's specified in a single BAR. The BAR order is + * hardcoded in the CS553x specifications. + * + * Copyright (c) 2010 Andres Salomon <dilinger@queued.net> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <asm/olpc.h> + +#define DRV_NAME "cs5535-mfd" + +enum cs5535_mfd_bars { + SMB_BAR = 0, + GPIO_BAR = 1, + MFGPT_BAR = 2, + PMS_BAR = 4, + ACPI_BAR = 5, + NR_BARS, +}; + +static int cs5535_mfd_res_enable(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) { + dev_err(&pdev->dev, "can't fetch device resource info\n"); + return -EIO; + } + + if (!request_region(res->start, resource_size(res), DRV_NAME)) { + dev_err(&pdev->dev, "can't request region\n"); + return -EIO; + } + + return 0; +} + +static int cs5535_mfd_res_disable(struct platform_device *pdev) +{ + struct resource *res; + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) { + dev_err(&pdev->dev, "can't fetch device resource info\n"); + return -EIO; + } + + release_region(res->start, resource_size(res)); + return 0; +} + +static struct resource cs5535_mfd_resources[NR_BARS]; + +static struct mfd_cell cs5535_mfd_cells[] = { + { + .id = SMB_BAR, + .name = "cs5535-smb", + .num_resources = 1, + .resources = &cs5535_mfd_resources[SMB_BAR], + }, + { + .id = GPIO_BAR, + .name = "cs5535-gpio", + .num_resources = 1, + .resources = &cs5535_mfd_resources[GPIO_BAR], + }, + { + .id = MFGPT_BAR, + .name = "cs5535-mfgpt", + .num_resources = 1, + .resources = &cs5535_mfd_resources[MFGPT_BAR], + }, + { + .id = PMS_BAR, + .name = "cs5535-pms", + .num_resources = 1, + .resources = &cs5535_mfd_resources[PMS_BAR], + + .enable = cs5535_mfd_res_enable, + .disable = cs5535_mfd_res_disable, + }, + { + .id = ACPI_BAR, + .name = "cs5535-acpi", + .num_resources = 1, + .resources = &cs5535_mfd_resources[ACPI_BAR], + + .enable = cs5535_mfd_res_enable, + .disable = cs5535_mfd_res_disable, + }, +}; + +#ifdef CONFIG_OLPC +static void cs5535_clone_olpc_cells(void) +{ + const char *acpi_clones[] = { "olpc-xo1-pm-acpi", "olpc-xo1-sci-acpi" }; + + if (!machine_is_olpc()) + return; + + mfd_clone_cell("cs5535-acpi", acpi_clones, ARRAY_SIZE(acpi_clones)); +} +#else +static void cs5535_clone_olpc_cells(void) { } +#endif + +static int cs5535_mfd_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int err, i; + + err = pci_enable_device(pdev); + if (err) + return err; + + /* fill in IO range for each cell; subdrivers handle the region */ + for (i = 0; i < ARRAY_SIZE(cs5535_mfd_cells); i++) { + int bar = cs5535_mfd_cells[i].id; + struct resource *r = &cs5535_mfd_resources[bar]; + + r->flags = IORESOURCE_IO; + r->start = pci_resource_start(pdev, bar); + r->end = pci_resource_end(pdev, bar); + + /* id is used for temporarily storing BAR; unset it now */ + cs5535_mfd_cells[i].id = 0; + } + + err = mfd_add_devices(&pdev->dev, -1, cs5535_mfd_cells, + ARRAY_SIZE(cs5535_mfd_cells), NULL, 0, NULL); + if (err) { + dev_err(&pdev->dev, "MFD add devices failed: %d\n", err); + goto err_disable; + } + cs5535_clone_olpc_cells(); + + dev_info(&pdev->dev, "%zu devices registered.\n", + ARRAY_SIZE(cs5535_mfd_cells)); + + return 0; + +err_disable: + pci_disable_device(pdev); + return err; +} + +static void cs5535_mfd_remove(struct pci_dev *pdev) +{ + mfd_remove_devices(&pdev->dev); + pci_disable_device(pdev); +} + +static const struct pci_device_id cs5535_mfd_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, cs5535_mfd_pci_tbl); + +static struct pci_driver cs5535_mfd_driver = { + .name = DRV_NAME, + .id_table = cs5535_mfd_pci_tbl, + .probe = cs5535_mfd_probe, + .remove = cs5535_mfd_remove, +}; + +module_pci_driver(cs5535_mfd_driver); + +MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>"); +MODULE_DESCRIPTION("MFD driver for CS5535/CS5536 southbridge's ISA PCI device"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da903x.c b/kernel/drivers/mfd/da903x.c new file mode 100644 index 000000000..e0a2e0ee6 --- /dev/null +++ b/kernel/drivers/mfd/da903x.c @@ -0,0 +1,575 @@ +/* + * Base driver for Dialog Semiconductor DA9030/DA9034 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/mfd/da903x.h> +#include <linux/slab.h> + +#define DA9030_CHIP_ID 0x00 +#define DA9030_EVENT_A 0x01 +#define DA9030_EVENT_B 0x02 +#define DA9030_EVENT_C 0x03 +#define DA9030_STATUS 0x04 +#define DA9030_IRQ_MASK_A 0x05 +#define DA9030_IRQ_MASK_B 0x06 +#define DA9030_IRQ_MASK_C 0x07 +#define DA9030_SYS_CTRL_A 0x08 +#define DA9030_SYS_CTRL_B 0x09 +#define DA9030_FAULT_LOG 0x0a + +#define DA9034_CHIP_ID 0x00 +#define DA9034_EVENT_A 0x01 +#define DA9034_EVENT_B 0x02 +#define DA9034_EVENT_C 0x03 +#define DA9034_EVENT_D 0x04 +#define DA9034_STATUS_A 0x05 +#define DA9034_STATUS_B 0x06 +#define DA9034_IRQ_MASK_A 0x07 +#define DA9034_IRQ_MASK_B 0x08 +#define DA9034_IRQ_MASK_C 0x09 +#define DA9034_IRQ_MASK_D 0x0a +#define DA9034_SYS_CTRL_A 0x0b +#define DA9034_SYS_CTRL_B 0x0c +#define DA9034_FAULT_LOG 0x0d + +struct da903x_chip; + +struct da903x_chip_ops { + int (*init_chip)(struct da903x_chip *); + int (*unmask_events)(struct da903x_chip *, unsigned int events); + int (*mask_events)(struct da903x_chip *, unsigned int events); + int (*read_events)(struct da903x_chip *, unsigned int *events); + int (*read_status)(struct da903x_chip *, unsigned int *status); +}; + +struct da903x_chip { + struct i2c_client *client; + struct device *dev; + struct da903x_chip_ops *ops; + + int type; + uint32_t events_mask; + + struct mutex lock; + struct work_struct irq_work; + + struct blocking_notifier_head notifier_list; +}; + +static inline int __da903x_read(struct i2c_client *client, + int reg, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "failed reading at 0x%02x\n", reg); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static inline int __da903x_reads(struct i2c_client *client, int reg, + int len, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, val); + if (ret < 0) { + dev_err(&client->dev, "failed reading from 0x%02x\n", reg); + return ret; + } + return 0; +} + +static inline int __da903x_write(struct i2c_client *client, + int reg, uint8_t val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", + val, reg); + return ret; + } + return 0; +} + +static inline int __da903x_writes(struct i2c_client *client, int reg, + int len, uint8_t *val) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, reg, len, val); + if (ret < 0) { + dev_err(&client->dev, "failed writings to 0x%02x\n", reg); + return ret; + } + return 0; +} + +int da903x_register_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + + chip->ops->unmask_events(chip, events); + return blocking_notifier_chain_register(&chip->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(da903x_register_notifier); + +int da903x_unregister_notifier(struct device *dev, struct notifier_block *nb, + unsigned int events) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + + chip->ops->mask_events(chip, events); + return blocking_notifier_chain_unregister(&chip->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(da903x_unregister_notifier); + +int da903x_write(struct device *dev, int reg, uint8_t val) +{ + return __da903x_write(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(da903x_write); + +int da903x_writes(struct device *dev, int reg, int len, uint8_t *val) +{ + return __da903x_writes(to_i2c_client(dev), reg, len, val); +} +EXPORT_SYMBOL_GPL(da903x_writes); + +int da903x_read(struct device *dev, int reg, uint8_t *val) +{ + return __da903x_read(to_i2c_client(dev), reg, val); +} +EXPORT_SYMBOL_GPL(da903x_read); + +int da903x_reads(struct device *dev, int reg, int len, uint8_t *val) +{ + return __da903x_reads(to_i2c_client(dev), reg, len, val); +} +EXPORT_SYMBOL_GPL(da903x_reads); + +int da903x_set_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&chip->lock); + + ret = __da903x_read(chip->client, reg, ®_val); + if (ret) + goto out; + + if ((reg_val & bit_mask) != bit_mask) { + reg_val |= bit_mask; + ret = __da903x_write(chip->client, reg, reg_val); + } +out: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(da903x_set_bits); + +int da903x_clr_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&chip->lock); + + ret = __da903x_read(chip->client, reg, ®_val); + if (ret) + goto out; + + if (reg_val & bit_mask) { + reg_val &= ~bit_mask; + ret = __da903x_write(chip->client, reg, reg_val); + } +out: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(da903x_clr_bits); + +int da903x_update(struct device *dev, int reg, uint8_t val, uint8_t mask) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + uint8_t reg_val; + int ret = 0; + + mutex_lock(&chip->lock); + + ret = __da903x_read(chip->client, reg, ®_val); + if (ret) + goto out; + + if ((reg_val & mask) != val) { + reg_val = (reg_val & ~mask) | val; + ret = __da903x_write(chip->client, reg, reg_val); + } +out: + mutex_unlock(&chip->lock); + return ret; +} +EXPORT_SYMBOL_GPL(da903x_update); + +int da903x_query_status(struct device *dev, unsigned int sbits) +{ + struct da903x_chip *chip = dev_get_drvdata(dev); + unsigned int status = 0; + + chip->ops->read_status(chip, &status); + return ((status & sbits) == sbits); +} +EXPORT_SYMBOL(da903x_query_status); + +static int da9030_init_chip(struct da903x_chip *chip) +{ + uint8_t chip_id; + int err; + + err = __da903x_read(chip->client, DA9030_CHIP_ID, &chip_id); + if (err) + return err; + + err = __da903x_write(chip->client, DA9030_SYS_CTRL_A, 0xE8); + if (err) + return err; + + dev_info(chip->dev, "DA9030 (CHIP ID: 0x%02x) detected\n", chip_id); + return 0; +} + +static int da9030_unmask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[3]; + + chip->events_mask &= ~events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + + return __da903x_writes(chip->client, DA9030_IRQ_MASK_A, 3, v); +} + +static int da9030_mask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[3]; + + chip->events_mask |= events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + + return __da903x_writes(chip->client, DA9030_IRQ_MASK_A, 3, v); +} + +static int da9030_read_events(struct da903x_chip *chip, unsigned int *events) +{ + uint8_t v[3] = {0, 0, 0}; + int ret; + + ret = __da903x_reads(chip->client, DA9030_EVENT_A, 3, v); + if (ret < 0) + return ret; + + *events = (v[2] << 16) | (v[1] << 8) | v[0]; + return 0; +} + +static int da9030_read_status(struct da903x_chip *chip, unsigned int *status) +{ + return __da903x_read(chip->client, DA9030_STATUS, (uint8_t *)status); +} + +static int da9034_init_chip(struct da903x_chip *chip) +{ + uint8_t chip_id; + int err; + + err = __da903x_read(chip->client, DA9034_CHIP_ID, &chip_id); + if (err) + return err; + + err = __da903x_write(chip->client, DA9034_SYS_CTRL_A, 0xE8); + if (err) + return err; + + /* avoid SRAM power off during sleep*/ + __da903x_write(chip->client, 0x10, 0x07); + __da903x_write(chip->client, 0x11, 0xff); + __da903x_write(chip->client, 0x12, 0xff); + + /* Enable the ONKEY power down functionality */ + __da903x_write(chip->client, DA9034_SYS_CTRL_B, 0x20); + __da903x_write(chip->client, DA9034_SYS_CTRL_A, 0x60); + + /* workaround to make LEDs work */ + __da903x_write(chip->client, 0x90, 0x01); + __da903x_write(chip->client, 0xB0, 0x08); + + /* make ADTV1 and SDTV1 effective */ + __da903x_write(chip->client, 0x20, 0x00); + + dev_info(chip->dev, "DA9034 (CHIP ID: 0x%02x) detected\n", chip_id); + return 0; +} + +static int da9034_unmask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[4]; + + chip->events_mask &= ~events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + v[3] = (chip->events_mask >> 24) & 0xff; + + return __da903x_writes(chip->client, DA9034_IRQ_MASK_A, 4, v); +} + +static int da9034_mask_events(struct da903x_chip *chip, unsigned int events) +{ + uint8_t v[4]; + + chip->events_mask |= events; + + v[0] = (chip->events_mask & 0xff); + v[1] = (chip->events_mask >> 8) & 0xff; + v[2] = (chip->events_mask >> 16) & 0xff; + v[3] = (chip->events_mask >> 24) & 0xff; + + return __da903x_writes(chip->client, DA9034_IRQ_MASK_A, 4, v); +} + +static int da9034_read_events(struct da903x_chip *chip, unsigned int *events) +{ + uint8_t v[4] = {0, 0, 0, 0}; + int ret; + + ret = __da903x_reads(chip->client, DA9034_EVENT_A, 4, v); + if (ret < 0) + return ret; + + *events = (v[3] << 24) | (v[2] << 16) | (v[1] << 8) | v[0]; + return 0; +} + +static int da9034_read_status(struct da903x_chip *chip, unsigned int *status) +{ + uint8_t v[2] = {0, 0}; + int ret = 0; + + ret = __da903x_reads(chip->client, DA9034_STATUS_A, 2, v); + if (ret) + return ret; + + *status = (v[1] << 8) | v[0]; + return 0; +} + +static void da903x_irq_work(struct work_struct *work) +{ + struct da903x_chip *chip = + container_of(work, struct da903x_chip, irq_work); + unsigned int events = 0; + + while (1) { + if (chip->ops->read_events(chip, &events)) + break; + + events &= ~chip->events_mask; + if (events == 0) + break; + + blocking_notifier_call_chain( + &chip->notifier_list, events, NULL); + } + enable_irq(chip->client->irq); +} + +static irqreturn_t da903x_irq_handler(int irq, void *data) +{ + struct da903x_chip *chip = data; + + disable_irq_nosync(irq); + (void)schedule_work(&chip->irq_work); + + return IRQ_HANDLED; +} + +static struct da903x_chip_ops da903x_ops[] = { + [0] = { + .init_chip = da9030_init_chip, + .unmask_events = da9030_unmask_events, + .mask_events = da9030_mask_events, + .read_events = da9030_read_events, + .read_status = da9030_read_status, + }, + [1] = { + .init_chip = da9034_init_chip, + .unmask_events = da9034_unmask_events, + .mask_events = da9034_mask_events, + .read_events = da9034_read_events, + .read_status = da9034_read_status, + } +}; + +static const struct i2c_device_id da903x_id_table[] = { + { "da9030", 0 }, + { "da9034", 1 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, da903x_id_table); + +static int __remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int da903x_remove_subdevs(struct da903x_chip *chip) +{ + return device_for_each_child(chip->dev, NULL, __remove_subdev); +} + +static int da903x_add_subdevs(struct da903x_chip *chip, + struct da903x_platform_data *pdata) +{ + struct da903x_subdev_info *subdev; + struct platform_device *pdev; + int i, ret = 0; + + for (i = 0; i < pdata->num_subdevs; i++) { + subdev = &pdata->subdevs[i]; + + pdev = platform_device_alloc(subdev->name, subdev->id); + if (!pdev) { + ret = -ENOMEM; + goto failed; + } + + pdev->dev.parent = chip->dev; + pdev->dev.platform_data = subdev->platform_data; + + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto failed; + } + } + return 0; + +failed: + da903x_remove_subdevs(chip); + return ret; +} + +static int da903x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct da903x_platform_data *pdata = dev_get_platdata(&client->dev); + struct da903x_chip *chip; + unsigned int tmp; + int ret; + + chip = devm_kzalloc(&client->dev, sizeof(struct da903x_chip), + GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + chip->ops = &da903x_ops[id->driver_data]; + + mutex_init(&chip->lock); + INIT_WORK(&chip->irq_work, da903x_irq_work); + BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list); + + i2c_set_clientdata(client, chip); + + ret = chip->ops->init_chip(chip); + if (ret) + return ret; + + /* mask and clear all IRQs */ + chip->events_mask = 0xffffffff; + chip->ops->mask_events(chip, chip->events_mask); + chip->ops->read_events(chip, &tmp); + + ret = devm_request_irq(&client->dev, client->irq, da903x_irq_handler, + IRQF_TRIGGER_FALLING, + "da903x", chip); + if (ret) { + dev_err(&client->dev, "failed to request irq %d\n", + client->irq); + return ret; + } + + ret = da903x_add_subdevs(chip, pdata); + if (ret) + return ret; + + return 0; +} + +static int da903x_remove(struct i2c_client *client) +{ + struct da903x_chip *chip = i2c_get_clientdata(client); + + da903x_remove_subdevs(chip); + return 0; +} + +static struct i2c_driver da903x_driver = { + .driver = { + .name = "da903x", + .owner = THIS_MODULE, + }, + .probe = da903x_probe, + .remove = da903x_remove, + .id_table = da903x_id_table, +}; + +static int __init da903x_init(void) +{ + return i2c_add_driver(&da903x_driver); +} +subsys_initcall(da903x_init); + +static void __exit da903x_exit(void) +{ + i2c_del_driver(&da903x_driver); +} +module_exit(da903x_exit); + +MODULE_DESCRIPTION("PMIC Driver for Dialog Semiconductor DA9034"); +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>" + "Mike Rapoport <mike@compulab.co.il>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da9052-core.c b/kernel/drivers/mfd/da9052-core.c new file mode 100644 index 000000000..46e3840c7 --- /dev/null +++ b/kernel/drivers/mfd/da9052-core.c @@ -0,0 +1,581 @@ +/* + * Device access for Dialog DA9052 PMICs. + * + * Copyright(c) 2011 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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/device.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/pdata.h> +#include <linux/mfd/da9052/reg.h> + +static bool da9052_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9052_PAGE0_CON_REG: + case DA9052_STATUS_A_REG: + case DA9052_STATUS_B_REG: + case DA9052_STATUS_C_REG: + case DA9052_STATUS_D_REG: + case DA9052_EVENT_A_REG: + case DA9052_EVENT_B_REG: + case DA9052_EVENT_C_REG: + case DA9052_EVENT_D_REG: + case DA9052_FAULTLOG_REG: + case DA9052_IRQ_MASK_A_REG: + case DA9052_IRQ_MASK_B_REG: + case DA9052_IRQ_MASK_C_REG: + case DA9052_IRQ_MASK_D_REG: + case DA9052_CONTROL_A_REG: + case DA9052_CONTROL_B_REG: + case DA9052_CONTROL_C_REG: + case DA9052_CONTROL_D_REG: + case DA9052_PDDIS_REG: + case DA9052_INTERFACE_REG: + case DA9052_RESET_REG: + case DA9052_GPIO_0_1_REG: + case DA9052_GPIO_2_3_REG: + case DA9052_GPIO_4_5_REG: + case DA9052_GPIO_6_7_REG: + case DA9052_GPIO_14_15_REG: + case DA9052_ID_0_1_REG: + case DA9052_ID_2_3_REG: + case DA9052_ID_4_5_REG: + case DA9052_ID_6_7_REG: + case DA9052_ID_8_9_REG: + case DA9052_ID_10_11_REG: + case DA9052_ID_12_13_REG: + case DA9052_ID_14_15_REG: + case DA9052_ID_16_17_REG: + case DA9052_ID_18_19_REG: + case DA9052_ID_20_21_REG: + case DA9052_SEQ_STATUS_REG: + case DA9052_SEQ_A_REG: + case DA9052_SEQ_B_REG: + case DA9052_SEQ_TIMER_REG: + case DA9052_BUCKA_REG: + case DA9052_BUCKB_REG: + case DA9052_BUCKCORE_REG: + case DA9052_BUCKPRO_REG: + case DA9052_BUCKMEM_REG: + case DA9052_BUCKPERI_REG: + case DA9052_LDO1_REG: + case DA9052_LDO2_REG: + case DA9052_LDO3_REG: + case DA9052_LDO4_REG: + case DA9052_LDO5_REG: + case DA9052_LDO6_REG: + case DA9052_LDO7_REG: + case DA9052_LDO8_REG: + case DA9052_LDO9_REG: + case DA9052_LDO10_REG: + case DA9052_SUPPLY_REG: + case DA9052_PULLDOWN_REG: + case DA9052_CHGBUCK_REG: + case DA9052_WAITCONT_REG: + case DA9052_ISET_REG: + case DA9052_BATCHG_REG: + case DA9052_CHG_CONT_REG: + case DA9052_INPUT_CONT_REG: + case DA9052_CHG_TIME_REG: + case DA9052_BBAT_CONT_REG: + case DA9052_BOOST_REG: + case DA9052_LED_CONT_REG: + case DA9052_LEDMIN123_REG: + case DA9052_LED1_CONF_REG: + case DA9052_LED2_CONF_REG: + case DA9052_LED3_CONF_REG: + case DA9052_LED1CONT_REG: + case DA9052_LED2CONT_REG: + case DA9052_LED3CONT_REG: + case DA9052_LED_CONT_4_REG: + case DA9052_LED_CONT_5_REG: + case DA9052_ADC_MAN_REG: + case DA9052_ADC_CONT_REG: + case DA9052_ADC_RES_L_REG: + case DA9052_ADC_RES_H_REG: + case DA9052_VDD_RES_REG: + case DA9052_VDD_MON_REG: + case DA9052_ICHG_AV_REG: + case DA9052_ICHG_THD_REG: + case DA9052_ICHG_END_REG: + case DA9052_TBAT_RES_REG: + case DA9052_TBAT_HIGHP_REG: + case DA9052_TBAT_HIGHN_REG: + case DA9052_TBAT_LOW_REG: + case DA9052_T_OFFSET_REG: + case DA9052_ADCIN4_RES_REG: + case DA9052_AUTO4_HIGH_REG: + case DA9052_AUTO4_LOW_REG: + case DA9052_ADCIN5_RES_REG: + case DA9052_AUTO5_HIGH_REG: + case DA9052_AUTO5_LOW_REG: + case DA9052_ADCIN6_RES_REG: + case DA9052_AUTO6_HIGH_REG: + case DA9052_AUTO6_LOW_REG: + case DA9052_TJUNC_RES_REG: + case DA9052_TSI_CONT_A_REG: + case DA9052_TSI_CONT_B_REG: + case DA9052_TSI_X_MSB_REG: + case DA9052_TSI_Y_MSB_REG: + case DA9052_TSI_LSB_REG: + case DA9052_TSI_Z_MSB_REG: + case DA9052_COUNT_S_REG: + case DA9052_COUNT_MI_REG: + case DA9052_COUNT_H_REG: + case DA9052_COUNT_D_REG: + case DA9052_COUNT_MO_REG: + case DA9052_COUNT_Y_REG: + case DA9052_ALARM_MI_REG: + case DA9052_ALARM_H_REG: + case DA9052_ALARM_D_REG: + case DA9052_ALARM_MO_REG: + case DA9052_ALARM_Y_REG: + case DA9052_SECOND_A_REG: + case DA9052_SECOND_B_REG: + case DA9052_SECOND_C_REG: + case DA9052_SECOND_D_REG: + case DA9052_PAGE1_CON_REG: + return true; + default: + return false; + } +} + +static bool da9052_reg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9052_PAGE0_CON_REG: + case DA9052_EVENT_A_REG: + case DA9052_EVENT_B_REG: + case DA9052_EVENT_C_REG: + case DA9052_EVENT_D_REG: + case DA9052_IRQ_MASK_A_REG: + case DA9052_IRQ_MASK_B_REG: + case DA9052_IRQ_MASK_C_REG: + case DA9052_IRQ_MASK_D_REG: + case DA9052_CONTROL_A_REG: + case DA9052_CONTROL_B_REG: + case DA9052_CONTROL_C_REG: + case DA9052_CONTROL_D_REG: + case DA9052_PDDIS_REG: + case DA9052_RESET_REG: + case DA9052_GPIO_0_1_REG: + case DA9052_GPIO_2_3_REG: + case DA9052_GPIO_4_5_REG: + case DA9052_GPIO_6_7_REG: + case DA9052_GPIO_14_15_REG: + case DA9052_ID_0_1_REG: + case DA9052_ID_2_3_REG: + case DA9052_ID_4_5_REG: + case DA9052_ID_6_7_REG: + case DA9052_ID_8_9_REG: + case DA9052_ID_10_11_REG: + case DA9052_ID_12_13_REG: + case DA9052_ID_14_15_REG: + case DA9052_ID_16_17_REG: + case DA9052_ID_18_19_REG: + case DA9052_ID_20_21_REG: + case DA9052_SEQ_STATUS_REG: + case DA9052_SEQ_A_REG: + case DA9052_SEQ_B_REG: + case DA9052_SEQ_TIMER_REG: + case DA9052_BUCKA_REG: + case DA9052_BUCKB_REG: + case DA9052_BUCKCORE_REG: + case DA9052_BUCKPRO_REG: + case DA9052_BUCKMEM_REG: + case DA9052_BUCKPERI_REG: + case DA9052_LDO1_REG: + case DA9052_LDO2_REG: + case DA9052_LDO3_REG: + case DA9052_LDO4_REG: + case DA9052_LDO5_REG: + case DA9052_LDO6_REG: + case DA9052_LDO7_REG: + case DA9052_LDO8_REG: + case DA9052_LDO9_REG: + case DA9052_LDO10_REG: + case DA9052_SUPPLY_REG: + case DA9052_PULLDOWN_REG: + case DA9052_CHGBUCK_REG: + case DA9052_WAITCONT_REG: + case DA9052_ISET_REG: + case DA9052_BATCHG_REG: + case DA9052_CHG_CONT_REG: + case DA9052_INPUT_CONT_REG: + case DA9052_BBAT_CONT_REG: + case DA9052_BOOST_REG: + case DA9052_LED_CONT_REG: + case DA9052_LEDMIN123_REG: + case DA9052_LED1_CONF_REG: + case DA9052_LED2_CONF_REG: + case DA9052_LED3_CONF_REG: + case DA9052_LED1CONT_REG: + case DA9052_LED2CONT_REG: + case DA9052_LED3CONT_REG: + case DA9052_LED_CONT_4_REG: + case DA9052_LED_CONT_5_REG: + case DA9052_ADC_MAN_REG: + case DA9052_ADC_CONT_REG: + case DA9052_ADC_RES_L_REG: + case DA9052_ADC_RES_H_REG: + case DA9052_VDD_RES_REG: + case DA9052_VDD_MON_REG: + case DA9052_ICHG_THD_REG: + case DA9052_ICHG_END_REG: + case DA9052_TBAT_HIGHP_REG: + case DA9052_TBAT_HIGHN_REG: + case DA9052_TBAT_LOW_REG: + case DA9052_T_OFFSET_REG: + case DA9052_AUTO4_HIGH_REG: + case DA9052_AUTO4_LOW_REG: + case DA9052_AUTO5_HIGH_REG: + case DA9052_AUTO5_LOW_REG: + case DA9052_AUTO6_HIGH_REG: + case DA9052_AUTO6_LOW_REG: + case DA9052_TSI_CONT_A_REG: + case DA9052_TSI_CONT_B_REG: + case DA9052_COUNT_S_REG: + case DA9052_COUNT_MI_REG: + case DA9052_COUNT_H_REG: + case DA9052_COUNT_D_REG: + case DA9052_COUNT_MO_REG: + case DA9052_COUNT_Y_REG: + case DA9052_ALARM_MI_REG: + case DA9052_ALARM_H_REG: + case DA9052_ALARM_D_REG: + case DA9052_ALARM_MO_REG: + case DA9052_ALARM_Y_REG: + case DA9052_PAGE1_CON_REG: + return true; + default: + return false; + } +} + +static bool da9052_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9052_STATUS_A_REG: + case DA9052_STATUS_B_REG: + case DA9052_STATUS_C_REG: + case DA9052_STATUS_D_REG: + case DA9052_EVENT_A_REG: + case DA9052_EVENT_B_REG: + case DA9052_EVENT_C_REG: + case DA9052_EVENT_D_REG: + case DA9052_CONTROL_B_REG: + case DA9052_CONTROL_D_REG: + case DA9052_SUPPLY_REG: + case DA9052_FAULTLOG_REG: + case DA9052_CHG_TIME_REG: + case DA9052_ADC_RES_L_REG: + case DA9052_ADC_RES_H_REG: + case DA9052_VDD_RES_REG: + case DA9052_ICHG_AV_REG: + case DA9052_TBAT_RES_REG: + case DA9052_ADCIN4_RES_REG: + case DA9052_ADCIN5_RES_REG: + case DA9052_ADCIN6_RES_REG: + case DA9052_TJUNC_RES_REG: + case DA9052_TSI_X_MSB_REG: + case DA9052_TSI_Y_MSB_REG: + case DA9052_TSI_LSB_REG: + case DA9052_TSI_Z_MSB_REG: + case DA9052_COUNT_S_REG: + case DA9052_COUNT_MI_REG: + case DA9052_COUNT_H_REG: + case DA9052_COUNT_D_REG: + case DA9052_COUNT_MO_REG: + case DA9052_COUNT_Y_REG: + case DA9052_ALARM_MI_REG: + return true; + default: + return false; + } +} + +/* + * TBAT look-up table is computed from the R90 reg (8 bit register) + * reading as below. The battery temperature is in milliCentigrade + * TBAT = (1/(t1+1/298) - 273) * 1000 mC + * where t1 = (1/B)* ln(( ADCval * 2.5)/(R25*ITBAT*255)) + * Default values are R25 = 10e3, B = 3380, ITBAT = 50e-6 + * Example: + * R25=10E3, B=3380, ITBAT=50e-6, ADCVAL=62d calculates + * TBAT = 20015 mili degrees Centrigrade + * +*/ +static const int32_t tbat_lookup[255] = { + 183258, 144221, 124334, 111336, 101826, 94397, 88343, 83257, + 78889, 75071, 71688, 68656, 65914, 63414, 61120, 59001, + 570366, 55204, 53490, 51881, 50364, 48931, 47574, 46285, + 45059, 43889, 42772, 41703, 40678, 39694, 38748, 37838, + 36961, 36115, 35297, 34507, 33743, 33002, 32284, 31588, + 30911, 30254, 29615, 28994, 28389, 27799, 27225, 26664, + 26117, 25584, 25062, 24553, 24054, 23567, 23091, 22624, + 22167, 21719, 21281, 20851, 20429, 20015, 19610, 19211, + 18820, 18436, 18058, 17688, 17323, 16965, 16612, 16266, + 15925, 15589, 15259, 14933, 14613, 14298, 13987, 13681, + 13379, 13082, 12788, 12499, 12214, 11933, 11655, 11382, + 11112, 10845, 10582, 10322, 10066, 9812, 9562, 9315, + 9071, 8830, 8591, 8356, 8123, 7893, 7665, 7440, + 7218, 6998, 6780, 6565, 6352, 6141, 5933, 5726, + 5522, 5320, 5120, 4922, 4726, 4532, 4340, 4149, + 3961, 3774, 3589, 3406, 3225, 3045, 2867, 2690, + 2516, 2342, 2170, 2000, 1831, 1664, 1498, 1334, + 1171, 1009, 849, 690, 532, 376, 221, 67, + -84, -236, -386, -535, -683, -830, -975, -1119, + -1263, -1405, -1546, -1686, -1825, -1964, -2101, -2237, + -2372, -2506, -2639, -2771, -2902, -3033, -3162, -3291, + -3418, -3545, -3671, -3796, -3920, -4044, -4166, -4288, + -4409, -4529, -4649, -4767, -4885, -5002, -5119, -5235, + -5349, -5464, -5577, -5690, -5802, -5913, -6024, -6134, + -6244, -6352, -6461, -6568, -6675, -6781, -6887, -6992, + -7096, -7200, -7303, -7406, -7508, -7609, -7710, -7810, + -7910, -8009, -8108, -8206, -8304, -8401, -8497, -8593, + -8689, -8784, -8878, -8972, -9066, -9159, -9251, -9343, + -9435, -9526, -9617, -9707, -9796, -9886, -9975, -10063, + -10151, -10238, -10325, -10412, -10839, -10923, -11007, -11090, + -11173, -11256, -11338, -11420, -11501, -11583, -11663, -11744, + -11823, -11903, -11982 +}; + +static const u8 chan_mux[DA9052_ADC_VBBAT + 1] = { + [DA9052_ADC_VDDOUT] = DA9052_ADC_MAN_MUXSEL_VDDOUT, + [DA9052_ADC_ICH] = DA9052_ADC_MAN_MUXSEL_ICH, + [DA9052_ADC_TBAT] = DA9052_ADC_MAN_MUXSEL_TBAT, + [DA9052_ADC_VBAT] = DA9052_ADC_MAN_MUXSEL_VBAT, + [DA9052_ADC_IN4] = DA9052_ADC_MAN_MUXSEL_AD4, + [DA9052_ADC_IN5] = DA9052_ADC_MAN_MUXSEL_AD5, + [DA9052_ADC_IN6] = DA9052_ADC_MAN_MUXSEL_AD6, + [DA9052_ADC_VBBAT] = DA9052_ADC_MAN_MUXSEL_VBBAT +}; + +int da9052_adc_manual_read(struct da9052 *da9052, unsigned char channel) +{ + int ret; + unsigned short calc_data; + unsigned short data; + unsigned char mux_sel; + + if (channel > DA9052_ADC_VBBAT) + return -EINVAL; + + mutex_lock(&da9052->auxadc_lock); + + /* Channel gets activated on enabling the Conversion bit */ + mux_sel = chan_mux[channel] | DA9052_ADC_MAN_MAN_CONV; + + ret = da9052_reg_write(da9052, DA9052_ADC_MAN_REG, mux_sel); + if (ret < 0) + goto err; + + /* Wait for an interrupt */ + if (!wait_for_completion_timeout(&da9052->done, + msecs_to_jiffies(500))) { + dev_err(da9052->dev, + "timeout waiting for ADC conversion interrupt\n"); + ret = -ETIMEDOUT; + goto err; + } + + ret = da9052_reg_read(da9052, DA9052_ADC_RES_H_REG); + if (ret < 0) + goto err; + + calc_data = (unsigned short)ret; + data = calc_data << 2; + + ret = da9052_reg_read(da9052, DA9052_ADC_RES_L_REG); + if (ret < 0) + goto err; + + calc_data = (unsigned short)(ret & DA9052_ADC_RES_LSB); + data |= calc_data; + + ret = data; + +err: + mutex_unlock(&da9052->auxadc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(da9052_adc_manual_read); + +int da9052_adc_read_temp(struct da9052 *da9052) +{ + int tbat; + + tbat = da9052_reg_read(da9052, DA9052_TBAT_RES_REG); + if (tbat <= 0) + return tbat; + + /* ARRAY_SIZE check is not needed since TBAT is a 8-bit register */ + return tbat_lookup[tbat - 1]; +} +EXPORT_SYMBOL_GPL(da9052_adc_read_temp); + +static const struct mfd_cell da9052_subdev_info[] = { + { + .name = "da9052-regulator", + .id = 0, + }, + { + .name = "da9052-regulator", + .id = 1, + }, + { + .name = "da9052-regulator", + .id = 2, + }, + { + .name = "da9052-regulator", + .id = 3, + }, + { + .name = "da9052-regulator", + .id = 4, + }, + { + .name = "da9052-regulator", + .id = 5, + }, + { + .name = "da9052-regulator", + .id = 6, + }, + { + .name = "da9052-regulator", + .id = 7, + }, + { + .name = "da9052-regulator", + .id = 8, + }, + { + .name = "da9052-regulator", + .id = 9, + }, + { + .name = "da9052-regulator", + .id = 10, + }, + { + .name = "da9052-regulator", + .id = 11, + }, + { + .name = "da9052-regulator", + .id = 12, + }, + { + .name = "da9052-regulator", + .id = 13, + }, + { + .name = "da9052-onkey", + }, + { + .name = "da9052-rtc", + }, + { + .name = "da9052-gpio", + }, + { + .name = "da9052-hwmon", + }, + { + .name = "da9052-leds", + }, + { + .name = "da9052-wled1", + }, + { + .name = "da9052-wled2", + }, + { + .name = "da9052-wled3", + }, + { + .name = "da9052-tsi", + }, + { + .name = "da9052-bat", + }, + { + .name = "da9052-watchdog", + }, +}; + +const struct regmap_config da9052_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .cache_type = REGCACHE_RBTREE, + + .max_register = DA9052_PAGE1_CON_REG, + .readable_reg = da9052_reg_readable, + .writeable_reg = da9052_reg_writeable, + .volatile_reg = da9052_reg_volatile, +}; +EXPORT_SYMBOL_GPL(da9052_regmap_config); + +int da9052_device_init(struct da9052 *da9052, u8 chip_id) +{ + struct da9052_pdata *pdata = dev_get_platdata(da9052->dev); + int ret; + + mutex_init(&da9052->auxadc_lock); + init_completion(&da9052->done); + + if (pdata && pdata->init != NULL) + pdata->init(da9052); + + da9052->chip_id = chip_id; + + ret = da9052_irq_init(da9052); + if (ret != 0) { + dev_err(da9052->dev, "da9052_irq_init failed: %d\n", ret); + return ret; + } + + ret = mfd_add_devices(da9052->dev, PLATFORM_DEVID_AUTO, + da9052_subdev_info, + ARRAY_SIZE(da9052_subdev_info), NULL, 0, NULL); + if (ret) { + dev_err(da9052->dev, "mfd_add_devices failed: %d\n", ret); + goto err; + } + + return 0; + +err: + da9052_irq_exit(da9052); + + return ret; +} + +void da9052_device_exit(struct da9052 *da9052) +{ + mfd_remove_devices(da9052->dev); + da9052_irq_exit(da9052); +} + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("DA9052 MFD Core"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da9052-i2c.c b/kernel/drivers/mfd/da9052-i2c.c new file mode 100644 index 000000000..ec39287a2 --- /dev/null +++ b/kernel/drivers/mfd/da9052-i2c.c @@ -0,0 +1,227 @@ +/* + * I2C access for DA9052 PMICs. + * + * Copyright(c) 2011 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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/device.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/mfd/core.h> +#include <linux/i2c.h> +#include <linux/err.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +#ifdef CONFIG_OF +#include <linux/of.h> +#include <linux/of_device.h> +#endif + +/* I2C safe register check */ +static inline bool i2c_safe_reg(unsigned char reg) +{ + switch (reg) { + case DA9052_STATUS_A_REG: + case DA9052_STATUS_B_REG: + case DA9052_STATUS_C_REG: + case DA9052_STATUS_D_REG: + case DA9052_ADC_RES_L_REG: + case DA9052_ADC_RES_H_REG: + case DA9052_VDD_RES_REG: + case DA9052_ICHG_AV_REG: + case DA9052_TBAT_RES_REG: + case DA9052_ADCIN4_RES_REG: + case DA9052_ADCIN5_RES_REG: + case DA9052_ADCIN6_RES_REG: + case DA9052_TJUNC_RES_REG: + case DA9052_TSI_X_MSB_REG: + case DA9052_TSI_Y_MSB_REG: + case DA9052_TSI_LSB_REG: + case DA9052_TSI_Z_MSB_REG: + return true; + default: + return false; + } +} + +/* + * There is an issue with DA9052 and DA9053_AA/BA/BB PMIC where the PMIC + * gets lockup up or fails to respond following a system reset. + * This fix is to follow any read or write with a dummy read to a safe + * register. + */ +static int da9052_i2c_fix(struct da9052 *da9052, unsigned char reg) +{ + int val; + + switch (da9052->chip_id) { + case DA9052: + case DA9053_AA: + case DA9053_BA: + case DA9053_BB: + /* A dummy read to a safe register address. */ + if (!i2c_safe_reg(reg)) + return regmap_read(da9052->regmap, + DA9052_PARK_REGISTER, + &val); + break; + case DA9053_BC: + default: + /* + * For other chips parking of I2C register + * to a safe place is not required. + */ + break; + } + + return 0; +} + +/* + * According to errata item 24, multiwrite mode should be avoided + * in order to prevent register data corruption after power-down. + */ +static int da9052_i2c_disable_multiwrite(struct da9052 *da9052) +{ + int reg_val, ret; + + ret = regmap_read(da9052->regmap, DA9052_CONTROL_B_REG, ®_val); + if (ret < 0) + return ret; + + if (!(reg_val & DA9052_CONTROL_B_WRITEMODE)) { + reg_val |= DA9052_CONTROL_B_WRITEMODE; + ret = regmap_write(da9052->regmap, DA9052_CONTROL_B_REG, + reg_val); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct i2c_device_id da9052_i2c_id[] = { + {"da9052", DA9052}, + {"da9053-aa", DA9053_AA}, + {"da9053-ba", DA9053_BA}, + {"da9053-bb", DA9053_BB}, + {"da9053-bc", DA9053_BC}, + {} +}; + +#ifdef CONFIG_OF +static const struct of_device_id dialog_dt_ids[] = { + { .compatible = "dlg,da9052", .data = &da9052_i2c_id[0] }, + { .compatible = "dlg,da9053-aa", .data = &da9052_i2c_id[1] }, + { .compatible = "dlg,da9053-ba", .data = &da9052_i2c_id[2] }, + { .compatible = "dlg,da9053-bb", .data = &da9052_i2c_id[3] }, + { .compatible = "dlg,da9053-bc", .data = &da9052_i2c_id[4] }, + { /* sentinel */ } +}; +#endif + +static int da9052_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct da9052 *da9052; + int ret; + + da9052 = devm_kzalloc(&client->dev, sizeof(struct da9052), GFP_KERNEL); + if (!da9052) + return -ENOMEM; + + da9052->dev = &client->dev; + da9052->chip_irq = client->irq; + da9052->fix_io = da9052_i2c_fix; + + i2c_set_clientdata(client, da9052); + + da9052->regmap = devm_regmap_init_i2c(client, &da9052_regmap_config); + if (IS_ERR(da9052->regmap)) { + ret = PTR_ERR(da9052->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = da9052_i2c_disable_multiwrite(da9052); + if (ret < 0) + return ret; + +#ifdef CONFIG_OF + if (!id) { + struct device_node *np = client->dev.of_node; + const struct of_device_id *deviceid; + + deviceid = of_match_node(dialog_dt_ids, np); + id = deviceid->data; + } +#endif + + if (!id) { + ret = -ENODEV; + dev_err(&client->dev, "id is null.\n"); + return ret; + } + + ret = da9052_device_init(da9052, id->driver_data); + if (ret != 0) + return ret; + + return 0; +} + +static int da9052_i2c_remove(struct i2c_client *client) +{ + struct da9052 *da9052 = i2c_get_clientdata(client); + + da9052_device_exit(da9052); + return 0; +} + +static struct i2c_driver da9052_i2c_driver = { + .probe = da9052_i2c_probe, + .remove = da9052_i2c_remove, + .id_table = da9052_i2c_id, + .driver = { + .name = "da9052", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = dialog_dt_ids, +#endif + }, +}; + +static int __init da9052_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&da9052_i2c_driver); + if (ret != 0) { + pr_err("DA9052 I2C registration failed %d\n", ret); + return ret; + } + + return 0; +} +subsys_initcall(da9052_i2c_init); + +static void __exit da9052_i2c_exit(void) +{ + i2c_del_driver(&da9052_i2c_driver); +} +module_exit(da9052_i2c_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("I2C driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da9052-irq.c b/kernel/drivers/mfd/da9052-irq.c new file mode 100644 index 000000000..e65ca194f --- /dev/null +++ b/kernel/drivers/mfd/da9052-irq.c @@ -0,0 +1,290 @@ +/* + * DA9052 interrupt support + * + * Author: Fabio Estevam <fabio.estevam@freescale.com> + * Based on arizona-irq.c, which is: + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +#define DA9052_NUM_IRQ_REGS 4 +#define DA9052_IRQ_MASK_POS_1 0x01 +#define DA9052_IRQ_MASK_POS_2 0x02 +#define DA9052_IRQ_MASK_POS_3 0x04 +#define DA9052_IRQ_MASK_POS_4 0x08 +#define DA9052_IRQ_MASK_POS_5 0x10 +#define DA9052_IRQ_MASK_POS_6 0x20 +#define DA9052_IRQ_MASK_POS_7 0x40 +#define DA9052_IRQ_MASK_POS_8 0x80 + +static struct regmap_irq da9052_irqs[] = { + [DA9052_IRQ_DCIN] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_1, + }, + [DA9052_IRQ_VBUS] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_2, + }, + [DA9052_IRQ_DCINREM] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_3, + }, + [DA9052_IRQ_VBUSREM] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_4, + }, + [DA9052_IRQ_VDDLOW] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_5, + }, + [DA9052_IRQ_ALARM] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_6, + }, + [DA9052_IRQ_SEQRDY] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_7, + }, + [DA9052_IRQ_COMP1V2] = { + .reg_offset = 0, + .mask = DA9052_IRQ_MASK_POS_8, + }, + [DA9052_IRQ_NONKEY] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_1, + }, + [DA9052_IRQ_IDFLOAT] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_2, + }, + [DA9052_IRQ_IDGND] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_3, + }, + [DA9052_IRQ_CHGEND] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_4, + }, + [DA9052_IRQ_TBAT] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_5, + }, + [DA9052_IRQ_ADC_EOM] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_6, + }, + [DA9052_IRQ_PENDOWN] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_7, + }, + [DA9052_IRQ_TSIREADY] = { + .reg_offset = 1, + .mask = DA9052_IRQ_MASK_POS_8, + }, + [DA9052_IRQ_GPI0] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_1, + }, + [DA9052_IRQ_GPI1] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_2, + }, + [DA9052_IRQ_GPI2] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_3, + }, + [DA9052_IRQ_GPI3] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_4, + }, + [DA9052_IRQ_GPI4] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_5, + }, + [DA9052_IRQ_GPI5] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_6, + }, + [DA9052_IRQ_GPI6] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_7, + }, + [DA9052_IRQ_GPI7] = { + .reg_offset = 2, + .mask = DA9052_IRQ_MASK_POS_8, + }, + [DA9052_IRQ_GPI8] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_1, + }, + [DA9052_IRQ_GPI9] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_2, + }, + [DA9052_IRQ_GPI10] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_3, + }, + [DA9052_IRQ_GPI11] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_4, + }, + [DA9052_IRQ_GPI12] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_5, + }, + [DA9052_IRQ_GPI13] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_6, + }, + [DA9052_IRQ_GPI14] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_7, + }, + [DA9052_IRQ_GPI15] = { + .reg_offset = 3, + .mask = DA9052_IRQ_MASK_POS_8, + }, +}; + +static struct regmap_irq_chip da9052_regmap_irq_chip = { + .name = "da9052_irq", + .status_base = DA9052_EVENT_A_REG, + .mask_base = DA9052_IRQ_MASK_A_REG, + .ack_base = DA9052_EVENT_A_REG, + .num_regs = DA9052_NUM_IRQ_REGS, + .irqs = da9052_irqs, + .num_irqs = ARRAY_SIZE(da9052_irqs), +}; + +static int da9052_map_irq(struct da9052 *da9052, int irq) +{ + return regmap_irq_get_virq(da9052->irq_data, irq); +} + +int da9052_enable_irq(struct da9052 *da9052, int irq) +{ + irq = da9052_map_irq(da9052, irq); + if (irq < 0) + return irq; + + enable_irq(irq); + + return 0; +} +EXPORT_SYMBOL_GPL(da9052_enable_irq); + +int da9052_disable_irq(struct da9052 *da9052, int irq) +{ + irq = da9052_map_irq(da9052, irq); + if (irq < 0) + return irq; + + disable_irq(irq); + + return 0; +} +EXPORT_SYMBOL_GPL(da9052_disable_irq); + +int da9052_disable_irq_nosync(struct da9052 *da9052, int irq) +{ + irq = da9052_map_irq(da9052, irq); + if (irq < 0) + return irq; + + disable_irq_nosync(irq); + + return 0; +} +EXPORT_SYMBOL_GPL(da9052_disable_irq_nosync); + +int da9052_request_irq(struct da9052 *da9052, int irq, char *name, + irq_handler_t handler, void *data) +{ + irq = da9052_map_irq(da9052, irq); + if (irq < 0) + return irq; + + return request_threaded_irq(irq, NULL, handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + name, data); +} +EXPORT_SYMBOL_GPL(da9052_request_irq); + +void da9052_free_irq(struct da9052 *da9052, int irq, void *data) +{ + irq = da9052_map_irq(da9052, irq); + if (irq < 0) + return; + + free_irq(irq, data); +} +EXPORT_SYMBOL_GPL(da9052_free_irq); + +static irqreturn_t da9052_auxadc_irq(int irq, void *irq_data) +{ + struct da9052 *da9052 = irq_data; + + complete(&da9052->done); + + return IRQ_HANDLED; +} + +int da9052_irq_init(struct da9052 *da9052) +{ + int ret; + + ret = regmap_add_irq_chip(da9052->regmap, da9052->chip_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + -1, &da9052_regmap_irq_chip, + &da9052->irq_data); + if (ret < 0) { + dev_err(da9052->dev, "regmap_add_irq_chip failed: %d\n", ret); + goto regmap_err; + } + + enable_irq_wake(da9052->chip_irq); + + ret = da9052_request_irq(da9052, DA9052_IRQ_ADC_EOM, "adc-irq", + da9052_auxadc_irq, da9052); + + if (ret != 0) { + dev_err(da9052->dev, "DA9052_IRQ_ADC_EOM failed: %d\n", ret); + goto request_irq_err; + } + + return 0; + +request_irq_err: + regmap_del_irq_chip(da9052->chip_irq, da9052->irq_data); +regmap_err: + return ret; + +} + +int da9052_irq_exit(struct da9052 *da9052) +{ + da9052_free_irq(da9052, DA9052_IRQ_ADC_EOM , da9052); + regmap_del_irq_chip(da9052->chip_irq, da9052->irq_data); + + return 0; +} diff --git a/kernel/drivers/mfd/da9052-spi.c b/kernel/drivers/mfd/da9052-spi.c new file mode 100644 index 000000000..b5de8a685 --- /dev/null +++ b/kernel/drivers/mfd/da9052-spi.c @@ -0,0 +1,115 @@ +/* + * SPI access for Dialog DA9052 PMICs. + * + * Copyright(c) 2011 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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/device.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/mfd/core.h> +#include <linux/spi/spi.h> +#include <linux/err.h> + +#include <linux/mfd/da9052/da9052.h> + +static int da9052_spi_probe(struct spi_device *spi) +{ + struct regmap_config config; + int ret; + const struct spi_device_id *id = spi_get_device_id(spi); + struct da9052 *da9052; + + da9052 = devm_kzalloc(&spi->dev, sizeof(struct da9052), GFP_KERNEL); + if (!da9052) + return -ENOMEM; + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + spi_setup(spi); + + da9052->dev = &spi->dev; + da9052->chip_irq = spi->irq; + + spi_set_drvdata(spi, da9052); + + config = da9052_regmap_config; + config.read_flag_mask = 1; + config.reg_bits = 7; + config.pad_bits = 1; + config.val_bits = 8; + config.use_single_rw = 1; + + da9052->regmap = devm_regmap_init_spi(spi, &config); + if (IS_ERR(da9052->regmap)) { + ret = PTR_ERR(da9052->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = da9052_device_init(da9052, id->driver_data); + if (ret != 0) + return ret; + + return 0; +} + +static int da9052_spi_remove(struct spi_device *spi) +{ + struct da9052 *da9052 = spi_get_drvdata(spi); + + da9052_device_exit(da9052); + return 0; +} + +static struct spi_device_id da9052_spi_id[] = { + {"da9052", DA9052}, + {"da9053-aa", DA9053_AA}, + {"da9053-ba", DA9053_BA}, + {"da9053-bb", DA9053_BB}, + {"da9053-bc", DA9053_BC}, + {} +}; + +static struct spi_driver da9052_spi_driver = { + .probe = da9052_spi_probe, + .remove = da9052_spi_remove, + .id_table = da9052_spi_id, + .driver = { + .name = "da9052", + .owner = THIS_MODULE, + }, +}; + +static int __init da9052_spi_init(void) +{ + int ret; + + ret = spi_register_driver(&da9052_spi_driver); + if (ret != 0) { + pr_err("Failed to register DA9052 SPI driver, %d\n", ret); + return ret; + } + + return 0; +} +subsys_initcall(da9052_spi_init); + +static void __exit da9052_spi_exit(void) +{ + spi_unregister_driver(&da9052_spi_driver); +} +module_exit(da9052_spi_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("SPI driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da9055-core.c b/kernel/drivers/mfd/da9055-core.c new file mode 100644 index 000000000..b4d920c1e --- /dev/null +++ b/kernel/drivers/mfd/da9055-core.c @@ -0,0 +1,428 @@ +/* + * Device access for Dialog DA9055 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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/module.h> +#include <linux/device.h> +#include <linux/input.h> +#include <linux/irq.h> +#include <linux/mutex.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/da9055/core.h> +#include <linux/mfd/da9055/pdata.h> +#include <linux/mfd/da9055/reg.h> + +#define DA9055_IRQ_NONKEY_MASK 0x01 +#define DA9055_IRQ_ALM_MASK 0x02 +#define DA9055_IRQ_TICK_MASK 0x04 +#define DA9055_IRQ_ADC_MASK 0x08 +#define DA9055_IRQ_BUCK_ILIM_MASK 0x08 + +static bool da9055_register_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9055_REG_STATUS_A: + case DA9055_REG_STATUS_B: + case DA9055_REG_EVENT_A: + case DA9055_REG_EVENT_B: + case DA9055_REG_EVENT_C: + case DA9055_REG_IRQ_MASK_A: + case DA9055_REG_IRQ_MASK_B: + case DA9055_REG_IRQ_MASK_C: + + case DA9055_REG_CONTROL_A: + case DA9055_REG_CONTROL_B: + case DA9055_REG_CONTROL_C: + case DA9055_REG_CONTROL_D: + case DA9055_REG_CONTROL_E: + + case DA9055_REG_ADC_MAN: + case DA9055_REG_ADC_CONT: + case DA9055_REG_VSYS_MON: + case DA9055_REG_ADC_RES_L: + case DA9055_REG_ADC_RES_H: + case DA9055_REG_VSYS_RES: + case DA9055_REG_ADCIN1_RES: + case DA9055_REG_ADCIN2_RES: + case DA9055_REG_ADCIN3_RES: + + case DA9055_REG_COUNT_S: + case DA9055_REG_COUNT_MI: + case DA9055_REG_COUNT_H: + case DA9055_REG_COUNT_D: + case DA9055_REG_COUNT_MO: + case DA9055_REG_COUNT_Y: + case DA9055_REG_ALARM_H: + case DA9055_REG_ALARM_D: + case DA9055_REG_ALARM_MI: + case DA9055_REG_ALARM_MO: + case DA9055_REG_ALARM_Y: + + case DA9055_REG_GPIO0_1: + case DA9055_REG_GPIO2: + case DA9055_REG_GPIO_MODE0_2: + + case DA9055_REG_BCORE_CONT: + case DA9055_REG_BMEM_CONT: + case DA9055_REG_LDO1_CONT: + case DA9055_REG_LDO2_CONT: + case DA9055_REG_LDO3_CONT: + case DA9055_REG_LDO4_CONT: + case DA9055_REG_LDO5_CONT: + case DA9055_REG_LDO6_CONT: + case DA9055_REG_BUCK_LIM: + case DA9055_REG_BCORE_MODE: + case DA9055_REG_VBCORE_A: + case DA9055_REG_VBMEM_A: + case DA9055_REG_VLDO1_A: + case DA9055_REG_VLDO2_A: + case DA9055_REG_VLDO3_A: + case DA9055_REG_VLDO4_A: + case DA9055_REG_VLDO5_A: + case DA9055_REG_VLDO6_A: + case DA9055_REG_VBCORE_B: + case DA9055_REG_VBMEM_B: + case DA9055_REG_VLDO1_B: + case DA9055_REG_VLDO2_B: + case DA9055_REG_VLDO3_B: + case DA9055_REG_VLDO4_B: + case DA9055_REG_VLDO5_B: + case DA9055_REG_VLDO6_B: + return true; + default: + return false; + } +} + +static bool da9055_register_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9055_REG_STATUS_A: + case DA9055_REG_STATUS_B: + case DA9055_REG_EVENT_A: + case DA9055_REG_EVENT_B: + case DA9055_REG_EVENT_C: + case DA9055_REG_IRQ_MASK_A: + case DA9055_REG_IRQ_MASK_B: + case DA9055_REG_IRQ_MASK_C: + + case DA9055_REG_CONTROL_A: + case DA9055_REG_CONTROL_B: + case DA9055_REG_CONTROL_C: + case DA9055_REG_CONTROL_D: + case DA9055_REG_CONTROL_E: + + case DA9055_REG_ADC_MAN: + case DA9055_REG_ADC_CONT: + case DA9055_REG_VSYS_MON: + case DA9055_REG_ADC_RES_L: + case DA9055_REG_ADC_RES_H: + case DA9055_REG_VSYS_RES: + case DA9055_REG_ADCIN1_RES: + case DA9055_REG_ADCIN2_RES: + case DA9055_REG_ADCIN3_RES: + + case DA9055_REG_COUNT_S: + case DA9055_REG_COUNT_MI: + case DA9055_REG_COUNT_H: + case DA9055_REG_COUNT_D: + case DA9055_REG_COUNT_MO: + case DA9055_REG_COUNT_Y: + case DA9055_REG_ALARM_H: + case DA9055_REG_ALARM_D: + case DA9055_REG_ALARM_MI: + case DA9055_REG_ALARM_MO: + case DA9055_REG_ALARM_Y: + + case DA9055_REG_GPIO0_1: + case DA9055_REG_GPIO2: + case DA9055_REG_GPIO_MODE0_2: + + case DA9055_REG_BCORE_CONT: + case DA9055_REG_BMEM_CONT: + case DA9055_REG_LDO1_CONT: + case DA9055_REG_LDO2_CONT: + case DA9055_REG_LDO3_CONT: + case DA9055_REG_LDO4_CONT: + case DA9055_REG_LDO5_CONT: + case DA9055_REG_LDO6_CONT: + case DA9055_REG_BUCK_LIM: + case DA9055_REG_BCORE_MODE: + case DA9055_REG_VBCORE_A: + case DA9055_REG_VBMEM_A: + case DA9055_REG_VLDO1_A: + case DA9055_REG_VLDO2_A: + case DA9055_REG_VLDO3_A: + case DA9055_REG_VLDO4_A: + case DA9055_REG_VLDO5_A: + case DA9055_REG_VLDO6_A: + case DA9055_REG_VBCORE_B: + case DA9055_REG_VBMEM_B: + case DA9055_REG_VLDO1_B: + case DA9055_REG_VLDO2_B: + case DA9055_REG_VLDO3_B: + case DA9055_REG_VLDO4_B: + case DA9055_REG_VLDO5_B: + case DA9055_REG_VLDO6_B: + return true; + default: + return false; + } +} + +static bool da9055_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9055_REG_STATUS_A: + case DA9055_REG_STATUS_B: + case DA9055_REG_EVENT_A: + case DA9055_REG_EVENT_B: + case DA9055_REG_EVENT_C: + + case DA9055_REG_CONTROL_A: + case DA9055_REG_CONTROL_E: + + case DA9055_REG_ADC_MAN: + case DA9055_REG_ADC_RES_L: + case DA9055_REG_ADC_RES_H: + case DA9055_REG_VSYS_RES: + case DA9055_REG_ADCIN1_RES: + case DA9055_REG_ADCIN2_RES: + case DA9055_REG_ADCIN3_RES: + + case DA9055_REG_COUNT_S: + case DA9055_REG_COUNT_MI: + case DA9055_REG_COUNT_H: + case DA9055_REG_COUNT_D: + case DA9055_REG_COUNT_MO: + case DA9055_REG_COUNT_Y: + case DA9055_REG_ALARM_MI: + + case DA9055_REG_BCORE_CONT: + case DA9055_REG_BMEM_CONT: + case DA9055_REG_LDO1_CONT: + case DA9055_REG_LDO2_CONT: + case DA9055_REG_LDO3_CONT: + case DA9055_REG_LDO4_CONT: + case DA9055_REG_LDO5_CONT: + case DA9055_REG_LDO6_CONT: + return true; + default: + return false; + } +} + +static struct regmap_irq da9055_irqs[] = { + [DA9055_IRQ_NONKEY] = { + .reg_offset = 0, + .mask = DA9055_IRQ_NONKEY_MASK, + }, + [DA9055_IRQ_ALARM] = { + .reg_offset = 0, + .mask = DA9055_IRQ_ALM_MASK, + }, + [DA9055_IRQ_TICK] = { + .reg_offset = 0, + .mask = DA9055_IRQ_TICK_MASK, + }, + [DA9055_IRQ_HWMON] = { + .reg_offset = 0, + .mask = DA9055_IRQ_ADC_MASK, + }, + [DA9055_IRQ_REGULATOR] = { + .reg_offset = 1, + .mask = DA9055_IRQ_BUCK_ILIM_MASK, + }, +}; + +struct regmap_config da9055_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .cache_type = REGCACHE_RBTREE, + + .max_register = DA9055_MAX_REGISTER_CNT, + .readable_reg = da9055_register_readable, + .writeable_reg = da9055_register_writeable, + .volatile_reg = da9055_register_volatile, +}; +EXPORT_SYMBOL_GPL(da9055_regmap_config); + +static struct resource da9055_onkey_resource = { + .name = "ONKEY", + .start = DA9055_IRQ_NONKEY, + .end = DA9055_IRQ_NONKEY, + .flags = IORESOURCE_IRQ, +}; + +static struct resource da9055_rtc_resource[] = { + { + .name = "ALM", + .start = DA9055_IRQ_ALARM, + .end = DA9055_IRQ_ALARM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TICK", + .start = DA9055_IRQ_TICK, + .end = DA9055_IRQ_TICK, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource da9055_hwmon_resource = { + .name = "HWMON", + .start = DA9055_IRQ_HWMON, + .end = DA9055_IRQ_HWMON, + .flags = IORESOURCE_IRQ, +}; + +static struct resource da9055_ld05_6_resource = { + .name = "REGULATOR", + .start = DA9055_IRQ_REGULATOR, + .end = DA9055_IRQ_REGULATOR, + .flags = IORESOURCE_IRQ, +}; + +static const struct mfd_cell da9055_devs[] = { + { + .of_compatible = "dlg,da9055-gpio", + .name = "da9055-gpio", + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 1, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 2, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 3, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 4, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 5, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 6, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .id = 7, + .resources = &da9055_ld05_6_resource, + .num_resources = 1, + }, + { + .of_compatible = "dlg,da9055-regulator", + .name = "da9055-regulator", + .resources = &da9055_ld05_6_resource, + .num_resources = 1, + .id = 8, + }, + { + .of_compatible = "dlg,da9055-onkey", + .name = "da9055-onkey", + .resources = &da9055_onkey_resource, + .num_resources = 1, + }, + { + .of_compatible = "dlg,da9055-rtc", + .name = "da9055-rtc", + .resources = da9055_rtc_resource, + .num_resources = ARRAY_SIZE(da9055_rtc_resource), + }, + { + .of_compatible = "dlg,da9055-hwmon", + .name = "da9055-hwmon", + .resources = &da9055_hwmon_resource, + .num_resources = 1, + }, + { + .of_compatible = "dlg,da9055-watchdog", + .name = "da9055-watchdog", + }, +}; + +static struct regmap_irq_chip da9055_regmap_irq_chip = { + .name = "da9055_irq", + .status_base = DA9055_REG_EVENT_A, + .mask_base = DA9055_REG_IRQ_MASK_A, + .ack_base = DA9055_REG_EVENT_A, + .num_regs = 3, + .irqs = da9055_irqs, + .num_irqs = ARRAY_SIZE(da9055_irqs), +}; + +int da9055_device_init(struct da9055 *da9055) +{ + struct da9055_pdata *pdata = dev_get_platdata(da9055->dev); + int ret; + uint8_t clear_events[3] = {0xFF, 0xFF, 0xFF}; + + if (pdata && pdata->init != NULL) + pdata->init(da9055); + + if (!pdata || !pdata->irq_base) + da9055->irq_base = -1; + else + da9055->irq_base = pdata->irq_base; + + ret = da9055_group_write(da9055, DA9055_REG_EVENT_A, 3, clear_events); + if (ret < 0) + return ret; + + ret = regmap_add_irq_chip(da9055->regmap, da9055->chip_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + da9055->irq_base, &da9055_regmap_irq_chip, + &da9055->irq_data); + if (ret < 0) + return ret; + + da9055->irq_base = regmap_irq_chip_get_base(da9055->irq_data); + + ret = mfd_add_devices(da9055->dev, -1, + da9055_devs, ARRAY_SIZE(da9055_devs), + NULL, da9055->irq_base, NULL); + if (ret) + goto err; + + return 0; + +err: + mfd_remove_devices(da9055->dev); + return ret; +} + +void da9055_device_exit(struct da9055 *da9055) +{ + regmap_del_irq_chip(da9055->chip_irq, da9055->irq_data); + mfd_remove_devices(da9055->dev); +} + +MODULE_DESCRIPTION("Core support for the DA9055 PMIC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); diff --git a/kernel/drivers/mfd/da9055-i2c.c b/kernel/drivers/mfd/da9055-i2c.c new file mode 100644 index 000000000..d4d4c165e --- /dev/null +++ b/kernel/drivers/mfd/da9055-i2c.c @@ -0,0 +1,109 @@ + /* I2C access for DA9055 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.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/module.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/mfd/da9055/core.h> + +static int da9055_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da9055 *da9055; + int ret; + + da9055 = devm_kzalloc(&i2c->dev, sizeof(struct da9055), GFP_KERNEL); + if (!da9055) + return -ENOMEM; + + da9055->regmap = devm_regmap_init_i2c(i2c, &da9055_regmap_config); + if (IS_ERR(da9055->regmap)) { + ret = PTR_ERR(da9055->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + da9055->dev = &i2c->dev; + da9055->chip_irq = i2c->irq; + + i2c_set_clientdata(i2c, da9055); + + return da9055_device_init(da9055); +} + +static int da9055_i2c_remove(struct i2c_client *i2c) +{ + struct da9055 *da9055 = i2c_get_clientdata(i2c); + + da9055_device_exit(da9055); + + return 0; +} + +/* + * DO NOT change the device Ids. The naming is intentionally specific as both + * the PMIC and CODEC parts of this chip are instantiated separately as I2C + * devices (both have configurable I2C addresses, and are to all intents and + * purposes separate). As a result there are specific DA9055 ids for PMIC + * and CODEC, which must be different to operate together. + */ +static struct i2c_device_id da9055_i2c_id[] = { + {"da9055-pmic", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, da9055_i2c_id); + +static const struct of_device_id da9055_of_match[] = { + { .compatible = "dlg,da9055-pmic", }, + { } +}; + +static struct i2c_driver da9055_i2c_driver = { + .probe = da9055_i2c_probe, + .remove = da9055_i2c_remove, + .id_table = da9055_i2c_id, + .driver = { + .name = "da9055-pmic", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(da9055_of_match), + }, +}; + +static int __init da9055_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&da9055_i2c_driver); + if (ret != 0) { + pr_err("DA9055 I2C registration failed %d\n", ret); + return ret; + } + + return 0; +} +subsys_initcall(da9055_i2c_init); + +static void __exit da9055_i2c_exit(void) +{ + i2c_del_driver(&da9055_i2c_driver); +} +module_exit(da9055_i2c_exit); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("I2C driver for Dialog DA9055 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da9063-core.c b/kernel/drivers/mfd/da9063-core.c new file mode 100644 index 000000000..facd3610a --- /dev/null +++ b/kernel/drivers/mfd/da9063-core.c @@ -0,0 +1,192 @@ +/* + * da9063-core.c: Device access for Dialog DA9063 modules + * + * Copyright 2012 Dialog Semiconductors Ltd. + * Copyright 2013 Philipp Zabel, Pengutronix + * + * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.com>, + * Michal Hajduk <michal.hajduk@diasemi.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/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/regmap.h> + +#include <linux/mfd/da9063/core.h> +#include <linux/mfd/da9063/pdata.h> +#include <linux/mfd/da9063/registers.h> + +#include <linux/proc_fs.h> +#include <linux/kthread.h> +#include <linux/uaccess.h> + + +static struct resource da9063_regulators_resources[] = { + { + .name = "LDO_LIM", + .start = DA9063_IRQ_LDO_LIM, + .end = DA9063_IRQ_LDO_LIM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource da9063_rtc_resources[] = { + { + .name = "ALARM", + .start = DA9063_IRQ_ALARM, + .end = DA9063_IRQ_ALARM, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TICK", + .start = DA9063_IRQ_TICK, + .end = DA9063_IRQ_TICK, + .flags = IORESOURCE_IRQ, + } +}; + +static struct resource da9063_onkey_resources[] = { + { + .start = DA9063_IRQ_ONKEY, + .end = DA9063_IRQ_ONKEY, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource da9063_hwmon_resources[] = { + { + .start = DA9063_IRQ_ADC_RDY, + .end = DA9063_IRQ_ADC_RDY, + .flags = IORESOURCE_IRQ, + }, +}; + + +static const struct mfd_cell da9063_devs[] = { + { + .name = DA9063_DRVNAME_REGULATORS, + .num_resources = ARRAY_SIZE(da9063_regulators_resources), + .resources = da9063_regulators_resources, + }, + { + .name = DA9063_DRVNAME_LEDS, + }, + { + .name = DA9063_DRVNAME_WATCHDOG, + .of_compatible = "dlg,da9063-watchdog", + }, + { + .name = DA9063_DRVNAME_HWMON, + .num_resources = ARRAY_SIZE(da9063_hwmon_resources), + .resources = da9063_hwmon_resources, + }, + { + .name = DA9063_DRVNAME_ONKEY, + .num_resources = ARRAY_SIZE(da9063_onkey_resources), + .resources = da9063_onkey_resources, + }, + { + .name = DA9063_DRVNAME_RTC, + .num_resources = ARRAY_SIZE(da9063_rtc_resources), + .resources = da9063_rtc_resources, + .of_compatible = "dlg,da9063-rtc", + }, + { + .name = DA9063_DRVNAME_VIBRATION, + }, +}; + +int da9063_device_init(struct da9063 *da9063, unsigned int irq) +{ + struct da9063_pdata *pdata = da9063->dev->platform_data; + int model, variant_id, variant_code; + int ret; + + if (pdata) { + da9063->flags = pdata->flags; + da9063->irq_base = pdata->irq_base; + } else { + da9063->flags = 0; + da9063->irq_base = -1; + } + da9063->chip_irq = irq; + + if (pdata && pdata->init != NULL) { + ret = pdata->init(da9063); + if (ret != 0) { + dev_err(da9063->dev, + "Platform initialization failed.\n"); + return ret; + } + } + + ret = regmap_read(da9063->regmap, DA9063_REG_CHIP_ID, &model); + if (ret < 0) { + dev_err(da9063->dev, "Cannot read chip model id.\n"); + return -EIO; + } + if (model != PMIC_DA9063) { + dev_err(da9063->dev, "Invalid chip model id: 0x%02x\n", model); + return -ENODEV; + } + + ret = regmap_read(da9063->regmap, DA9063_REG_CHIP_VARIANT, &variant_id); + if (ret < 0) { + dev_err(da9063->dev, "Cannot read chip variant id.\n"); + return -EIO; + } + + variant_code = variant_id >> DA9063_CHIP_VARIANT_SHIFT; + + dev_info(da9063->dev, + "Device detected (chip-ID: 0x%02X, var-ID: 0x%02X)\n", + model, variant_id); + + if (variant_code < PMIC_DA9063_BB && variant_code != PMIC_DA9063_AD) { + dev_err(da9063->dev, + "Cannot support variant code: 0x%02X\n", variant_code); + return -ENODEV; + } + + da9063->model = model; + da9063->variant_code = variant_code; + + ret = da9063_irq_init(da9063); + if (ret) { + dev_err(da9063->dev, "Cannot initialize interrupts.\n"); + return ret; + } + + da9063->irq_base = regmap_irq_chip_get_base(da9063->regmap_irq); + + ret = mfd_add_devices(da9063->dev, -1, da9063_devs, + ARRAY_SIZE(da9063_devs), NULL, da9063->irq_base, + NULL); + if (ret) + dev_err(da9063->dev, "Cannot add MFD cells\n"); + + return ret; +} + +void da9063_device_exit(struct da9063 *da9063) +{ + mfd_remove_devices(da9063->dev); + da9063_irq_exit(da9063); +} + +MODULE_DESCRIPTION("PMIC driver for Dialog DA9063"); +MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>, Michal Hajduk <michal.hajduk@diasemi.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/da9063-i2c.c b/kernel/drivers/mfd/da9063-i2c.c new file mode 100644 index 000000000..6f3a7c000 --- /dev/null +++ b/kernel/drivers/mfd/da9063-i2c.c @@ -0,0 +1,275 @@ +/* da9063-i2c.c: Interrupt support for Dialog DA9063 + * + * Copyright 2012 Dialog Semiconductor Ltd. + * Copyright 2013 Philipp Zabel, Pengutronix + * + * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.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/i2c.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/da9063/core.h> +#include <linux/mfd/da9063/pdata.h> +#include <linux/mfd/da9063/registers.h> + +#include <linux/of.h> +#include <linux/regulator/of_regulator.h> + +static const struct regmap_range da9063_ad_readable_ranges[] = { + { + .range_min = DA9063_REG_PAGE_CON, + .range_max = DA9063_AD_REG_SECOND_D, + }, { + .range_min = DA9063_REG_SEQ, + .range_max = DA9063_REG_ID_32_31, + }, { + .range_min = DA9063_REG_SEQ_A, + .range_max = DA9063_REG_AUTO3_LOW, + }, { + .range_min = DA9063_REG_T_OFFSET, + .range_max = DA9063_AD_REG_GP_ID_19, + }, { + .range_min = DA9063_REG_CHIP_ID, + .range_max = DA9063_REG_CHIP_VARIANT, + }, +}; + +static const struct regmap_range da9063_ad_writeable_ranges[] = { + { + .range_min = DA9063_REG_PAGE_CON, + .range_max = DA9063_REG_PAGE_CON, + }, { + .range_min = DA9063_REG_FAULT_LOG, + .range_max = DA9063_REG_VSYS_MON, + }, { + .range_min = DA9063_REG_COUNT_S, + .range_max = DA9063_AD_REG_ALARM_Y, + }, { + .range_min = DA9063_REG_SEQ, + .range_max = DA9063_REG_ID_32_31, + }, { + .range_min = DA9063_REG_SEQ_A, + .range_max = DA9063_REG_AUTO3_LOW, + }, { + .range_min = DA9063_REG_CONFIG_I, + .range_max = DA9063_AD_REG_MON_REG_4, + }, { + .range_min = DA9063_AD_REG_GP_ID_0, + .range_max = DA9063_AD_REG_GP_ID_19, + }, +}; + +static const struct regmap_range da9063_ad_volatile_ranges[] = { + { + .range_min = DA9063_REG_STATUS_A, + .range_max = DA9063_REG_EVENT_D, + }, { + .range_min = DA9063_REG_CONTROL_F, + .range_max = DA9063_REG_CONTROL_F, + }, { + .range_min = DA9063_REG_ADC_MAN, + .range_max = DA9063_REG_ADC_MAN, + }, { + .range_min = DA9063_REG_ADC_RES_L, + .range_max = DA9063_AD_REG_SECOND_D, + }, { + .range_min = DA9063_AD_REG_MON_REG_5, + .range_max = DA9063_AD_REG_MON_REG_6, + }, +}; + +static const struct regmap_access_table da9063_ad_readable_table = { + .yes_ranges = da9063_ad_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(da9063_ad_readable_ranges), +}; + +static const struct regmap_access_table da9063_ad_writeable_table = { + .yes_ranges = da9063_ad_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(da9063_ad_writeable_ranges), +}; + +static const struct regmap_access_table da9063_ad_volatile_table = { + .yes_ranges = da9063_ad_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(da9063_ad_volatile_ranges), +}; + +static const struct regmap_range da9063_bb_readable_ranges[] = { + { + .range_min = DA9063_REG_PAGE_CON, + .range_max = DA9063_BB_REG_SECOND_D, + }, { + .range_min = DA9063_REG_SEQ, + .range_max = DA9063_REG_ID_32_31, + }, { + .range_min = DA9063_REG_SEQ_A, + .range_max = DA9063_REG_AUTO3_LOW, + }, { + .range_min = DA9063_REG_T_OFFSET, + .range_max = DA9063_BB_REG_GP_ID_19, + }, { + .range_min = DA9063_REG_CHIP_ID, + .range_max = DA9063_REG_CHIP_VARIANT, + }, +}; + +static const struct regmap_range da9063_bb_writeable_ranges[] = { + { + .range_min = DA9063_REG_PAGE_CON, + .range_max = DA9063_REG_PAGE_CON, + }, { + .range_min = DA9063_REG_FAULT_LOG, + .range_max = DA9063_REG_VSYS_MON, + }, { + .range_min = DA9063_REG_COUNT_S, + .range_max = DA9063_BB_REG_ALARM_Y, + }, { + .range_min = DA9063_REG_SEQ, + .range_max = DA9063_REG_ID_32_31, + }, { + .range_min = DA9063_REG_SEQ_A, + .range_max = DA9063_REG_AUTO3_LOW, + }, { + .range_min = DA9063_REG_CONFIG_I, + .range_max = DA9063_BB_REG_MON_REG_4, + }, { + .range_min = DA9063_BB_REG_GP_ID_0, + .range_max = DA9063_BB_REG_GP_ID_19, + }, +}; + +static const struct regmap_range da9063_bb_volatile_ranges[] = { + { + .range_min = DA9063_REG_STATUS_A, + .range_max = DA9063_REG_EVENT_D, + }, { + .range_min = DA9063_REG_CONTROL_F, + .range_max = DA9063_REG_CONTROL_F, + }, { + .range_min = DA9063_REG_ADC_MAN, + .range_max = DA9063_REG_ADC_MAN, + }, { + .range_min = DA9063_REG_ADC_RES_L, + .range_max = DA9063_BB_REG_SECOND_D, + }, { + .range_min = DA9063_BB_REG_MON_REG_5, + .range_max = DA9063_BB_REG_MON_REG_6, + }, +}; + +static const struct regmap_access_table da9063_bb_readable_table = { + .yes_ranges = da9063_bb_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(da9063_bb_readable_ranges), +}; + +static const struct regmap_access_table da9063_bb_writeable_table = { + .yes_ranges = da9063_bb_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(da9063_bb_writeable_ranges), +}; + +static const struct regmap_access_table da9063_bb_volatile_table = { + .yes_ranges = da9063_bb_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(da9063_bb_volatile_ranges), +}; + +static const struct regmap_range_cfg da9063_range_cfg[] = { + { + .range_min = DA9063_REG_PAGE_CON, + .range_max = DA9063_REG_CHIP_VARIANT, + .selector_reg = DA9063_REG_PAGE_CON, + .selector_mask = 1 << DA9063_I2C_PAGE_SEL_SHIFT, + .selector_shift = DA9063_I2C_PAGE_SEL_SHIFT, + .window_start = 0, + .window_len = 256, + } +}; + +static struct regmap_config da9063_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .ranges = da9063_range_cfg, + .num_ranges = ARRAY_SIZE(da9063_range_cfg), + .max_register = DA9063_REG_CHIP_VARIANT, + + .cache_type = REGCACHE_RBTREE, +}; + +static const struct of_device_id da9063_dt_ids[] = { + { .compatible = "dlg,da9063", }, + { } +}; +MODULE_DEVICE_TABLE(of, da9063_dt_ids); +static int da9063_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da9063 *da9063; + int ret; + + da9063 = devm_kzalloc(&i2c->dev, sizeof(struct da9063), GFP_KERNEL); + if (da9063 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, da9063); + da9063->dev = &i2c->dev; + da9063->chip_irq = i2c->irq; + + if (da9063->variant_code == PMIC_DA9063_AD) { + da9063_regmap_config.rd_table = &da9063_ad_readable_table; + da9063_regmap_config.wr_table = &da9063_ad_writeable_table; + da9063_regmap_config.volatile_table = &da9063_ad_volatile_table; + } else { + da9063_regmap_config.rd_table = &da9063_bb_readable_table; + da9063_regmap_config.wr_table = &da9063_bb_writeable_table; + da9063_regmap_config.volatile_table = &da9063_bb_volatile_table; + } + + da9063->regmap = devm_regmap_init_i2c(i2c, &da9063_regmap_config); + if (IS_ERR(da9063->regmap)) { + ret = PTR_ERR(da9063->regmap); + dev_err(da9063->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + return da9063_device_init(da9063, i2c->irq); +} + +static int da9063_i2c_remove(struct i2c_client *i2c) +{ + struct da9063 *da9063 = i2c_get_clientdata(i2c); + + da9063_device_exit(da9063); + + return 0; +} + +static const struct i2c_device_id da9063_i2c_id[] = { + {"da9063", PMIC_DA9063}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, da9063_i2c_id); + +static struct i2c_driver da9063_i2c_driver = { + .driver = { + .name = "da9063", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(da9063_dt_ids), + }, + .probe = da9063_i2c_probe, + .remove = da9063_i2c_remove, + .id_table = da9063_i2c_id, +}; + +module_i2c_driver(da9063_i2c_driver); diff --git a/kernel/drivers/mfd/da9063-irq.c b/kernel/drivers/mfd/da9063-irq.c new file mode 100644 index 000000000..822922602 --- /dev/null +++ b/kernel/drivers/mfd/da9063-irq.c @@ -0,0 +1,193 @@ +/* da9063-irq.c: Interrupts support for Dialog DA9063 + * + * Copyright 2012 Dialog Semiconductor Ltd. + * Copyright 2013 Philipp Zabel, Pengutronix + * + * Author: Michal Hajduk <michal.hajduk@diasemi.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/irq.h> +#include <linux/mfd/core.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/mfd/da9063/core.h> +#include <linux/mfd/da9063/pdata.h> + +#define DA9063_REG_EVENT_A_OFFSET 0 +#define DA9063_REG_EVENT_B_OFFSET 1 +#define DA9063_REG_EVENT_C_OFFSET 2 +#define DA9063_REG_EVENT_D_OFFSET 3 +#define EVENTS_BUF_LEN 4 + +static const u8 mask_events_buf[] = { [0 ... (EVENTS_BUF_LEN - 1)] = ~0 }; + +struct da9063_irq_data { + u16 reg; + u8 mask; +}; + +static struct regmap_irq da9063_irqs[] = { + /* DA9063 event A register */ + [DA9063_IRQ_ONKEY] = { + .reg_offset = DA9063_REG_EVENT_A_OFFSET, + .mask = DA9063_M_ONKEY, + }, + [DA9063_IRQ_ALARM] = { + .reg_offset = DA9063_REG_EVENT_A_OFFSET, + .mask = DA9063_M_ALARM, + }, + [DA9063_IRQ_TICK] = { + .reg_offset = DA9063_REG_EVENT_A_OFFSET, + .mask = DA9063_M_TICK, + }, + [DA9063_IRQ_ADC_RDY] = { + .reg_offset = DA9063_REG_EVENT_A_OFFSET, + .mask = DA9063_M_ADC_RDY, + }, + [DA9063_IRQ_SEQ_RDY] = { + .reg_offset = DA9063_REG_EVENT_A_OFFSET, + .mask = DA9063_M_SEQ_RDY, + }, + /* DA9063 event B register */ + [DA9063_IRQ_WAKE] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_WAKE, + }, + [DA9063_IRQ_TEMP] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_TEMP, + }, + [DA9063_IRQ_COMP_1V2] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_COMP_1V2, + }, + [DA9063_IRQ_LDO_LIM] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_LDO_LIM, + }, + [DA9063_IRQ_REG_UVOV] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_UVOV, + }, + [DA9063_IRQ_VDD_MON] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_VDD_MON, + }, + [DA9063_IRQ_WARN] = { + .reg_offset = DA9063_REG_EVENT_B_OFFSET, + .mask = DA9063_M_VDD_WARN, + }, + /* DA9063 event C register */ + [DA9063_IRQ_GPI0] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI0, + }, + [DA9063_IRQ_GPI1] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI1, + }, + [DA9063_IRQ_GPI2] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI2, + }, + [DA9063_IRQ_GPI3] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI3, + }, + [DA9063_IRQ_GPI4] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI4, + }, + [DA9063_IRQ_GPI5] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI5, + }, + [DA9063_IRQ_GPI6] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI6, + }, + [DA9063_IRQ_GPI7] = { + .reg_offset = DA9063_REG_EVENT_C_OFFSET, + .mask = DA9063_M_GPI7, + }, + /* DA9063 event D register */ + [DA9063_IRQ_GPI8] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI8, + }, + [DA9063_IRQ_GPI9] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI9, + }, + [DA9063_IRQ_GPI10] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI10, + }, + [DA9063_IRQ_GPI11] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI11, + }, + [DA9063_IRQ_GPI12] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI12, + }, + [DA9063_IRQ_GPI13] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI13, + }, + [DA9063_IRQ_GPI14] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI14, + }, + [DA9063_IRQ_GPI15] = { + .reg_offset = DA9063_REG_EVENT_D_OFFSET, + .mask = DA9063_M_GPI15, + }, +}; + +static struct regmap_irq_chip da9063_irq_chip = { + .name = "da9063-irq", + .irqs = da9063_irqs, + .num_irqs = DA9063_NUM_IRQ, + + .num_regs = 4, + .status_base = DA9063_REG_EVENT_A, + .mask_base = DA9063_REG_IRQ_MASK_A, + .ack_base = DA9063_REG_EVENT_A, + .init_ack_masked = true, +}; + +int da9063_irq_init(struct da9063 *da9063) +{ + int ret; + + if (!da9063->chip_irq) { + dev_err(da9063->dev, "No IRQ configured\n"); + return -EINVAL; + } + + ret = regmap_add_irq_chip(da9063->regmap, da9063->chip_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, + da9063->irq_base, &da9063_irq_chip, + &da9063->regmap_irq); + if (ret) { + dev_err(da9063->dev, "Failed to reguest IRQ %d: %d\n", + da9063->chip_irq, ret); + return ret; + } + + return 0; +} + +void da9063_irq_exit(struct da9063 *da9063) +{ + regmap_del_irq_chip(da9063->chip_irq, da9063->regmap_irq); +} diff --git a/kernel/drivers/mfd/da9150-core.c b/kernel/drivers/mfd/da9150-core.c new file mode 100644 index 000000000..5549817df --- /dev/null +++ b/kernel/drivers/mfd/da9150-core.c @@ -0,0 +1,413 @@ +/* + * DA9150 Core MFD Driver + * + * Copyright (c) 2014 Dialog Semiconductor + * + * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.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/platform_device.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/mfd/da9150/core.h> +#include <linux/mfd/da9150/registers.h> + +static bool da9150_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA9150_PAGE_CON: + case DA9150_STATUS_A: + case DA9150_STATUS_B: + case DA9150_STATUS_C: + case DA9150_STATUS_D: + case DA9150_STATUS_E: + case DA9150_STATUS_F: + case DA9150_STATUS_G: + case DA9150_STATUS_H: + case DA9150_STATUS_I: + case DA9150_STATUS_J: + case DA9150_STATUS_K: + case DA9150_STATUS_L: + case DA9150_STATUS_N: + case DA9150_FAULT_LOG_A: + case DA9150_FAULT_LOG_B: + case DA9150_EVENT_E: + case DA9150_EVENT_F: + case DA9150_EVENT_G: + case DA9150_EVENT_H: + case DA9150_CONTROL_B: + case DA9150_CONTROL_C: + case DA9150_GPADC_MAN: + case DA9150_GPADC_RES_A: + case DA9150_GPADC_RES_B: + case DA9150_ADETVB_CFG_C: + case DA9150_ADETD_STAT: + case DA9150_ADET_CMPSTAT: + case DA9150_ADET_CTRL_A: + case DA9150_PPR_TCTR_B: + case DA9150_COREBTLD_STAT_A: + case DA9150_CORE_DATA_A: + case DA9150_CORE_DATA_B: + case DA9150_CORE_DATA_C: + case DA9150_CORE_DATA_D: + case DA9150_CORE2WIRE_STAT_A: + case DA9150_FW_CTRL_C: + case DA9150_FG_CTRL_B: + case DA9150_FW_CTRL_B: + case DA9150_GPADC_CMAN: + case DA9150_GPADC_CRES_A: + case DA9150_GPADC_CRES_B: + case DA9150_CC_ICHG_RES_A: + case DA9150_CC_ICHG_RES_B: + case DA9150_CC_IAVG_RES_A: + case DA9150_CC_IAVG_RES_B: + case DA9150_TAUX_CTRL_A: + case DA9150_TAUX_VALUE_H: + case DA9150_TAUX_VALUE_L: + case DA9150_TBAT_RES_A: + case DA9150_TBAT_RES_B: + return true; + default: + return false; + } +} + +static const struct regmap_range_cfg da9150_range_cfg[] = { + { + .range_min = DA9150_PAGE_CON, + .range_max = DA9150_TBAT_RES_B, + .selector_reg = DA9150_PAGE_CON, + .selector_mask = DA9150_I2C_PAGE_MASK, + .selector_shift = DA9150_I2C_PAGE_SHIFT, + .window_start = 0, + .window_len = 256, + }, +}; + +static const struct regmap_config da9150_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .ranges = da9150_range_cfg, + .num_ranges = ARRAY_SIZE(da9150_range_cfg), + .max_register = DA9150_TBAT_RES_B, + + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = da9150_volatile_reg, +}; + +u8 da9150_reg_read(struct da9150 *da9150, u16 reg) +{ + int val, ret; + + ret = regmap_read(da9150->regmap, reg, &val); + if (ret) + dev_err(da9150->dev, "Failed to read from reg 0x%x: %d\n", + reg, ret); + + return (u8) val; +} +EXPORT_SYMBOL_GPL(da9150_reg_read); + +void da9150_reg_write(struct da9150 *da9150, u16 reg, u8 val) +{ + int ret; + + ret = regmap_write(da9150->regmap, reg, val); + if (ret) + dev_err(da9150->dev, "Failed to write to reg 0x%x: %d\n", + reg, ret); +} +EXPORT_SYMBOL_GPL(da9150_reg_write); + +void da9150_set_bits(struct da9150 *da9150, u16 reg, u8 mask, u8 val) +{ + int ret; + + ret = regmap_update_bits(da9150->regmap, reg, mask, val); + if (ret) + dev_err(da9150->dev, "Failed to set bits in reg 0x%x: %d\n", + reg, ret); +} +EXPORT_SYMBOL_GPL(da9150_set_bits); + +void da9150_bulk_read(struct da9150 *da9150, u16 reg, int count, u8 *buf) +{ + int ret; + + ret = regmap_bulk_read(da9150->regmap, reg, buf, count); + if (ret) + dev_err(da9150->dev, "Failed to bulk read from reg 0x%x: %d\n", + reg, ret); +} +EXPORT_SYMBOL_GPL(da9150_bulk_read); + +void da9150_bulk_write(struct da9150 *da9150, u16 reg, int count, const u8 *buf) +{ + int ret; + + ret = regmap_raw_write(da9150->regmap, reg, buf, count); + if (ret) + dev_err(da9150->dev, "Failed to bulk write to reg 0x%x %d\n", + reg, ret); +} +EXPORT_SYMBOL_GPL(da9150_bulk_write); + +static struct regmap_irq da9150_irqs[] = { + [DA9150_IRQ_VBUS] = { + .reg_offset = 0, + .mask = DA9150_E_VBUS_MASK, + }, + [DA9150_IRQ_CHG] = { + .reg_offset = 0, + .mask = DA9150_E_CHG_MASK, + }, + [DA9150_IRQ_TCLASS] = { + .reg_offset = 0, + .mask = DA9150_E_TCLASS_MASK, + }, + [DA9150_IRQ_TJUNC] = { + .reg_offset = 0, + .mask = DA9150_E_TJUNC_MASK, + }, + [DA9150_IRQ_VFAULT] = { + .reg_offset = 0, + .mask = DA9150_E_VFAULT_MASK, + }, + [DA9150_IRQ_CONF] = { + .reg_offset = 1, + .mask = DA9150_E_CONF_MASK, + }, + [DA9150_IRQ_DAT] = { + .reg_offset = 1, + .mask = DA9150_E_DAT_MASK, + }, + [DA9150_IRQ_DTYPE] = { + .reg_offset = 1, + .mask = DA9150_E_DTYPE_MASK, + }, + [DA9150_IRQ_ID] = { + .reg_offset = 1, + .mask = DA9150_E_ID_MASK, + }, + [DA9150_IRQ_ADP] = { + .reg_offset = 1, + .mask = DA9150_E_ADP_MASK, + }, + [DA9150_IRQ_SESS_END] = { + .reg_offset = 1, + .mask = DA9150_E_SESS_END_MASK, + }, + [DA9150_IRQ_SESS_VLD] = { + .reg_offset = 1, + .mask = DA9150_E_SESS_VLD_MASK, + }, + [DA9150_IRQ_FG] = { + .reg_offset = 2, + .mask = DA9150_E_FG_MASK, + }, + [DA9150_IRQ_GP] = { + .reg_offset = 2, + .mask = DA9150_E_GP_MASK, + }, + [DA9150_IRQ_TBAT] = { + .reg_offset = 2, + .mask = DA9150_E_TBAT_MASK, + }, + [DA9150_IRQ_GPIOA] = { + .reg_offset = 2, + .mask = DA9150_E_GPIOA_MASK, + }, + [DA9150_IRQ_GPIOB] = { + .reg_offset = 2, + .mask = DA9150_E_GPIOB_MASK, + }, + [DA9150_IRQ_GPIOC] = { + .reg_offset = 2, + .mask = DA9150_E_GPIOC_MASK, + }, + [DA9150_IRQ_GPIOD] = { + .reg_offset = 2, + .mask = DA9150_E_GPIOD_MASK, + }, + [DA9150_IRQ_GPADC] = { + .reg_offset = 2, + .mask = DA9150_E_GPADC_MASK, + }, + [DA9150_IRQ_WKUP] = { + .reg_offset = 3, + .mask = DA9150_E_WKUP_MASK, + }, +}; + +static struct regmap_irq_chip da9150_regmap_irq_chip = { + .name = "da9150_irq", + .status_base = DA9150_EVENT_E, + .mask_base = DA9150_IRQ_MASK_E, + .ack_base = DA9150_EVENT_E, + .num_regs = DA9150_NUM_IRQ_REGS, + .irqs = da9150_irqs, + .num_irqs = ARRAY_SIZE(da9150_irqs), +}; + +static struct resource da9150_gpadc_resources[] = { + { + .name = "GPADC", + .start = DA9150_IRQ_GPADC, + .end = DA9150_IRQ_GPADC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource da9150_charger_resources[] = { + { + .name = "CHG_STATUS", + .start = DA9150_IRQ_CHG, + .end = DA9150_IRQ_CHG, + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHG_TJUNC", + .start = DA9150_IRQ_TJUNC, + .end = DA9150_IRQ_TJUNC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHG_VFAULT", + .start = DA9150_IRQ_VFAULT, + .end = DA9150_IRQ_VFAULT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHG_VBUS", + .start = DA9150_IRQ_VBUS, + .end = DA9150_IRQ_VBUS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell da9150_devs[] = { + { + .name = "da9150-gpadc", + .of_compatible = "dlg,da9150-gpadc", + .resources = da9150_gpadc_resources, + .num_resources = ARRAY_SIZE(da9150_gpadc_resources), + }, + { + .name = "da9150-charger", + .of_compatible = "dlg,da9150-charger", + .resources = da9150_charger_resources, + .num_resources = ARRAY_SIZE(da9150_charger_resources), + }, +}; + +static int da9150_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct da9150 *da9150; + struct da9150_pdata *pdata = dev_get_platdata(&client->dev); + int ret; + + da9150 = devm_kzalloc(&client->dev, sizeof(*da9150), GFP_KERNEL); + if (!da9150) + return -ENOMEM; + + da9150->dev = &client->dev; + da9150->irq = client->irq; + i2c_set_clientdata(client, da9150); + + da9150->regmap = devm_regmap_init_i2c(client, &da9150_regmap_config); + if (IS_ERR(da9150->regmap)) { + ret = PTR_ERR(da9150->regmap); + dev_err(da9150->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + da9150->irq_base = pdata ? pdata->irq_base : -1; + + ret = regmap_add_irq_chip(da9150->regmap, da9150->irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + da9150->irq_base, &da9150_regmap_irq_chip, + &da9150->regmap_irq_data); + if (ret) + return ret; + + da9150->irq_base = regmap_irq_chip_get_base(da9150->regmap_irq_data); + enable_irq_wake(da9150->irq); + + ret = mfd_add_devices(da9150->dev, -1, da9150_devs, + ARRAY_SIZE(da9150_devs), NULL, + da9150->irq_base, NULL); + if (ret) { + dev_err(da9150->dev, "Failed to add child devices: %d\n", ret); + regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data); + return ret; + } + + return 0; +} + +static int da9150_remove(struct i2c_client *client) +{ + struct da9150 *da9150 = i2c_get_clientdata(client); + + regmap_del_irq_chip(da9150->irq, da9150->regmap_irq_data); + mfd_remove_devices(da9150->dev); + + return 0; +} + +static void da9150_shutdown(struct i2c_client *client) +{ + struct da9150 *da9150 = i2c_get_clientdata(client); + + /* Make sure we have a wakup source for the device */ + da9150_set_bits(da9150, DA9150_CONFIG_D, + DA9150_WKUP_PM_EN_MASK, + DA9150_WKUP_PM_EN_MASK); + + /* Set device to DISABLED mode */ + da9150_set_bits(da9150, DA9150_CONTROL_C, + DA9150_DISABLE_MASK, DA9150_DISABLE_MASK); +} + +static const struct i2c_device_id da9150_i2c_id[] = { + { "da9150", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da9150_i2c_id); + +static const struct of_device_id da9150_of_match[] = { + { .compatible = "dlg,da9150", }, + { } +}; +MODULE_DEVICE_TABLE(of, da9150_of_match); + +static struct i2c_driver da9150_driver = { + .driver = { + .name = "da9150", + .of_match_table = of_match_ptr(da9150_of_match), + }, + .probe = da9150_probe, + .remove = da9150_remove, + .shutdown = da9150_shutdown, + .id_table = da9150_i2c_id, +}; + +module_i2c_driver(da9150_driver); + +MODULE_DESCRIPTION("MFD Core Driver for DA9150"); +MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/davinci_voicecodec.c b/kernel/drivers/mfd/davinci_voicecodec.c new file mode 100644 index 000000000..9bbc642a7 --- /dev/null +++ b/kernel/drivers/mfd/davinci_voicecodec.c @@ -0,0 +1,154 @@ +/* + * DaVinci Voice Codec Core Interface for TI platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/regmap.h> + +#include <sound/pcm.h> + +#include <linux/mfd/davinci_voicecodec.h> + +static const struct regmap_config davinci_vc_regmap = { + .reg_bits = 32, + .val_bits = 32, +}; + +static int __init davinci_vc_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc; + struct resource *res; + struct mfd_cell *cell = NULL; + int ret; + + davinci_vc = devm_kzalloc(&pdev->dev, + sizeof(struct davinci_vc), GFP_KERNEL); + if (!davinci_vc) { + dev_dbg(&pdev->dev, + "could not allocate memory for private data\n"); + return -ENOMEM; + } + + davinci_vc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(davinci_vc->clk)) { + dev_dbg(&pdev->dev, + "could not get the clock for voice codec\n"); + return -ENODEV; + } + clk_enable(davinci_vc->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + davinci_vc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(davinci_vc->base)) { + ret = PTR_ERR(davinci_vc->base); + goto fail; + } + + davinci_vc->regmap = devm_regmap_init_mmio(&pdev->dev, + davinci_vc->base, + &davinci_vc_regmap); + if (IS_ERR(davinci_vc->regmap)) { + ret = PTR_ERR(davinci_vc->regmap); + goto fail; + } + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + ret = -ENXIO; + goto fail; + } + + davinci_vc->davinci_vcif.dma_tx_channel = res->start; + davinci_vc->davinci_vcif.dma_tx_addr = + (dma_addr_t)(io_v2p(davinci_vc->base) + DAVINCI_VC_WFIFO); + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + ret = -ENXIO; + goto fail; + } + + davinci_vc->davinci_vcif.dma_rx_channel = res->start; + davinci_vc->davinci_vcif.dma_rx_addr = + (dma_addr_t)(io_v2p(davinci_vc->base) + DAVINCI_VC_RFIFO); + + davinci_vc->dev = &pdev->dev; + davinci_vc->pdev = pdev; + + /* Voice codec interface client */ + cell = &davinci_vc->cells[DAVINCI_VC_VCIF_CELL]; + cell->name = "davinci-vcif"; + cell->platform_data = davinci_vc; + cell->pdata_size = sizeof(*davinci_vc); + + /* Voice codec CQ93VC client */ + cell = &davinci_vc->cells[DAVINCI_VC_CQ93VC_CELL]; + cell->name = "cq93vc-codec"; + cell->platform_data = davinci_vc; + cell->pdata_size = sizeof(*davinci_vc); + + ret = mfd_add_devices(&pdev->dev, pdev->id, davinci_vc->cells, + DAVINCI_VC_CELLS, NULL, 0, NULL); + if (ret != 0) { + dev_err(&pdev->dev, "fail to register client devices\n"); + goto fail; + } + + return 0; + +fail: + clk_disable(davinci_vc->clk); + + return ret; +} + +static int davinci_vc_remove(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + + clk_disable(davinci_vc->clk); + + return 0; +} + +static struct platform_driver davinci_vc_driver = { + .driver = { + .name = "davinci_voicecodec", + }, + .remove = davinci_vc_remove, +}; + +module_platform_driver_probe(davinci_vc_driver, davinci_vc_probe); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci Voice Codec Core Interface"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/db8500-prcmu.c b/kernel/drivers/mfd/db8500-prcmu.c new file mode 100644 index 000000000..cc1a40432 --- /dev/null +++ b/kernel/drivers/mfd/db8500-prcmu.c @@ -0,0 +1,3240 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> + * Author: Sundar Iyer <sundar.iyer@stericsson.com> + * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> + * + * U8500 PRCM Unit interface driver + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/irq.h> +#include <linux/jiffies.h> +#include <linux/bitops.h> +#include <linux/fs.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/mfd/core.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/regulator/db8500-prcmu.h> +#include <linux/regulator/machine.h> +#include <linux/cpufreq.h> +#include <linux/platform_data/ux500_wdt.h> +#include <linux/platform_data/db8500_thermal.h> +#include "dbx500-prcmu-regs.h" + +/* Index of different voltages to be used when accessing AVSData */ +#define PRCM_AVS_BASE 0x2FC +#define PRCM_AVS_VBB_RET (PRCM_AVS_BASE + 0x0) +#define PRCM_AVS_VBB_MAX_OPP (PRCM_AVS_BASE + 0x1) +#define PRCM_AVS_VBB_100_OPP (PRCM_AVS_BASE + 0x2) +#define PRCM_AVS_VBB_50_OPP (PRCM_AVS_BASE + 0x3) +#define PRCM_AVS_VARM_MAX_OPP (PRCM_AVS_BASE + 0x4) +#define PRCM_AVS_VARM_100_OPP (PRCM_AVS_BASE + 0x5) +#define PRCM_AVS_VARM_50_OPP (PRCM_AVS_BASE + 0x6) +#define PRCM_AVS_VARM_RET (PRCM_AVS_BASE + 0x7) +#define PRCM_AVS_VAPE_100_OPP (PRCM_AVS_BASE + 0x8) +#define PRCM_AVS_VAPE_50_OPP (PRCM_AVS_BASE + 0x9) +#define PRCM_AVS_VMOD_100_OPP (PRCM_AVS_BASE + 0xA) +#define PRCM_AVS_VMOD_50_OPP (PRCM_AVS_BASE + 0xB) +#define PRCM_AVS_VSAFE (PRCM_AVS_BASE + 0xC) + +#define PRCM_AVS_VOLTAGE 0 +#define PRCM_AVS_VOLTAGE_MASK 0x3f +#define PRCM_AVS_ISSLOWSTARTUP 6 +#define PRCM_AVS_ISSLOWSTARTUP_MASK (1 << PRCM_AVS_ISSLOWSTARTUP) +#define PRCM_AVS_ISMODEENABLE 7 +#define PRCM_AVS_ISMODEENABLE_MASK (1 << PRCM_AVS_ISMODEENABLE) + +#define PRCM_BOOT_STATUS 0xFFF +#define PRCM_ROMCODE_A2P 0xFFE +#define PRCM_ROMCODE_P2A 0xFFD +#define PRCM_XP70_CUR_PWR_STATE 0xFFC /* 4 BYTES */ + +#define PRCM_SW_RST_REASON 0xFF8 /* 2 bytes */ + +#define _PRCM_MBOX_HEADER 0xFE8 /* 16 bytes */ +#define PRCM_MBOX_HEADER_REQ_MB0 (_PRCM_MBOX_HEADER + 0x0) +#define PRCM_MBOX_HEADER_REQ_MB1 (_PRCM_MBOX_HEADER + 0x1) +#define PRCM_MBOX_HEADER_REQ_MB2 (_PRCM_MBOX_HEADER + 0x2) +#define PRCM_MBOX_HEADER_REQ_MB3 (_PRCM_MBOX_HEADER + 0x3) +#define PRCM_MBOX_HEADER_REQ_MB4 (_PRCM_MBOX_HEADER + 0x4) +#define PRCM_MBOX_HEADER_REQ_MB5 (_PRCM_MBOX_HEADER + 0x5) +#define PRCM_MBOX_HEADER_ACK_MB0 (_PRCM_MBOX_HEADER + 0x8) + +/* Req Mailboxes */ +#define PRCM_REQ_MB0 0xFDC /* 12 bytes */ +#define PRCM_REQ_MB1 0xFD0 /* 12 bytes */ +#define PRCM_REQ_MB2 0xFC0 /* 16 bytes */ +#define PRCM_REQ_MB3 0xE4C /* 372 bytes */ +#define PRCM_REQ_MB4 0xE48 /* 4 bytes */ +#define PRCM_REQ_MB5 0xE44 /* 4 bytes */ + +/* Ack Mailboxes */ +#define PRCM_ACK_MB0 0xE08 /* 52 bytes */ +#define PRCM_ACK_MB1 0xE04 /* 4 bytes */ +#define PRCM_ACK_MB2 0xE00 /* 4 bytes */ +#define PRCM_ACK_MB3 0xDFC /* 4 bytes */ +#define PRCM_ACK_MB4 0xDF8 /* 4 bytes */ +#define PRCM_ACK_MB5 0xDF4 /* 4 bytes */ + +/* Mailbox 0 headers */ +#define MB0H_POWER_STATE_TRANS 0 +#define MB0H_CONFIG_WAKEUPS_EXE 1 +#define MB0H_READ_WAKEUP_ACK 3 +#define MB0H_CONFIG_WAKEUPS_SLEEP 4 + +#define MB0H_WAKEUP_EXE 2 +#define MB0H_WAKEUP_SLEEP 5 + +/* Mailbox 0 REQs */ +#define PRCM_REQ_MB0_AP_POWER_STATE (PRCM_REQ_MB0 + 0x0) +#define PRCM_REQ_MB0_AP_PLL_STATE (PRCM_REQ_MB0 + 0x1) +#define PRCM_REQ_MB0_ULP_CLOCK_STATE (PRCM_REQ_MB0 + 0x2) +#define PRCM_REQ_MB0_DO_NOT_WFI (PRCM_REQ_MB0 + 0x3) +#define PRCM_REQ_MB0_WAKEUP_8500 (PRCM_REQ_MB0 + 0x4) +#define PRCM_REQ_MB0_WAKEUP_4500 (PRCM_REQ_MB0 + 0x8) + +/* Mailbox 0 ACKs */ +#define PRCM_ACK_MB0_AP_PWRSTTR_STATUS (PRCM_ACK_MB0 + 0x0) +#define PRCM_ACK_MB0_READ_POINTER (PRCM_ACK_MB0 + 0x1) +#define PRCM_ACK_MB0_WAKEUP_0_8500 (PRCM_ACK_MB0 + 0x4) +#define PRCM_ACK_MB0_WAKEUP_0_4500 (PRCM_ACK_MB0 + 0x8) +#define PRCM_ACK_MB0_WAKEUP_1_8500 (PRCM_ACK_MB0 + 0x1C) +#define PRCM_ACK_MB0_WAKEUP_1_4500 (PRCM_ACK_MB0 + 0x20) +#define PRCM_ACK_MB0_EVENT_4500_NUMBERS 20 + +/* Mailbox 1 headers */ +#define MB1H_ARM_APE_OPP 0x0 +#define MB1H_RESET_MODEM 0x2 +#define MB1H_REQUEST_APE_OPP_100_VOLT 0x3 +#define MB1H_RELEASE_APE_OPP_100_VOLT 0x4 +#define MB1H_RELEASE_USB_WAKEUP 0x5 +#define MB1H_PLL_ON_OFF 0x6 + +/* Mailbox 1 Requests */ +#define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) +#define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) +#define PRCM_REQ_MB1_PLL_ON_OFF (PRCM_REQ_MB1 + 0x4) +#define PLL_SOC0_OFF 0x1 +#define PLL_SOC0_ON 0x2 +#define PLL_SOC1_OFF 0x4 +#define PLL_SOC1_ON 0x8 + +/* Mailbox 1 ACKs */ +#define PRCM_ACK_MB1_CURRENT_ARM_OPP (PRCM_ACK_MB1 + 0x0) +#define PRCM_ACK_MB1_CURRENT_APE_OPP (PRCM_ACK_MB1 + 0x1) +#define PRCM_ACK_MB1_APE_VOLTAGE_STATUS (PRCM_ACK_MB1 + 0x2) +#define PRCM_ACK_MB1_DVFS_STATUS (PRCM_ACK_MB1 + 0x3) + +/* Mailbox 2 headers */ +#define MB2H_DPS 0x0 +#define MB2H_AUTO_PWR 0x1 + +/* Mailbox 2 REQs */ +#define PRCM_REQ_MB2_SVA_MMDSP (PRCM_REQ_MB2 + 0x0) +#define PRCM_REQ_MB2_SVA_PIPE (PRCM_REQ_MB2 + 0x1) +#define PRCM_REQ_MB2_SIA_MMDSP (PRCM_REQ_MB2 + 0x2) +#define PRCM_REQ_MB2_SIA_PIPE (PRCM_REQ_MB2 + 0x3) +#define PRCM_REQ_MB2_SGA (PRCM_REQ_MB2 + 0x4) +#define PRCM_REQ_MB2_B2R2_MCDE (PRCM_REQ_MB2 + 0x5) +#define PRCM_REQ_MB2_ESRAM12 (PRCM_REQ_MB2 + 0x6) +#define PRCM_REQ_MB2_ESRAM34 (PRCM_REQ_MB2 + 0x7) +#define PRCM_REQ_MB2_AUTO_PM_SLEEP (PRCM_REQ_MB2 + 0x8) +#define PRCM_REQ_MB2_AUTO_PM_IDLE (PRCM_REQ_MB2 + 0xC) + +/* Mailbox 2 ACKs */ +#define PRCM_ACK_MB2_DPS_STATUS (PRCM_ACK_MB2 + 0x0) +#define HWACC_PWR_ST_OK 0xFE + +/* Mailbox 3 headers */ +#define MB3H_ANC 0x0 +#define MB3H_SIDETONE 0x1 +#define MB3H_SYSCLK 0xE + +/* Mailbox 3 Requests */ +#define PRCM_REQ_MB3_ANC_FIR_COEFF (PRCM_REQ_MB3 + 0x0) +#define PRCM_REQ_MB3_ANC_IIR_COEFF (PRCM_REQ_MB3 + 0x20) +#define PRCM_REQ_MB3_ANC_SHIFTER (PRCM_REQ_MB3 + 0x60) +#define PRCM_REQ_MB3_ANC_WARP (PRCM_REQ_MB3 + 0x64) +#define PRCM_REQ_MB3_SIDETONE_FIR_GAIN (PRCM_REQ_MB3 + 0x68) +#define PRCM_REQ_MB3_SIDETONE_FIR_COEFF (PRCM_REQ_MB3 + 0x6C) +#define PRCM_REQ_MB3_SYSCLK_MGT (PRCM_REQ_MB3 + 0x16C) + +/* Mailbox 4 headers */ +#define MB4H_DDR_INIT 0x0 +#define MB4H_MEM_ST 0x1 +#define MB4H_HOTDOG 0x12 +#define MB4H_HOTMON 0x13 +#define MB4H_HOT_PERIOD 0x14 +#define MB4H_A9WDOG_CONF 0x16 +#define MB4H_A9WDOG_EN 0x17 +#define MB4H_A9WDOG_DIS 0x18 +#define MB4H_A9WDOG_LOAD 0x19 +#define MB4H_A9WDOG_KICK 0x20 + +/* Mailbox 4 Requests */ +#define PRCM_REQ_MB4_DDR_ST_AP_SLEEP_IDLE (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_DDR_ST_AP_DEEP_IDLE (PRCM_REQ_MB4 + 0x1) +#define PRCM_REQ_MB4_ESRAM0_ST (PRCM_REQ_MB4 + 0x3) +#define PRCM_REQ_MB4_HOTDOG_THRESHOLD (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_HOTMON_LOW (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_HOTMON_HIGH (PRCM_REQ_MB4 + 0x1) +#define PRCM_REQ_MB4_HOTMON_CONFIG (PRCM_REQ_MB4 + 0x2) +#define PRCM_REQ_MB4_HOT_PERIOD (PRCM_REQ_MB4 + 0x0) +#define HOTMON_CONFIG_LOW BIT(0) +#define HOTMON_CONFIG_HIGH BIT(1) +#define PRCM_REQ_MB4_A9WDOG_0 (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_A9WDOG_1 (PRCM_REQ_MB4 + 0x1) +#define PRCM_REQ_MB4_A9WDOG_2 (PRCM_REQ_MB4 + 0x2) +#define PRCM_REQ_MB4_A9WDOG_3 (PRCM_REQ_MB4 + 0x3) +#define A9WDOG_AUTO_OFF_EN BIT(7) +#define A9WDOG_AUTO_OFF_DIS 0 +#define A9WDOG_ID_MASK 0xf + +/* Mailbox 5 Requests */ +#define PRCM_REQ_MB5_I2C_SLAVE_OP (PRCM_REQ_MB5 + 0x0) +#define PRCM_REQ_MB5_I2C_HW_BITS (PRCM_REQ_MB5 + 0x1) +#define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 0x2) +#define PRCM_REQ_MB5_I2C_VAL (PRCM_REQ_MB5 + 0x3) +#define PRCMU_I2C_WRITE(slave) (((slave) << 1) | BIT(6)) +#define PRCMU_I2C_READ(slave) (((slave) << 1) | BIT(0) | BIT(6)) +#define PRCMU_I2C_STOP_EN BIT(3) + +/* Mailbox 5 ACKs */ +#define PRCM_ACK_MB5_I2C_STATUS (PRCM_ACK_MB5 + 0x1) +#define PRCM_ACK_MB5_I2C_VAL (PRCM_ACK_MB5 + 0x3) +#define I2C_WR_OK 0x1 +#define I2C_RD_OK 0x2 + +#define NUM_MB 8 +#define MBOX_BIT BIT +#define ALL_MBOX_BITS (MBOX_BIT(NUM_MB) - 1) + +/* + * Wakeups/IRQs + */ + +#define WAKEUP_BIT_RTC BIT(0) +#define WAKEUP_BIT_RTT0 BIT(1) +#define WAKEUP_BIT_RTT1 BIT(2) +#define WAKEUP_BIT_HSI0 BIT(3) +#define WAKEUP_BIT_HSI1 BIT(4) +#define WAKEUP_BIT_CA_WAKE BIT(5) +#define WAKEUP_BIT_USB BIT(6) +#define WAKEUP_BIT_ABB BIT(7) +#define WAKEUP_BIT_ABB_FIFO BIT(8) +#define WAKEUP_BIT_SYSCLK_OK BIT(9) +#define WAKEUP_BIT_CA_SLEEP BIT(10) +#define WAKEUP_BIT_AC_WAKE_ACK BIT(11) +#define WAKEUP_BIT_SIDE_TONE_OK BIT(12) +#define WAKEUP_BIT_ANC_OK BIT(13) +#define WAKEUP_BIT_SW_ERROR BIT(14) +#define WAKEUP_BIT_AC_SLEEP_ACK BIT(15) +#define WAKEUP_BIT_ARM BIT(17) +#define WAKEUP_BIT_HOTMON_LOW BIT(18) +#define WAKEUP_BIT_HOTMON_HIGH BIT(19) +#define WAKEUP_BIT_MODEM_SW_RESET_REQ BIT(20) +#define WAKEUP_BIT_GPIO0 BIT(23) +#define WAKEUP_BIT_GPIO1 BIT(24) +#define WAKEUP_BIT_GPIO2 BIT(25) +#define WAKEUP_BIT_GPIO3 BIT(26) +#define WAKEUP_BIT_GPIO4 BIT(27) +#define WAKEUP_BIT_GPIO5 BIT(28) +#define WAKEUP_BIT_GPIO6 BIT(29) +#define WAKEUP_BIT_GPIO7 BIT(30) +#define WAKEUP_BIT_GPIO8 BIT(31) + +static struct { + bool valid; + struct prcmu_fw_version version; +} fw_info; + +static struct irq_domain *db8500_irq_domain; + +/* + * This vector maps irq numbers to the bits in the bit field used in + * communication with the PRCMU firmware. + * + * The reason for having this is to keep the irq numbers contiguous even though + * the bits in the bit field are not. (The bits also have a tendency to move + * around, to further complicate matters.) + */ +#define IRQ_INDEX(_name) ((IRQ_PRCMU_##_name)) +#define IRQ_ENTRY(_name)[IRQ_INDEX(_name)] = (WAKEUP_BIT_##_name) + +#define IRQ_PRCMU_RTC 0 +#define IRQ_PRCMU_RTT0 1 +#define IRQ_PRCMU_RTT1 2 +#define IRQ_PRCMU_HSI0 3 +#define IRQ_PRCMU_HSI1 4 +#define IRQ_PRCMU_CA_WAKE 5 +#define IRQ_PRCMU_USB 6 +#define IRQ_PRCMU_ABB 7 +#define IRQ_PRCMU_ABB_FIFO 8 +#define IRQ_PRCMU_ARM 9 +#define IRQ_PRCMU_MODEM_SW_RESET_REQ 10 +#define IRQ_PRCMU_GPIO0 11 +#define IRQ_PRCMU_GPIO1 12 +#define IRQ_PRCMU_GPIO2 13 +#define IRQ_PRCMU_GPIO3 14 +#define IRQ_PRCMU_GPIO4 15 +#define IRQ_PRCMU_GPIO5 16 +#define IRQ_PRCMU_GPIO6 17 +#define IRQ_PRCMU_GPIO7 18 +#define IRQ_PRCMU_GPIO8 19 +#define IRQ_PRCMU_CA_SLEEP 20 +#define IRQ_PRCMU_HOTMON_LOW 21 +#define IRQ_PRCMU_HOTMON_HIGH 22 +#define NUM_PRCMU_WAKEUPS 23 + +static u32 prcmu_irq_bit[NUM_PRCMU_WAKEUPS] = { + IRQ_ENTRY(RTC), + IRQ_ENTRY(RTT0), + IRQ_ENTRY(RTT1), + IRQ_ENTRY(HSI0), + IRQ_ENTRY(HSI1), + IRQ_ENTRY(CA_WAKE), + IRQ_ENTRY(USB), + IRQ_ENTRY(ABB), + IRQ_ENTRY(ABB_FIFO), + IRQ_ENTRY(CA_SLEEP), + IRQ_ENTRY(ARM), + IRQ_ENTRY(HOTMON_LOW), + IRQ_ENTRY(HOTMON_HIGH), + IRQ_ENTRY(MODEM_SW_RESET_REQ), + IRQ_ENTRY(GPIO0), + IRQ_ENTRY(GPIO1), + IRQ_ENTRY(GPIO2), + IRQ_ENTRY(GPIO3), + IRQ_ENTRY(GPIO4), + IRQ_ENTRY(GPIO5), + IRQ_ENTRY(GPIO6), + IRQ_ENTRY(GPIO7), + IRQ_ENTRY(GPIO8) +}; + +#define VALID_WAKEUPS (BIT(NUM_PRCMU_WAKEUP_INDICES) - 1) +#define WAKEUP_ENTRY(_name)[PRCMU_WAKEUP_INDEX_##_name] = (WAKEUP_BIT_##_name) +static u32 prcmu_wakeup_bit[NUM_PRCMU_WAKEUP_INDICES] = { + WAKEUP_ENTRY(RTC), + WAKEUP_ENTRY(RTT0), + WAKEUP_ENTRY(RTT1), + WAKEUP_ENTRY(HSI0), + WAKEUP_ENTRY(HSI1), + WAKEUP_ENTRY(USB), + WAKEUP_ENTRY(ABB), + WAKEUP_ENTRY(ABB_FIFO), + WAKEUP_ENTRY(ARM) +}; + +/* + * mb0_transfer - state needed for mailbox 0 communication. + * @lock: The transaction lock. + * @dbb_events_lock: A lock used to handle concurrent access to (parts of) + * the request data. + * @mask_work: Work structure used for (un)masking wakeup interrupts. + * @req: Request data that need to persist between requests. + */ +static struct { + spinlock_t lock; + spinlock_t dbb_irqs_lock; + struct work_struct mask_work; + struct mutex ac_wake_lock; + struct completion ac_wake_work; + struct { + u32 dbb_irqs; + u32 dbb_wakeups; + u32 abb_events; + } req; +} mb0_transfer; + +/* + * mb1_transfer - state needed for mailbox 1 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ape_opp: The current APE OPP. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + u8 ape_opp; + struct { + u8 header; + u8 arm_opp; + u8 ape_opp; + u8 ape_voltage_status; + } ack; +} mb1_transfer; + +/* + * mb2_transfer - state needed for mailbox 2 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @auto_pm_lock: The autonomous power management configuration lock. + * @auto_pm_enabled: A flag indicating whether autonomous PM is enabled. + * @req: Request data that need to persist between requests. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + spinlock_t auto_pm_lock; + bool auto_pm_enabled; + struct { + u8 status; + } ack; +} mb2_transfer; + +/* + * mb3_transfer - state needed for mailbox 3 communication. + * @lock: The request lock. + * @sysclk_lock: A lock used to handle concurrent sysclk requests. + * @sysclk_work: Work structure used for sysclk requests. + */ +static struct { + spinlock_t lock; + struct mutex sysclk_lock; + struct completion sysclk_work; +} mb3_transfer; + +/* + * mb4_transfer - state needed for mailbox 4 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + */ +static struct { + struct mutex lock; + struct completion work; +} mb4_transfer; + +/* + * mb5_transfer - state needed for mailbox 5 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 status; + u8 value; + } ack; +} mb5_transfer; + +static atomic_t ac_wake_req_state = ATOMIC_INIT(0); + +/* Spinlocks */ +static DEFINE_SPINLOCK(prcmu_lock); +static DEFINE_SPINLOCK(clkout_lock); + +/* Global var to runtime determine TCDM base for v2 or v1 */ +static __iomem void *tcdm_base; +static __iomem void *prcmu_base; + +struct clk_mgt { + u32 offset; + u32 pllsw; + int branch; + bool clk38div; +}; + +enum { + PLL_RAW, + PLL_FIX, + PLL_DIV +}; + +static DEFINE_SPINLOCK(clk_mgt_lock); + +#define CLK_MGT_ENTRY(_name, _branch, _clk38div)[PRCMU_##_name] = \ + { (PRCM_##_name##_MGT), 0 , _branch, _clk38div} +static struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { + CLK_MGT_ENTRY(SGACLK, PLL_DIV, false), + CLK_MGT_ENTRY(UARTCLK, PLL_FIX, true), + CLK_MGT_ENTRY(MSP02CLK, PLL_FIX, true), + CLK_MGT_ENTRY(MSP1CLK, PLL_FIX, true), + CLK_MGT_ENTRY(I2CCLK, PLL_FIX, true), + CLK_MGT_ENTRY(SDMMCCLK, PLL_DIV, true), + CLK_MGT_ENTRY(SLIMCLK, PLL_FIX, true), + CLK_MGT_ENTRY(PER1CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER2CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER3CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER5CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER6CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER7CLK, PLL_DIV, true), + CLK_MGT_ENTRY(LCDCLK, PLL_FIX, true), + CLK_MGT_ENTRY(BMLCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HSITXCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HSIRXCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HDMICLK, PLL_FIX, false), + CLK_MGT_ENTRY(APEATCLK, PLL_DIV, true), + CLK_MGT_ENTRY(APETRACECLK, PLL_DIV, true), + CLK_MGT_ENTRY(MCDECLK, PLL_DIV, true), + CLK_MGT_ENTRY(IPI2CCLK, PLL_FIX, true), + CLK_MGT_ENTRY(DSIALTCLK, PLL_FIX, false), + CLK_MGT_ENTRY(DMACLK, PLL_DIV, true), + CLK_MGT_ENTRY(B2R2CLK, PLL_DIV, true), + CLK_MGT_ENTRY(TVCLK, PLL_FIX, true), + CLK_MGT_ENTRY(SSPCLK, PLL_FIX, true), + CLK_MGT_ENTRY(RNGCLK, PLL_FIX, true), + CLK_MGT_ENTRY(UICCCLK, PLL_FIX, false), +}; + +struct dsiclk { + u32 divsel_mask; + u32 divsel_shift; + u32 divsel; +}; + +static struct dsiclk dsiclk[2] = { + { + .divsel_mask = PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_MASK, + .divsel_shift = PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_SHIFT, + .divsel = PRCM_DSI_PLLOUT_SEL_PHI, + }, + { + .divsel_mask = PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_MASK, + .divsel_shift = PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_SHIFT, + .divsel = PRCM_DSI_PLLOUT_SEL_PHI, + } +}; + +struct dsiescclk { + u32 en; + u32 div_mask; + u32 div_shift; +}; + +static struct dsiescclk dsiescclk[3] = { + { + .en = PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_EN, + .div_mask = PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_MASK, + .div_shift = PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_SHIFT, + }, + { + .en = PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_EN, + .div_mask = PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_MASK, + .div_shift = PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_SHIFT, + }, + { + .en = PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_EN, + .div_mask = PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_MASK, + .div_shift = PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_SHIFT, + } +}; + + +/* +* Used by MCDE to setup all necessary PRCMU registers +*/ +#define PRCMU_RESET_DSIPLL 0x00004000 +#define PRCMU_UNCLAMP_DSIPLL 0x00400800 + +#define PRCMU_CLK_PLL_DIV_SHIFT 0 +#define PRCMU_CLK_PLL_SW_SHIFT 5 +#define PRCMU_CLK_38 (1 << 9) +#define PRCMU_CLK_38_SRC (1 << 10) +#define PRCMU_CLK_38_DIV (1 << 11) + +/* PLLDIV=12, PLLSW=4 (PLLDDR) */ +#define PRCMU_DSI_CLOCK_SETTING 0x0000008C + +/* DPI 50000000 Hz */ +#define PRCMU_DPI_CLOCK_SETTING ((1 << PRCMU_CLK_PLL_SW_SHIFT) | \ + (16 << PRCMU_CLK_PLL_DIV_SHIFT)) +#define PRCMU_DSI_LP_CLOCK_SETTING 0x00000E00 + +/* D=101, N=1, R=4, SELDIV2=0 */ +#define PRCMU_PLLDSI_FREQ_SETTING 0x00040165 + +#define PRCMU_ENABLE_PLLDSI 0x00000001 +#define PRCMU_DISABLE_PLLDSI 0x00000000 +#define PRCMU_RELEASE_RESET_DSS 0x0000400C +#define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000202 +/* ESC clk, div0=1, div1=1, div2=3 */ +#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x07030101 +#define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00030101 +#define PRCMU_DSI_RESET_SW 0x00000007 + +#define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 + +int db8500_prcmu_enable_dsipll(void) +{ + int i; + + /* Clear DSIPLL_RESETN */ + writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); + /* Unclamp DSIPLL in/out */ + writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); + + /* Set DSI PLL FREQ */ + writel(PRCMU_PLLDSI_FREQ_SETTING, PRCM_PLLDSI_FREQ); + writel(PRCMU_DSI_PLLOUT_SEL_SETTING, PRCM_DSI_PLLOUT_SEL); + /* Enable Escape clocks */ + writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + + /* Start DSI PLL */ + writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + /* Reset DSI PLL */ + writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); + for (i = 0; i < 10; i++) { + if ((readl(PRCM_PLLDSI_LOCKP) & PRCMU_PLLDSI_LOCKP_LOCKED) + == PRCMU_PLLDSI_LOCKP_LOCKED) + break; + udelay(100); + } + /* Set DSIPLL_RESETN */ + writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); + return 0; +} + +int db8500_prcmu_disable_dsipll(void) +{ + /* Disable dsi pll */ + writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + /* Disable escapeclock */ + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + return 0; +} + +int db8500_prcmu_set_display_clocks(void) +{ + unsigned long flags; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + writel(PRCMU_DSI_CLOCK_SETTING, prcmu_base + PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_LP_CLOCK_SETTING, prcmu_base + PRCM_TVCLK_MGT); + writel(PRCMU_DPI_CLOCK_SETTING, prcmu_base + PRCM_LCDCLK_MGT); + + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +u32 db8500_prcmu_read(unsigned int reg) +{ + return readl(prcmu_base + reg); +} + +void db8500_prcmu_write(unsigned int reg, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&prcmu_lock, flags); + writel(value, (prcmu_base + reg)); + spin_unlock_irqrestore(&prcmu_lock, flags); +} + +void db8500_prcmu_write_masked(unsigned int reg, u32 mask, u32 value) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&prcmu_lock, flags); + val = readl(prcmu_base + reg); + val = ((val & ~mask) | (value & mask)); + writel(val, (prcmu_base + reg)); + spin_unlock_irqrestore(&prcmu_lock, flags); +} + +struct prcmu_fw_version *prcmu_get_fw_version(void) +{ + return fw_info.valid ? &fw_info.version : NULL; +} + +bool prcmu_has_arm_maxopp(void) +{ + return (readb(tcdm_base + PRCM_AVS_VARM_MAX_OPP) & + PRCM_AVS_ISMODEENABLE_MASK) == PRCM_AVS_ISMODEENABLE_MASK; +} + +/** + * prcmu_set_rc_a2p - This function is used to run few power state sequences + * @val: Value to be set, i.e. transition requested + * Returns: 0 on success, -EINVAL on invalid argument + * + * This function is used to run the following power state sequences - + * any state to ApReset, ApDeepSleep to ApExecute, ApExecute to ApDeepSleep + */ +int prcmu_set_rc_a2p(enum romcode_write val) +{ + if (val < RDY_2_DS || val > RDY_2_XP70_RST) + return -EINVAL; + writeb(val, (tcdm_base + PRCM_ROMCODE_A2P)); + return 0; +} + +/** + * prcmu_get_rc_p2a - This function is used to get power state sequences + * Returns: the power transition that has last happened + * + * This function can return the following transitions- + * any state to ApReset, ApDeepSleep to ApExecute, ApExecute to ApDeepSleep + */ +enum romcode_read prcmu_get_rc_p2a(void) +{ + return readb(tcdm_base + PRCM_ROMCODE_P2A); +} + +/** + * prcmu_get_current_mode - Return the current XP70 power mode + * Returns: Returns the current AP(ARM) power mode: init, + * apBoot, apExecute, apDeepSleep, apSleep, apIdle, apReset + */ +enum ap_pwrst prcmu_get_xp70_current_state(void) +{ + return readb(tcdm_base + PRCM_XP70_CUR_PWR_STATE); +} + +/** + * prcmu_config_clkout - Configure one of the programmable clock outputs. + * @clkout: The CLKOUT number (0 or 1). + * @source: The clock to be used (one of the PRCMU_CLKSRC_*). + * @div: The divider to be applied. + * + * Configures one of the programmable clock outputs (CLKOUTs). + * @div should be in the range [1,63] to request a configuration, or 0 to + * inform that the configuration is no longer requested. + */ +int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + static int requests[2]; + int r = 0; + unsigned long flags; + u32 val; + u32 bits; + u32 mask; + u32 div_mask; + + BUG_ON(clkout > 1); + BUG_ON(div > 63); + BUG_ON((clkout == 0) && (source > PRCMU_CLKSRC_CLK009)); + + if (!div && !requests[clkout]) + return -EINVAL; + + switch (clkout) { + case 0: + div_mask = PRCM_CLKOCR_CLKODIV0_MASK; + mask = (PRCM_CLKOCR_CLKODIV0_MASK | PRCM_CLKOCR_CLKOSEL0_MASK); + bits = ((source << PRCM_CLKOCR_CLKOSEL0_SHIFT) | + (div << PRCM_CLKOCR_CLKODIV0_SHIFT)); + break; + case 1: + div_mask = PRCM_CLKOCR_CLKODIV1_MASK; + mask = (PRCM_CLKOCR_CLKODIV1_MASK | PRCM_CLKOCR_CLKOSEL1_MASK | + PRCM_CLKOCR_CLK1TYPE); + bits = ((source << PRCM_CLKOCR_CLKOSEL1_SHIFT) | + (div << PRCM_CLKOCR_CLKODIV1_SHIFT)); + break; + } + bits &= mask; + + spin_lock_irqsave(&clkout_lock, flags); + + val = readl(PRCM_CLKOCR); + if (val & div_mask) { + if (div) { + if ((val & mask) != bits) { + r = -EBUSY; + goto unlock_and_return; + } + } else { + if ((val & mask & ~div_mask) != bits) { + r = -EINVAL; + goto unlock_and_return; + } + } + } + writel((bits | (val & ~mask)), PRCM_CLKOCR); + requests[clkout] += (div ? 1 : -1); + +unlock_and_return: + spin_unlock_irqrestore(&clkout_lock, flags); + + return r; +} + +int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) +{ + unsigned long flags; + + BUG_ON((state < PRCMU_AP_SLEEP) || (PRCMU_AP_DEEP_IDLE < state)); + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writeb(MB0H_POWER_STATE_TRANS, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); + writeb(state, (tcdm_base + PRCM_REQ_MB0_AP_POWER_STATE)); + writeb((keep_ap_pll ? 1 : 0), (tcdm_base + PRCM_REQ_MB0_AP_PLL_STATE)); + writeb((keep_ulp_clk ? 1 : 0), + (tcdm_base + PRCM_REQ_MB0_ULP_CLOCK_STATE)); + writeb(0, (tcdm_base + PRCM_REQ_MB0_DO_NOT_WFI)); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return 0; +} + +u8 db8500_prcmu_get_power_state_result(void) +{ + return readb(tcdm_base + PRCM_ACK_MB0_AP_PWRSTTR_STATUS); +} + +/* This function should only be called while mb0_transfer.lock is held. */ +static void config_wakeups(void) +{ + const u8 header[2] = { + MB0H_CONFIG_WAKEUPS_EXE, + MB0H_CONFIG_WAKEUPS_SLEEP + }; + static u32 last_dbb_events; + static u32 last_abb_events; + u32 dbb_events; + u32 abb_events; + unsigned int i; + + dbb_events = mb0_transfer.req.dbb_irqs | mb0_transfer.req.dbb_wakeups; + dbb_events |= (WAKEUP_BIT_AC_WAKE_ACK | WAKEUP_BIT_AC_SLEEP_ACK); + + abb_events = mb0_transfer.req.abb_events; + + if ((dbb_events == last_dbb_events) && (abb_events == last_abb_events)) + return; + + for (i = 0; i < 2; i++) { + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + writel(dbb_events, (tcdm_base + PRCM_REQ_MB0_WAKEUP_8500)); + writel(abb_events, (tcdm_base + PRCM_REQ_MB0_WAKEUP_4500)); + writeb(header[i], (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + } + last_dbb_events = dbb_events; + last_abb_events = abb_events; +} + +void db8500_prcmu_enable_wakeups(u32 wakeups) +{ + unsigned long flags; + u32 bits; + int i; + + BUG_ON(wakeups != (wakeups & VALID_WAKEUPS)); + + for (i = 0, bits = 0; i < NUM_PRCMU_WAKEUP_INDICES; i++) { + if (wakeups & BIT(i)) + bits |= prcmu_wakeup_bit[i]; + } + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.dbb_wakeups = bits; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db8500_prcmu_config_abb_event_readout(u32 abb_events) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.abb_events = abb_events; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db8500_prcmu_get_abb_event_buffer(void __iomem **buf) +{ + if (readb(tcdm_base + PRCM_ACK_MB0_READ_POINTER) & 1) + *buf = (tcdm_base + PRCM_ACK_MB0_WAKEUP_1_4500); + else + *buf = (tcdm_base + PRCM_ACK_MB0_WAKEUP_0_4500); +} + +/** + * db8500_prcmu_set_arm_opp - set the appropriate ARM OPP + * @opp: The new ARM operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function sets the the operating point of the ARM. + */ +int db8500_prcmu_set_arm_opp(u8 opp) +{ + int r; + + if (opp < ARM_NO_CHANGE || opp > ARM_EXTCLK) + return -EINVAL; + + r = 0; + + mutex_lock(&mb1_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writeb(opp, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); + writeb(APE_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_APE_OPP)); + + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || + (mb1_transfer.ack.arm_opp != opp)) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * db8500_prcmu_get_arm_opp - get the current ARM OPP + * + * Returns: the current ARM OPP + */ +int db8500_prcmu_get_arm_opp(void) +{ + return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_ARM_OPP); +} + +/** + * db8500_prcmu_get_ddr_opp - get the current DDR OPP + * + * Returns: the current DDR OPP + */ +int db8500_prcmu_get_ddr_opp(void) +{ + return readb(PRCM_DDR_SUBSYS_APE_MINBW); +} + +/** + * db8500_set_ddr_opp - set the appropriate DDR OPP + * @opp: The new DDR operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function sets the operating point of the DDR. + */ +static bool enable_set_ddr_opp; +int db8500_prcmu_set_ddr_opp(u8 opp) +{ + if (opp < DDR_100_OPP || opp > DDR_25_OPP) + return -EINVAL; + /* Changing the DDR OPP can hang the hardware pre-v21 */ + if (enable_set_ddr_opp) + writeb(opp, PRCM_DDR_SUBSYS_APE_MINBW); + + return 0; +} + +/* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */ +static void request_even_slower_clocks(bool enable) +{ + u32 clock_reg[] = { + PRCM_ACLK_MGT, + PRCM_DMACLK_MGT + }; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < ARRAY_SIZE(clock_reg); i++) { + u32 val; + u32 div; + + val = readl(prcmu_base + clock_reg[i]); + div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK); + if (enable) { + if ((div <= 1) || (div > 15)) { + pr_err("prcmu: Bad clock divider %d in %s\n", + div, __func__); + goto unlock_and_return; + } + div <<= 1; + } else { + if (div <= 2) + goto unlock_and_return; + div >>= 1; + } + val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) | + (div & PRCM_CLK_MGT_CLKPLLDIV_MASK)); + writel(val, prcmu_base + clock_reg[i]); + } + +unlock_and_return: + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} + +/** + * db8500_set_ape_opp - set the appropriate APE OPP + * @opp: The new APE operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function sets the operating point of the APE. + */ +int db8500_prcmu_set_ape_opp(u8 opp) +{ + int r = 0; + + if (opp == mb1_transfer.ape_opp) + return 0; + + mutex_lock(&mb1_transfer.lock); + + if (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP) + request_even_slower_clocks(false); + + if ((opp != APE_100_OPP) && (mb1_transfer.ape_opp != APE_100_OPP)) + goto skip_message; + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writeb(ARM_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); + writeb(((opp == APE_50_PARTLY_25_OPP) ? APE_50_OPP : opp), + (tcdm_base + PRCM_REQ_MB1_APE_OPP)); + + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || + (mb1_transfer.ack.ape_opp != opp)) + r = -EIO; + +skip_message: + if ((!r && (opp == APE_50_PARTLY_25_OPP)) || + (r && (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP))) + request_even_slower_clocks(true); + if (!r) + mb1_transfer.ape_opp = opp; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * db8500_prcmu_get_ape_opp - get the current APE OPP + * + * Returns: the current APE OPP + */ +int db8500_prcmu_get_ape_opp(void) +{ + return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_APE_OPP); +} + +/** + * db8500_prcmu_request_ape_opp_100_voltage - Request APE OPP 100% voltage + * @enable: true to request the higher voltage, false to drop a request. + * + * Calls to this function to enable and disable requests must be balanced. + */ +int db8500_prcmu_request_ape_opp_100_voltage(bool enable) +{ + int r = 0; + u8 header; + static unsigned int requests; + + mutex_lock(&mb1_transfer.lock); + + if (enable) { + if (0 != requests++) + goto unlock_and_return; + header = MB1H_REQUEST_APE_OPP_100_VOLT; + } else { + if (requests == 0) { + r = -EIO; + goto unlock_and_return; + } else if (1 != requests--) { + goto unlock_and_return; + } + header = MB1H_RELEASE_APE_OPP_100_VOLT; + } + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(header, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != header) || + ((mb1_transfer.ack.ape_voltage_status & BIT(0)) != 0)) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * prcmu_release_usb_wakeup_state - release the state required by a USB wakeup + * + * This function releases the power state requirements of a USB wakeup. + */ +int prcmu_release_usb_wakeup_state(void) +{ + int r = 0; + + mutex_lock(&mb1_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_RELEASE_USB_WAKEUP, + (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != MB1H_RELEASE_USB_WAKEUP) || + ((mb1_transfer.ack.ape_voltage_status & BIT(0)) != 0)) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +static int request_pll(u8 clock, bool enable) +{ + int r = 0; + + if (clock == PRCMU_PLLSOC0) + clock = (enable ? PLL_SOC0_ON : PLL_SOC0_OFF); + else if (clock == PRCMU_PLLSOC1) + clock = (enable ? PLL_SOC1_ON : PLL_SOC1_OFF); + else + return -EINVAL; + + mutex_lock(&mb1_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_PLL_ON_OFF, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writeb(clock, (tcdm_base + PRCM_REQ_MB1_PLL_ON_OFF)); + + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + if (mb1_transfer.ack.header != MB1H_PLL_ON_OFF) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * db8500_prcmu_set_epod - set the state of a EPOD (power domain) + * @epod_id: The EPOD to set + * @epod_state: The new EPOD state + * + * This function sets the state of a EPOD (power domain). It may not be called + * from interrupt context. + */ +int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + int r = 0; + bool ram_retention = false; + int i; + + /* check argument */ + BUG_ON(epod_id >= NUM_EPOD_ID); + + /* set flag if retention is possible */ + switch (epod_id) { + case EPOD_ID_SVAMMDSP: + case EPOD_ID_SIAMMDSP: + case EPOD_ID_ESRAM12: + case EPOD_ID_ESRAM34: + ram_retention = true; + break; + } + + /* check argument */ + BUG_ON(epod_state > EPOD_STATE_ON); + BUG_ON(epod_state == EPOD_STATE_RAMRET && !ram_retention); + + /* get lock */ + mutex_lock(&mb2_transfer.lock); + + /* wait for mailbox */ + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* fill in mailbox */ + for (i = 0; i < NUM_EPOD_ID; i++) + writeb(EPOD_STATE_NO_CHANGE, (tcdm_base + PRCM_REQ_MB2 + i)); + writeb(epod_state, (tcdm_base + PRCM_REQ_MB2 + epod_id)); + + writeb(MB2H_DPS, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB2)); + + writel(MBOX_BIT(2), PRCM_MBOX_CPU_SET); + + /* + * The current firmware version does not handle errors correctly, + * and we cannot recover if there is an error. + * This is expected to change when the firmware is updated. + */ + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + goto unlock_and_return; + } + + if (mb2_transfer.ack.status != HWACC_PWR_ST_OK) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + +/** + * prcmu_configure_auto_pm - Configure autonomous power management. + * @sleep: Configuration for ApSleep. + * @idle: Configuration for ApIdle. + */ +void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, + struct prcmu_auto_pm_config *idle) +{ + u32 sleep_cfg; + u32 idle_cfg; + unsigned long flags; + + BUG_ON((sleep == NULL) || (idle == NULL)); + + sleep_cfg = (sleep->sva_auto_pm_enable & 0xF); + sleep_cfg = ((sleep_cfg << 4) | (sleep->sia_auto_pm_enable & 0xF)); + sleep_cfg = ((sleep_cfg << 8) | (sleep->sva_power_on & 0xFF)); + sleep_cfg = ((sleep_cfg << 8) | (sleep->sia_power_on & 0xFF)); + sleep_cfg = ((sleep_cfg << 4) | (sleep->sva_policy & 0xF)); + sleep_cfg = ((sleep_cfg << 4) | (sleep->sia_policy & 0xF)); + + idle_cfg = (idle->sva_auto_pm_enable & 0xF); + idle_cfg = ((idle_cfg << 4) | (idle->sia_auto_pm_enable & 0xF)); + idle_cfg = ((idle_cfg << 8) | (idle->sva_power_on & 0xFF)); + idle_cfg = ((idle_cfg << 8) | (idle->sia_power_on & 0xFF)); + idle_cfg = ((idle_cfg << 4) | (idle->sva_policy & 0xF)); + idle_cfg = ((idle_cfg << 4) | (idle->sia_policy & 0xF)); + + spin_lock_irqsave(&mb2_transfer.auto_pm_lock, flags); + + /* + * The autonomous power management configuration is done through + * fields in mailbox 2, but these fields are only used as shared + * variables - i.e. there is no need to send a message. + */ + writel(sleep_cfg, (tcdm_base + PRCM_REQ_MB2_AUTO_PM_SLEEP)); + writel(idle_cfg, (tcdm_base + PRCM_REQ_MB2_AUTO_PM_IDLE)); + + mb2_transfer.auto_pm_enabled = + ((sleep->sva_auto_pm_enable == PRCMU_AUTO_PM_ON) || + (sleep->sia_auto_pm_enable == PRCMU_AUTO_PM_ON) || + (idle->sva_auto_pm_enable == PRCMU_AUTO_PM_ON) || + (idle->sia_auto_pm_enable == PRCMU_AUTO_PM_ON)); + + spin_unlock_irqrestore(&mb2_transfer.auto_pm_lock, flags); +} +EXPORT_SYMBOL(prcmu_configure_auto_pm); + +bool prcmu_is_auto_pm_enabled(void) +{ + return mb2_transfer.auto_pm_enabled; +} + +static int request_sysclk(bool enable) +{ + int r; + unsigned long flags; + + r = 0; + + mutex_lock(&mb3_transfer.sysclk_lock); + + spin_lock_irqsave(&mb3_transfer.lock, flags); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) + cpu_relax(); + + writeb((enable ? ON : OFF), (tcdm_base + PRCM_REQ_MB3_SYSCLK_MGT)); + + writeb(MB3H_SYSCLK, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB3)); + writel(MBOX_BIT(3), PRCM_MBOX_CPU_SET); + + spin_unlock_irqrestore(&mb3_transfer.lock, flags); + + /* + * The firmware only sends an ACK if we want to enable the + * SysClk, and it succeeds. + */ + if (enable && !wait_for_completion_timeout(&mb3_transfer.sysclk_work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + } + + mutex_unlock(&mb3_transfer.sysclk_lock); + + return r; +} + +static int request_timclk(bool enable) +{ + u32 val = (PRCM_TCR_DOZE_MODE | PRCM_TCR_TENSEL_MASK); + + if (!enable) + val |= PRCM_TCR_STOP_TIMERS; + writel(val, PRCM_TCR); + + return 0; +} + +static int request_clock(u8 clock, bool enable) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(prcmu_base + clk_mgt[clock].offset); + if (enable) { + val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); + } else { + clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); + } + writel(val, prcmu_base + clk_mgt[clock].offset); + + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +static int request_sga_clock(u8 clock, bool enable) +{ + u32 val; + int ret; + + if (enable) { + val = readl(PRCM_CGATING_BYPASS); + writel(val | PRCM_CGATING_BYPASS_ICN2, PRCM_CGATING_BYPASS); + } + + ret = request_clock(clock, enable); + + if (!ret && !enable) { + val = readl(PRCM_CGATING_BYPASS); + writel(val & ~PRCM_CGATING_BYPASS_ICN2, PRCM_CGATING_BYPASS); + } + + return ret; +} + +static inline bool plldsi_locked(void) +{ + return (readl(PRCM_PLLDSI_LOCKP) & + (PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP10 | + PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP3)) == + (PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP10 | + PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP3); +} + +static int request_plldsi(bool enable) +{ + int r = 0; + u32 val; + + writel((PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP | + PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI), (enable ? + PRCM_MMIP_LS_CLAMP_CLR : PRCM_MMIP_LS_CLAMP_SET)); + + val = readl(PRCM_PLLDSI_ENABLE); + if (enable) + val |= PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE; + else + val &= ~PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE; + writel(val, PRCM_PLLDSI_ENABLE); + + if (enable) { + unsigned int i; + bool locked = plldsi_locked(); + + for (i = 10; !locked && (i > 0); --i) { + udelay(100); + locked = plldsi_locked(); + } + if (locked) { + writel(PRCM_APE_RESETN_DSIPLL_RESETN, + PRCM_APE_RESETN_SET); + } else { + writel((PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP | + PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI), + PRCM_MMIP_LS_CLAMP_SET); + val &= ~PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE; + writel(val, PRCM_PLLDSI_ENABLE); + r = -EAGAIN; + } + } else { + writel(PRCM_APE_RESETN_DSIPLL_RESETN, PRCM_APE_RESETN_CLR); + } + return r; +} + +static int request_dsiclk(u8 n, bool enable) +{ + u32 val; + + val = readl(PRCM_DSI_PLLOUT_SEL); + val &= ~dsiclk[n].divsel_mask; + val |= ((enable ? dsiclk[n].divsel : PRCM_DSI_PLLOUT_SEL_OFF) << + dsiclk[n].divsel_shift); + writel(val, PRCM_DSI_PLLOUT_SEL); + return 0; +} + +static int request_dsiescclk(u8 n, bool enable) +{ + u32 val; + + val = readl(PRCM_DSITVCLK_DIV); + enable ? (val |= dsiescclk[n].en) : (val &= ~dsiescclk[n].en); + writel(val, PRCM_DSITVCLK_DIV); + return 0; +} + +/** + * db8500_prcmu_request_clock() - Request for a clock to be enabled or disabled. + * @clock: The clock for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +int db8500_prcmu_request_clock(u8 clock, bool enable) +{ + if (clock == PRCMU_SGACLK) + return request_sga_clock(clock, enable); + else if (clock < PRCMU_NUM_REG_CLOCKS) + return request_clock(clock, enable); + else if (clock == PRCMU_TIMCLK) + return request_timclk(enable); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + return request_dsiclk((clock - PRCMU_DSI0CLK), enable); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + return request_dsiescclk((clock - PRCMU_DSI0ESCCLK), enable); + else if (clock == PRCMU_PLLDSI) + return request_plldsi(enable); + else if (clock == PRCMU_SYSCLK) + return request_sysclk(enable); + else if ((clock == PRCMU_PLLSOC0) || (clock == PRCMU_PLLSOC1)) + return request_pll(clock, enable); + else + return -EINVAL; +} + +static unsigned long pll_rate(void __iomem *reg, unsigned long src_rate, + int branch) +{ + u64 rate; + u32 val; + u32 d; + u32 div = 1; + + val = readl(reg); + + rate = src_rate; + rate *= ((val & PRCM_PLL_FREQ_D_MASK) >> PRCM_PLL_FREQ_D_SHIFT); + + d = ((val & PRCM_PLL_FREQ_N_MASK) >> PRCM_PLL_FREQ_N_SHIFT); + if (d > 1) + div *= d; + + d = ((val & PRCM_PLL_FREQ_R_MASK) >> PRCM_PLL_FREQ_R_SHIFT); + if (d > 1) + div *= d; + + if (val & PRCM_PLL_FREQ_SELDIV2) + div *= 2; + + if ((branch == PLL_FIX) || ((branch == PLL_DIV) && + (val & PRCM_PLL_FREQ_DIV2EN) && + ((reg == PRCM_PLLSOC0_FREQ) || + (reg == PRCM_PLLARM_FREQ) || + (reg == PRCM_PLLDDR_FREQ)))) + div *= 2; + + (void)do_div(rate, div); + + return (unsigned long)rate; +} + +#define ROOT_CLOCK_RATE 38400000 + +static unsigned long clock_rate(u8 clock) +{ + u32 val; + u32 pllsw; + unsigned long rate = ROOT_CLOCK_RATE; + + val = readl(prcmu_base + clk_mgt[clock].offset); + + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div && (val & PRCM_CLK_MGT_CLK38DIV)) + rate /= 2; + return rate; + } + + val |= clk_mgt[clock].pllsw; + pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + + if (pllsw == PRCM_CLK_MGT_CLKPLLSW_SOC0) + rate = pll_rate(PRCM_PLLSOC0_FREQ, rate, clk_mgt[clock].branch); + else if (pllsw == PRCM_CLK_MGT_CLKPLLSW_SOC1) + rate = pll_rate(PRCM_PLLSOC1_FREQ, rate, clk_mgt[clock].branch); + else if (pllsw == PRCM_CLK_MGT_CLKPLLSW_DDR) + rate = pll_rate(PRCM_PLLDDR_FREQ, rate, clk_mgt[clock].branch); + else + return 0; + + if ((clock == PRCMU_SGACLK) && + (val & PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN)) { + u64 r = (rate * 10); + + (void)do_div(r, 25); + return (unsigned long)r; + } + val &= PRCM_CLK_MGT_CLKPLLDIV_MASK; + if (val) + return rate / val; + else + return 0; +} + +static unsigned long armss_rate(void) +{ + u32 r; + unsigned long rate; + + r = readl(PRCM_ARM_CHGCLKREQ); + + if (r & PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ) { + /* External ARMCLKFIX clock */ + + rate = pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, PLL_FIX); + + /* Check PRCM_ARM_CHGCLKREQ divider */ + if (!(r & PRCM_ARM_CHGCLKREQ_PRCM_ARM_DIVSEL)) + rate /= 2; + + /* Check PRCM_ARMCLKFIX_MGT divider */ + r = readl(PRCM_ARMCLKFIX_MGT); + r &= PRCM_CLK_MGT_CLKPLLDIV_MASK; + rate /= r; + + } else {/* ARM PLL */ + rate = pll_rate(PRCM_PLLARM_FREQ, ROOT_CLOCK_RATE, PLL_DIV); + } + + return rate; +} + +static unsigned long dsiclk_rate(u8 n) +{ + u32 divsel; + u32 div = 1; + + divsel = readl(PRCM_DSI_PLLOUT_SEL); + divsel = ((divsel & dsiclk[n].divsel_mask) >> dsiclk[n].divsel_shift); + + if (divsel == PRCM_DSI_PLLOUT_SEL_OFF) + divsel = dsiclk[n].divsel; + else + dsiclk[n].divsel = divsel; + + switch (divsel) { + case PRCM_DSI_PLLOUT_SEL_PHI_4: + div *= 2; + case PRCM_DSI_PLLOUT_SEL_PHI_2: + div *= 2; + case PRCM_DSI_PLLOUT_SEL_PHI: + return pll_rate(PRCM_PLLDSI_FREQ, clock_rate(PRCMU_HDMICLK), + PLL_RAW) / div; + default: + return 0; + } +} + +static unsigned long dsiescclk_rate(u8 n) +{ + u32 div; + + div = readl(PRCM_DSITVCLK_DIV); + div = ((div & dsiescclk[n].div_mask) >> (dsiescclk[n].div_shift)); + return clock_rate(PRCMU_TVCLK) / max((u32)1, div); +} + +unsigned long prcmu_clock_rate(u8 clock) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return clock_rate(clock); + else if (clock == PRCMU_TIMCLK) + return ROOT_CLOCK_RATE / 16; + else if (clock == PRCMU_SYSCLK) + return ROOT_CLOCK_RATE; + else if (clock == PRCMU_PLLSOC0) + return pll_rate(PRCM_PLLSOC0_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLSOC1) + return pll_rate(PRCM_PLLSOC1_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_ARMSS) + return armss_rate(); + else if (clock == PRCMU_PLLDDR) + return pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLDSI) + return pll_rate(PRCM_PLLDSI_FREQ, clock_rate(PRCMU_HDMICLK), + PLL_RAW); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + return dsiclk_rate(clock - PRCMU_DSI0CLK); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + return dsiescclk_rate(clock - PRCMU_DSI0ESCCLK); + else + return 0; +} + +static unsigned long clock_source_rate(u32 clk_mgt_val, int branch) +{ + if (clk_mgt_val & PRCM_CLK_MGT_CLK38) + return ROOT_CLOCK_RATE; + clk_mgt_val &= PRCM_CLK_MGT_CLKPLLSW_MASK; + if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_SOC0) + return pll_rate(PRCM_PLLSOC0_FREQ, ROOT_CLOCK_RATE, branch); + else if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_SOC1) + return pll_rate(PRCM_PLLSOC1_FREQ, ROOT_CLOCK_RATE, branch); + else if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_DDR) + return pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, branch); + else + return 0; +} + +static u32 clock_divider(unsigned long src_rate, unsigned long rate) +{ + u32 div; + + div = (src_rate / rate); + if (div == 0) + return 1; + if (rate < (src_rate / div)) + div++; + return div; +} + +static long round_clock_rate(u8 clock, unsigned long rate) +{ + u32 val; + u32 div; + unsigned long src_rate; + long rounded_rate; + + val = readl(prcmu_base + clk_mgt[clock].offset); + src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), + clk_mgt[clock].branch); + div = clock_divider(src_rate, rate); + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div) { + if (div > 2) + div = 2; + } else { + div = 1; + } + } else if ((clock == PRCMU_SGACLK) && (div == 3)) { + u64 r = (src_rate * 10); + + (void)do_div(r, 25); + if (r <= rate) + return (unsigned long)r; + } + rounded_rate = (src_rate / min(div, (u32)31)); + + return rounded_rate; +} + +/* CPU FREQ table, may be changed due to if MAX_OPP is supported. */ +static struct cpufreq_frequency_table db8500_cpufreq_table[] = { + { .frequency = 200000, .driver_data = ARM_EXTCLK,}, + { .frequency = 400000, .driver_data = ARM_50_OPP,}, + { .frequency = 800000, .driver_data = ARM_100_OPP,}, + { .frequency = CPUFREQ_TABLE_END,}, /* To be used for MAX_OPP. */ + { .frequency = CPUFREQ_TABLE_END,}, +}; + +static long round_armss_rate(unsigned long rate) +{ + struct cpufreq_frequency_table *pos; + long freq = 0; + + /* cpufreq table frequencies is in KHz. */ + rate = rate / 1000; + + /* Find the corresponding arm opp from the cpufreq table. */ + cpufreq_for_each_entry(pos, db8500_cpufreq_table) { + freq = pos->frequency; + if (freq == rate) + break; + } + + /* Return the last valid value, even if a match was not found. */ + return freq * 1000; +} + +#define MIN_PLL_VCO_RATE 600000000ULL +#define MAX_PLL_VCO_RATE 1680640000ULL + +static long round_plldsi_rate(unsigned long rate) +{ + long rounded_rate = 0; + unsigned long src_rate; + unsigned long rem; + u32 r; + + src_rate = clock_rate(PRCMU_HDMICLK); + rem = rate; + + for (r = 7; (rem > 0) && (r > 0); r--) { + u64 d; + + d = (r * rate); + (void)do_div(d, src_rate); + if (d < 6) + d = 6; + else if (d > 255) + d = 255; + d *= src_rate; + if (((2 * d) < (r * MIN_PLL_VCO_RATE)) || + ((r * MAX_PLL_VCO_RATE) < (2 * d))) + continue; + (void)do_div(d, r); + if (rate < d) { + if (rounded_rate == 0) + rounded_rate = (long)d; + break; + } + if ((rate - d) < rem) { + rem = (rate - d); + rounded_rate = (long)d; + } + } + return rounded_rate; +} + +static long round_dsiclk_rate(unsigned long rate) +{ + u32 div; + unsigned long src_rate; + long rounded_rate; + + src_rate = pll_rate(PRCM_PLLDSI_FREQ, clock_rate(PRCMU_HDMICLK), + PLL_RAW); + div = clock_divider(src_rate, rate); + rounded_rate = (src_rate / ((div > 2) ? 4 : div)); + + return rounded_rate; +} + +static long round_dsiescclk_rate(unsigned long rate) +{ + u32 div; + unsigned long src_rate; + long rounded_rate; + + src_rate = clock_rate(PRCMU_TVCLK); + div = clock_divider(src_rate, rate); + rounded_rate = (src_rate / min(div, (u32)255)); + + return rounded_rate; +} + +long prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return round_clock_rate(clock, rate); + else if (clock == PRCMU_ARMSS) + return round_armss_rate(rate); + else if (clock == PRCMU_PLLDSI) + return round_plldsi_rate(rate); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + return round_dsiclk_rate(rate); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + return round_dsiescclk_rate(rate); + else + return (long)prcmu_clock_rate(clock); +} + +static void set_clock_rate(u8 clock, unsigned long rate) +{ + u32 val; + u32 div; + unsigned long src_rate; + unsigned long flags; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(prcmu_base + clk_mgt[clock].offset); + src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), + clk_mgt[clock].branch); + div = clock_divider(src_rate, rate); + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div) { + if (div > 1) + val |= PRCM_CLK_MGT_CLK38DIV; + else + val &= ~PRCM_CLK_MGT_CLK38DIV; + } + } else if (clock == PRCMU_SGACLK) { + val &= ~(PRCM_CLK_MGT_CLKPLLDIV_MASK | + PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN); + if (div == 3) { + u64 r = (src_rate * 10); + + (void)do_div(r, 25); + if (r <= rate) { + val |= PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN; + div = 0; + } + } + val |= min(div, (u32)31); + } else { + val &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + val |= min(div, (u32)31); + } + writel(val, prcmu_base + clk_mgt[clock].offset); + + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} + +static int set_armss_rate(unsigned long rate) +{ + struct cpufreq_frequency_table *pos; + + /* cpufreq table frequencies is in KHz. */ + rate = rate / 1000; + + /* Find the corresponding arm opp from the cpufreq table. */ + cpufreq_for_each_entry(pos, db8500_cpufreq_table) + if (pos->frequency == rate) + break; + + if (pos->frequency != rate) + return -EINVAL; + + /* Set the new arm opp. */ + return db8500_prcmu_set_arm_opp(pos->driver_data); +} + +static int set_plldsi_rate(unsigned long rate) +{ + unsigned long src_rate; + unsigned long rem; + u32 pll_freq = 0; + u32 r; + + src_rate = clock_rate(PRCMU_HDMICLK); + rem = rate; + + for (r = 7; (rem > 0) && (r > 0); r--) { + u64 d; + u64 hwrate; + + d = (r * rate); + (void)do_div(d, src_rate); + if (d < 6) + d = 6; + else if (d > 255) + d = 255; + hwrate = (d * src_rate); + if (((2 * hwrate) < (r * MIN_PLL_VCO_RATE)) || + ((r * MAX_PLL_VCO_RATE) < (2 * hwrate))) + continue; + (void)do_div(hwrate, r); + if (rate < hwrate) { + if (pll_freq == 0) + pll_freq = (((u32)d << PRCM_PLL_FREQ_D_SHIFT) | + (r << PRCM_PLL_FREQ_R_SHIFT)); + break; + } + if ((rate - hwrate) < rem) { + rem = (rate - hwrate); + pll_freq = (((u32)d << PRCM_PLL_FREQ_D_SHIFT) | + (r << PRCM_PLL_FREQ_R_SHIFT)); + } + } + if (pll_freq == 0) + return -EINVAL; + + pll_freq |= (1 << PRCM_PLL_FREQ_N_SHIFT); + writel(pll_freq, PRCM_PLLDSI_FREQ); + + return 0; +} + +static void set_dsiclk_rate(u8 n, unsigned long rate) +{ + u32 val; + u32 div; + + div = clock_divider(pll_rate(PRCM_PLLDSI_FREQ, + clock_rate(PRCMU_HDMICLK), PLL_RAW), rate); + + dsiclk[n].divsel = (div == 1) ? PRCM_DSI_PLLOUT_SEL_PHI : + (div == 2) ? PRCM_DSI_PLLOUT_SEL_PHI_2 : + /* else */ PRCM_DSI_PLLOUT_SEL_PHI_4; + + val = readl(PRCM_DSI_PLLOUT_SEL); + val &= ~dsiclk[n].divsel_mask; + val |= (dsiclk[n].divsel << dsiclk[n].divsel_shift); + writel(val, PRCM_DSI_PLLOUT_SEL); +} + +static void set_dsiescclk_rate(u8 n, unsigned long rate) +{ + u32 val; + u32 div; + + div = clock_divider(clock_rate(PRCMU_TVCLK), rate); + val = readl(PRCM_DSITVCLK_DIV); + val &= ~dsiescclk[n].div_mask; + val |= (min(div, (u32)255) << dsiescclk[n].div_shift); + writel(val, PRCM_DSITVCLK_DIV); +} + +int prcmu_set_clock_rate(u8 clock, unsigned long rate) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + set_clock_rate(clock, rate); + else if (clock == PRCMU_ARMSS) + return set_armss_rate(rate); + else if (clock == PRCMU_PLLDSI) + return set_plldsi_rate(rate); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + set_dsiclk_rate((clock - PRCMU_DSI0CLK), rate); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + set_dsiescclk_rate((clock - PRCMU_DSI0ESCCLK), rate); + return 0; +} + +int db8500_prcmu_config_esram0_deep_sleep(u8 state) +{ + if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || + (state < ESRAM0_DEEP_SLEEP_STATE_OFF)) + return -EINVAL; + + mutex_lock(&mb4_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(MB4H_MEM_ST, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + writeb(((DDR_PWR_STATE_OFFHIGHLAT << 4) | DDR_PWR_STATE_ON), + (tcdm_base + PRCM_REQ_MB4_DDR_ST_AP_SLEEP_IDLE)); + writeb(DDR_PWR_STATE_ON, + (tcdm_base + PRCM_REQ_MB4_DDR_ST_AP_DEEP_IDLE)); + writeb(state, (tcdm_base + PRCM_REQ_MB4_ESRAM0_ST)); + + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +int db8500_prcmu_config_hotdog(u8 threshold) +{ + mutex_lock(&mb4_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(threshold, (tcdm_base + PRCM_REQ_MB4_HOTDOG_THRESHOLD)); + writeb(MB4H_HOTDOG, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +int db8500_prcmu_config_hotmon(u8 low, u8 high) +{ + mutex_lock(&mb4_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(low, (tcdm_base + PRCM_REQ_MB4_HOTMON_LOW)); + writeb(high, (tcdm_base + PRCM_REQ_MB4_HOTMON_HIGH)); + writeb((HOTMON_CONFIG_LOW | HOTMON_CONFIG_HIGH), + (tcdm_base + PRCM_REQ_MB4_HOTMON_CONFIG)); + writeb(MB4H_HOTMON, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +static int config_hot_period(u16 val) +{ + mutex_lock(&mb4_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(val, (tcdm_base + PRCM_REQ_MB4_HOT_PERIOD)); + writeb(MB4H_HOT_PERIOD, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +int db8500_prcmu_start_temp_sense(u16 cycles32k) +{ + if (cycles32k == 0xFFFF) + return -EINVAL; + + return config_hot_period(cycles32k); +} + +int db8500_prcmu_stop_temp_sense(void) +{ + return config_hot_period(0xFFFF); +} + +static int prcmu_a9wdog(u8 cmd, u8 d0, u8 d1, u8 d2, u8 d3) +{ + + mutex_lock(&mb4_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(d0, (tcdm_base + PRCM_REQ_MB4_A9WDOG_0)); + writeb(d1, (tcdm_base + PRCM_REQ_MB4_A9WDOG_1)); + writeb(d2, (tcdm_base + PRCM_REQ_MB4_A9WDOG_2)); + writeb(d3, (tcdm_base + PRCM_REQ_MB4_A9WDOG_3)); + + writeb(cmd, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; + +} + +int db8500_prcmu_config_a9wdog(u8 num, bool sleep_auto_off) +{ + BUG_ON(num == 0 || num > 0xf); + return prcmu_a9wdog(MB4H_A9WDOG_CONF, num, 0, 0, + sleep_auto_off ? A9WDOG_AUTO_OFF_EN : + A9WDOG_AUTO_OFF_DIS); +} +EXPORT_SYMBOL(db8500_prcmu_config_a9wdog); + +int db8500_prcmu_enable_a9wdog(u8 id) +{ + return prcmu_a9wdog(MB4H_A9WDOG_EN, id, 0, 0, 0); +} +EXPORT_SYMBOL(db8500_prcmu_enable_a9wdog); + +int db8500_prcmu_disable_a9wdog(u8 id) +{ + return prcmu_a9wdog(MB4H_A9WDOG_DIS, id, 0, 0, 0); +} +EXPORT_SYMBOL(db8500_prcmu_disable_a9wdog); + +int db8500_prcmu_kick_a9wdog(u8 id) +{ + return prcmu_a9wdog(MB4H_A9WDOG_KICK, id, 0, 0, 0); +} +EXPORT_SYMBOL(db8500_prcmu_kick_a9wdog); + +/* + * timeout is 28 bit, in ms. + */ +int db8500_prcmu_load_a9wdog(u8 id, u32 timeout) +{ + return prcmu_a9wdog(MB4H_A9WDOG_LOAD, + (id & A9WDOG_ID_MASK) | + /* + * Put the lowest 28 bits of timeout at + * offset 4. Four first bits are used for id. + */ + (u8)((timeout << 4) & 0xf0), + (u8)((timeout >> 4) & 0xff), + (u8)((timeout >> 12) & 0xff), + (u8)((timeout >> 20) & 0xff)); +} +EXPORT_SYMBOL(db8500_prcmu_load_a9wdog); + +/** + * prcmu_abb_read() - Read register value(s) from the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The read out value(s). + * @size: The number of registers to read. + * + * Reads register value(s) from the ABB. + * @size has to be 1 for the current firmware version. + */ +int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) +{ + int r; + + if (size != 1) + return -EINVAL; + + mutex_lock(&mb5_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + cpu_relax(); + + writeb(0, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB5)); + writeb(PRCMU_I2C_READ(slave), (tcdm_base + PRCM_REQ_MB5_I2C_SLAVE_OP)); + writeb(PRCMU_I2C_STOP_EN, (tcdm_base + PRCM_REQ_MB5_I2C_HW_BITS)); + writeb(reg, (tcdm_base + PRCM_REQ_MB5_I2C_REG)); + writeb(0, (tcdm_base + PRCM_REQ_MB5_I2C_VAL)); + + writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb5_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + } else { + r = ((mb5_transfer.ack.status == I2C_RD_OK) ? 0 : -EIO); + } + + if (!r) + *value = mb5_transfer.ack.value; + + mutex_unlock(&mb5_transfer.lock); + + return r; +} + +/** + * prcmu_abb_write_masked() - Write masked register value(s) to the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The value(s) to write. + * @mask: The mask(s) to use. + * @size: The number of registers to write. + * + * Writes masked register value(s) to the ABB. + * For each @value, only the bits set to 1 in the corresponding @mask + * will be written. The other bits are not changed. + * @size has to be 1 for the current firmware version. + */ +int prcmu_abb_write_masked(u8 slave, u8 reg, u8 *value, u8 *mask, u8 size) +{ + int r; + + if (size != 1) + return -EINVAL; + + mutex_lock(&mb5_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + cpu_relax(); + + writeb(~*mask, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB5)); + writeb(PRCMU_I2C_WRITE(slave), (tcdm_base + PRCM_REQ_MB5_I2C_SLAVE_OP)); + writeb(PRCMU_I2C_STOP_EN, (tcdm_base + PRCM_REQ_MB5_I2C_HW_BITS)); + writeb(reg, (tcdm_base + PRCM_REQ_MB5_I2C_REG)); + writeb(*value, (tcdm_base + PRCM_REQ_MB5_I2C_VAL)); + + writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb5_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + } else { + r = ((mb5_transfer.ack.status == I2C_WR_OK) ? 0 : -EIO); + } + + mutex_unlock(&mb5_transfer.lock); + + return r; +} + +/** + * prcmu_abb_write() - Write register value(s) to the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The value(s) to write. + * @size: The number of registers to write. + * + * Writes register value(s) to the ABB. + * @size has to be 1 for the current firmware version. + */ +int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +{ + u8 mask = ~0; + + return prcmu_abb_write_masked(slave, reg, value, &mask, size); +} + +/** + * prcmu_ac_wake_req - should be called whenever ARM wants to wakeup Modem + */ +int prcmu_ac_wake_req(void) +{ + u32 val; + int ret = 0; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(PRCM_HOSTACCESS_REQ); + if (val & PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ) + goto unlock_and_return; + + atomic_set(&ac_wake_req_state, 1); + + /* + * Force Modem Wake-up before hostaccess_req ping-pong. + * It prevents Modem to enter in Sleep while acking the hostaccess + * request. The 31us delay has been calculated by HWI. + */ + val |= PRCM_HOSTACCESS_REQ_WAKE_REQ; + writel(val, PRCM_HOSTACCESS_REQ); + + udelay(31); + + val |= PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ; + writel(val, PRCM_HOSTACCESS_REQ); + + if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, + msecs_to_jiffies(5000))) { + pr_crit("prcmu: %s timed out (5 s) waiting for a reply.\n", + __func__); + ret = -EFAULT; + } + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); + return ret; +} + +/** + * prcmu_ac_sleep_req - called when ARM no longer needs to talk to modem + */ +void prcmu_ac_sleep_req(void) +{ + u32 val; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(PRCM_HOSTACCESS_REQ); + if (!(val & PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ)) + goto unlock_and_return; + + writel((val & ~PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), + PRCM_HOSTACCESS_REQ); + + if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, + msecs_to_jiffies(5000))) { + pr_crit("prcmu: %s timed out (5 s) waiting for a reply.\n", + __func__); + } + + atomic_set(&ac_wake_req_state, 0); + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); +} + +bool db8500_prcmu_is_ac_wake_requested(void) +{ + return (atomic_read(&ac_wake_req_state) != 0); +} + +/** + * db8500_prcmu_system_reset - System reset + * + * Saves the reset reason code and then sets the APE_SOFTRST register which + * fires interrupt to fw + */ +void db8500_prcmu_system_reset(u16 reset_code) +{ + writew(reset_code, (tcdm_base + PRCM_SW_RST_REASON)); + writel(1, PRCM_APE_SOFTRST); +} + +/** + * db8500_prcmu_get_reset_code - Retrieve SW reset reason code + * + * Retrieves the reset reason code stored by prcmu_system_reset() before + * last restart. + */ +u16 db8500_prcmu_get_reset_code(void) +{ + return readw(tcdm_base + PRCM_SW_RST_REASON); +} + +/** + * db8500_prcmu_reset_modem - ask the PRCMU to reset modem + */ +void db8500_prcmu_modem_reset(void) +{ + mutex_lock(&mb1_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_RESET_MODEM, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + /* + * No need to check return from PRCMU as modem should go in reset state + * This state is already managed by upper layer + */ + + mutex_unlock(&mb1_transfer.lock); +} + +static void ack_dbb_wakeup(void) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writeb(MB0H_READ_WAKEUP_ACK, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +static inline void print_unknown_header_warning(u8 n, u8 header) +{ + pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", + header, n); +} + +static bool read_mailbox_0(void) +{ + bool r; + u32 ev; + unsigned int n; + u8 header; + + header = readb(tcdm_base + PRCM_MBOX_HEADER_ACK_MB0); + switch (header) { + case MB0H_WAKEUP_EXE: + case MB0H_WAKEUP_SLEEP: + if (readb(tcdm_base + PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(tcdm_base + PRCM_ACK_MB0_WAKEUP_1_8500); + else + ev = readl(tcdm_base + PRCM_ACK_MB0_WAKEUP_0_8500); + + if (ev & (WAKEUP_BIT_AC_WAKE_ACK | WAKEUP_BIT_AC_SLEEP_ACK)) + complete(&mb0_transfer.ac_wake_work); + if (ev & WAKEUP_BIT_SYSCLK_OK) + complete(&mb3_transfer.sysclk_work); + + ev &= mb0_transfer.req.dbb_irqs; + + for (n = 0; n < NUM_PRCMU_WAKEUPS; n++) { + if (ev & prcmu_irq_bit[n]) + generic_handle_irq(irq_find_mapping(db8500_irq_domain, n)); + } + r = true; + break; + default: + print_unknown_header_warning(0, header); + r = false; + break; + } + writel(MBOX_BIT(0), PRCM_ARM_IT1_CLR); + return r; +} + +static bool read_mailbox_1(void) +{ + mb1_transfer.ack.header = readb(tcdm_base + PRCM_MBOX_HEADER_REQ_MB1); + mb1_transfer.ack.arm_opp = readb(tcdm_base + + PRCM_ACK_MB1_CURRENT_ARM_OPP); + mb1_transfer.ack.ape_opp = readb(tcdm_base + + PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_status = readb(tcdm_base + + PRCM_ACK_MB1_APE_VOLTAGE_STATUS); + writel(MBOX_BIT(1), PRCM_ARM_IT1_CLR); + complete(&mb1_transfer.work); + return false; +} + +static bool read_mailbox_2(void) +{ + mb2_transfer.ack.status = readb(tcdm_base + PRCM_ACK_MB2_DPS_STATUS); + writel(MBOX_BIT(2), PRCM_ARM_IT1_CLR); + complete(&mb2_transfer.work); + return false; +} + +static bool read_mailbox_3(void) +{ + writel(MBOX_BIT(3), PRCM_ARM_IT1_CLR); + return false; +} + +static bool read_mailbox_4(void) +{ + u8 header; + bool do_complete = true; + + header = readb(tcdm_base + PRCM_MBOX_HEADER_REQ_MB4); + switch (header) { + case MB4H_MEM_ST: + case MB4H_HOTDOG: + case MB4H_HOTMON: + case MB4H_HOT_PERIOD: + case MB4H_A9WDOG_CONF: + case MB4H_A9WDOG_EN: + case MB4H_A9WDOG_DIS: + case MB4H_A9WDOG_LOAD: + case MB4H_A9WDOG_KICK: + break; + default: + print_unknown_header_warning(4, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(4), PRCM_ARM_IT1_CLR); + + if (do_complete) + complete(&mb4_transfer.work); + + return false; +} + +static bool read_mailbox_5(void) +{ + mb5_transfer.ack.status = readb(tcdm_base + PRCM_ACK_MB5_I2C_STATUS); + mb5_transfer.ack.value = readb(tcdm_base + PRCM_ACK_MB5_I2C_VAL); + writel(MBOX_BIT(5), PRCM_ARM_IT1_CLR); + complete(&mb5_transfer.work); + return false; +} + +static bool read_mailbox_6(void) +{ + writel(MBOX_BIT(6), PRCM_ARM_IT1_CLR); + return false; +} + +static bool read_mailbox_7(void) +{ + writel(MBOX_BIT(7), PRCM_ARM_IT1_CLR); + return false; +} + +static bool (* const read_mailbox[NUM_MB])(void) = { + read_mailbox_0, + read_mailbox_1, + read_mailbox_2, + read_mailbox_3, + read_mailbox_4, + read_mailbox_5, + read_mailbox_6, + read_mailbox_7 +}; + +static irqreturn_t prcmu_irq_handler(int irq, void *data) +{ + u32 bits; + u8 n; + irqreturn_t r; + + bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); + if (unlikely(!bits)) + return IRQ_NONE; + + r = IRQ_HANDLED; + for (n = 0; bits; n++) { + if (bits & MBOX_BIT(n)) { + bits -= MBOX_BIT(n); + if (read_mailbox[n]()) + r = IRQ_WAKE_THREAD; + } + } + return r; +} + +static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) +{ + ack_dbb_wakeup(); + return IRQ_HANDLED; +} + +static void prcmu_mask_work(struct work_struct *work) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +static void prcmu_irq_mask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs &= ~prcmu_irq_bit[d->hwirq]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + + if (d->irq != IRQ_PRCMU_CA_SLEEP) + schedule_work(&mb0_transfer.mask_work); +} + +static void prcmu_irq_unmask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs |= prcmu_irq_bit[d->hwirq]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + + if (d->irq != IRQ_PRCMU_CA_SLEEP) + schedule_work(&mb0_transfer.mask_work); +} + +static void noop(struct irq_data *d) +{ +} + +static struct irq_chip prcmu_irq_chip = { + .name = "prcmu", + .irq_disable = prcmu_irq_mask, + .irq_ack = noop, + .irq_mask = prcmu_irq_mask, + .irq_unmask = prcmu_irq_unmask, +}; + +static __init char *fw_project_name(u32 project) +{ + switch (project) { + case PRCMU_FW_PROJECT_U8500: + return "U8500"; + case PRCMU_FW_PROJECT_U8400: + return "U8400"; + case PRCMU_FW_PROJECT_U9500: + return "U9500"; + case PRCMU_FW_PROJECT_U8500_MBB: + return "U8500 MBB"; + case PRCMU_FW_PROJECT_U8500_C1: + return "U8500 C1"; + case PRCMU_FW_PROJECT_U8500_C2: + return "U8500 C2"; + case PRCMU_FW_PROJECT_U8500_C3: + return "U8500 C3"; + case PRCMU_FW_PROJECT_U8500_C4: + return "U8500 C4"; + case PRCMU_FW_PROJECT_U9500_MBL: + return "U9500 MBL"; + case PRCMU_FW_PROJECT_U8500_MBL: + return "U8500 MBL"; + case PRCMU_FW_PROJECT_U8500_MBL2: + return "U8500 MBL2"; + case PRCMU_FW_PROJECT_U8520: + return "U8520 MBL"; + case PRCMU_FW_PROJECT_U8420: + return "U8420"; + case PRCMU_FW_PROJECT_U9540: + return "U9540"; + case PRCMU_FW_PROJECT_A9420: + return "A9420"; + case PRCMU_FW_PROJECT_L8540: + return "L8540"; + case PRCMU_FW_PROJECT_L8580: + return "L8580"; + default: + return "Unknown"; + } +} + +static int db8500_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(virq, &prcmu_irq_chip, + handle_simple_irq); + set_irq_flags(virq, IRQF_VALID); + + return 0; +} + +static struct irq_domain_ops db8500_irq_ops = { + .map = db8500_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int db8500_irq_init(struct device_node *np) +{ + int i; + + db8500_irq_domain = irq_domain_add_simple( + np, NUM_PRCMU_WAKEUPS, 0, + &db8500_irq_ops, NULL); + + if (!db8500_irq_domain) { + pr_err("Failed to create irqdomain\n"); + return -ENOSYS; + } + + /* All wakeups will be used, so create mappings for all */ + for (i = 0; i < NUM_PRCMU_WAKEUPS; i++) + irq_create_mapping(db8500_irq_domain, i); + + return 0; +} + +static void dbx500_fw_version_init(struct platform_device *pdev, + u32 version_offset) +{ + struct resource *res; + void __iomem *tcpm_base; + u32 version; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "prcmu-tcpm"); + if (!res) { + dev_err(&pdev->dev, + "Error: no prcmu tcpm memory region provided\n"); + return; + } + tcpm_base = ioremap(res->start, resource_size(res)); + if (!tcpm_base) { + dev_err(&pdev->dev, "no prcmu tcpm mem region provided\n"); + return; + } + + version = readl(tcpm_base + version_offset); + fw_info.version.project = (version & 0xFF); + fw_info.version.api_version = (version >> 8) & 0xFF; + fw_info.version.func_version = (version >> 16) & 0xFF; + fw_info.version.errata = (version >> 24) & 0xFF; + strncpy(fw_info.version.project_name, + fw_project_name(fw_info.version.project), + PRCMU_FW_PROJECT_NAME_LEN); + fw_info.valid = true; + pr_info("PRCMU firmware: %s(%d), version %d.%d.%d\n", + fw_info.version.project_name, + fw_info.version.project, + fw_info.version.api_version, + fw_info.version.func_version, + fw_info.version.errata); + iounmap(tcpm_base); +} + +void __init db8500_prcmu_early_init(u32 phy_base, u32 size) +{ + /* + * This is a temporary remap to bring up the clocks. It is + * subsequently replaces with a real remap. After the merge of + * the mailbox subsystem all of this early code goes away, and the + * clock driver can probe independently. An early initcall will + * still be needed, but it can be diverted into drivers/clk/ux500. + */ + prcmu_base = ioremap(phy_base, size); + if (!prcmu_base) + pr_err("%s: ioremap() of prcmu registers failed!\n", __func__); + + spin_lock_init(&mb0_transfer.lock); + spin_lock_init(&mb0_transfer.dbb_irqs_lock); + mutex_init(&mb0_transfer.ac_wake_lock); + init_completion(&mb0_transfer.ac_wake_work); + mutex_init(&mb1_transfer.lock); + init_completion(&mb1_transfer.work); + mb1_transfer.ape_opp = APE_NO_CHANGE; + mutex_init(&mb2_transfer.lock); + init_completion(&mb2_transfer.work); + spin_lock_init(&mb2_transfer.auto_pm_lock); + spin_lock_init(&mb3_transfer.lock); + mutex_init(&mb3_transfer.sysclk_lock); + init_completion(&mb3_transfer.sysclk_work); + mutex_init(&mb4_transfer.lock); + init_completion(&mb4_transfer.work); + mutex_init(&mb5_transfer.lock); + init_completion(&mb5_transfer.work); + + INIT_WORK(&mb0_transfer.mask_work, prcmu_mask_work); +} + +static void __init init_prcm_registers(void) +{ + u32 val; + + val = readl(PRCM_A9PL_FORCE_CLKEN); + val &= ~(PRCM_A9PL_FORCE_CLKEN_PRCM_A9PL_FORCE_CLKEN | + PRCM_A9PL_FORCE_CLKEN_PRCM_A9AXI_FORCE_CLKEN); + writel(val, (PRCM_A9PL_FORCE_CLKEN)); +} + +/* + * Power domain switches (ePODs) modeled as regulators for the DB8500 SoC + */ +static struct regulator_consumer_supply db8500_vape_consumers[] = { + REGULATOR_SUPPLY("v-ape", NULL), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.0"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.1"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.2"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.3"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.4"), + /* "v-mmc" changed to "vcore" in the mainline kernel */ + REGULATOR_SUPPLY("vcore", "sdi0"), + REGULATOR_SUPPLY("vcore", "sdi1"), + REGULATOR_SUPPLY("vcore", "sdi2"), + REGULATOR_SUPPLY("vcore", "sdi3"), + REGULATOR_SUPPLY("vcore", "sdi4"), + REGULATOR_SUPPLY("v-dma", "dma40.0"), + REGULATOR_SUPPLY("v-ape", "ab8500-usb.0"), + /* "v-uart" changed to "vcore" in the mainline kernel */ + REGULATOR_SUPPLY("vcore", "uart0"), + REGULATOR_SUPPLY("vcore", "uart1"), + REGULATOR_SUPPLY("vcore", "uart2"), + REGULATOR_SUPPLY("v-ape", "nmk-ske-keypad.0"), + REGULATOR_SUPPLY("v-hsi", "ste_hsi.0"), + REGULATOR_SUPPLY("vddvario", "smsc911x.0"), +}; + +static struct regulator_consumer_supply db8500_vsmps2_consumers[] = { + REGULATOR_SUPPLY("musb_1v8", "ab8500-usb.0"), + /* AV8100 regulator */ + REGULATOR_SUPPLY("hdmi_1v8", "0-0070"), +}; + +static struct regulator_consumer_supply db8500_b2r2_mcde_consumers[] = { + REGULATOR_SUPPLY("vsupply", "b2r2_bus"), + REGULATOR_SUPPLY("vsupply", "mcde"), +}; + +/* SVA MMDSP regulator switch */ +static struct regulator_consumer_supply db8500_svammdsp_consumers[] = { + REGULATOR_SUPPLY("sva-mmdsp", "cm_control"), +}; + +/* SVA pipe regulator switch */ +static struct regulator_consumer_supply db8500_svapipe_consumers[] = { + REGULATOR_SUPPLY("sva-pipe", "cm_control"), +}; + +/* SIA MMDSP regulator switch */ +static struct regulator_consumer_supply db8500_siammdsp_consumers[] = { + REGULATOR_SUPPLY("sia-mmdsp", "cm_control"), +}; + +/* SIA pipe regulator switch */ +static struct regulator_consumer_supply db8500_siapipe_consumers[] = { + REGULATOR_SUPPLY("sia-pipe", "cm_control"), +}; + +static struct regulator_consumer_supply db8500_sga_consumers[] = { + REGULATOR_SUPPLY("v-mali", NULL), +}; + +/* ESRAM1 and 2 regulator switch */ +static struct regulator_consumer_supply db8500_esram12_consumers[] = { + REGULATOR_SUPPLY("esram12", "cm_control"), +}; + +/* ESRAM3 and 4 regulator switch */ +static struct regulator_consumer_supply db8500_esram34_consumers[] = { + REGULATOR_SUPPLY("v-esram34", "mcde"), + REGULATOR_SUPPLY("esram34", "cm_control"), + REGULATOR_SUPPLY("lcla_esram", "dma40.0"), +}; + +static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { + [DB8500_REGULATOR_VAPE] = { + .constraints = { + .name = "db8500-vape", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .always_on = true, + }, + .consumer_supplies = db8500_vape_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_vape_consumers), + }, + [DB8500_REGULATOR_VARM] = { + .constraints = { + .name = "db8500-varm", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VMODEM] = { + .constraints = { + .name = "db8500-vmodem", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VPLL] = { + .constraints = { + .name = "db8500-vpll", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VSMPS1] = { + .constraints = { + .name = "db8500-vsmps1", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VSMPS2] = { + .constraints = { + .name = "db8500-vsmps2", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_vsmps2_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_vsmps2_consumers), + }, + [DB8500_REGULATOR_VSMPS3] = { + .constraints = { + .name = "db8500-vsmps3", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VRF1] = { + .constraints = { + .name = "db8500-vrf1", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSP] = { + /* dependency to u8500-vape is handled outside regulator framework */ + .constraints = { + .name = "db8500-sva-mmdsp", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_svammdsp_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_svammdsp_consumers), + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSPRET] = { + .constraints = { + /* "ret" means "retention" */ + .name = "db8500-sva-mmdsp-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAPIPE] = { + /* dependency to u8500-vape is handled outside regulator framework */ + .constraints = { + .name = "db8500-sva-pipe", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_svapipe_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_svapipe_consumers), + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSP] = { + /* dependency to u8500-vape is handled outside regulator framework */ + .constraints = { + .name = "db8500-sia-mmdsp", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_siammdsp_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_siammdsp_consumers), + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSPRET] = { + .constraints = { + .name = "db8500-sia-mmdsp-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SIAPIPE] = { + /* dependency to u8500-vape is handled outside regulator framework */ + .constraints = { + .name = "db8500-sia-pipe", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_siapipe_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_siapipe_consumers), + }, + [DB8500_REGULATOR_SWITCH_SGA] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-sga", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_sga_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_sga_consumers), + + }, + [DB8500_REGULATOR_SWITCH_B2R2_MCDE] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-b2r2-mcde", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_b2r2_mcde_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_b2r2_mcde_consumers), + }, + [DB8500_REGULATOR_SWITCH_ESRAM12] = { + /* + * esram12 is set in retention and supplied by Vsafe when Vape is off, + * no need to hold Vape + */ + .constraints = { + .name = "db8500-esram12", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_esram12_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_esram12_consumers), + }, + [DB8500_REGULATOR_SWITCH_ESRAM12RET] = { + .constraints = { + .name = "db8500-esram12-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34] = { + /* + * esram34 is set in retention and supplied by Vsafe when Vape is off, + * no need to hold Vape + */ + .constraints = { + .name = "db8500-esram34", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_esram34_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_esram34_consumers), + }, + [DB8500_REGULATOR_SWITCH_ESRAM34RET] = { + .constraints = { + .name = "db8500-esram34-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, +}; + +static struct ux500_wdt_data db8500_wdt_pdata = { + .timeout = 600, /* 10 minutes */ + .has_28_bits_resolution = true, +}; +/* + * Thermal Sensor + */ + +static struct resource db8500_thsens_resources[] = { + { + .name = "IRQ_HOTMON_LOW", + .start = IRQ_PRCMU_HOTMON_LOW, + .end = IRQ_PRCMU_HOTMON_LOW, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IRQ_HOTMON_HIGH", + .start = IRQ_PRCMU_HOTMON_HIGH, + .end = IRQ_PRCMU_HOTMON_HIGH, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct db8500_thsens_platform_data db8500_thsens_data = { + .trip_points[0] = { + .temp = 70000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[1] = { + .temp = 75000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[2] = { + .temp = 80000, + .type = THERMAL_TRIP_ACTIVE, + .cdev_name = { + [0] = "thermal-cpufreq-0", + }, + }, + .trip_points[3] = { + .temp = 85000, + .type = THERMAL_TRIP_CRITICAL, + }, + .num_trips = 4, +}; + +static const struct mfd_cell common_prcmu_devs[] = { + { + .name = "ux500_wdt", + .platform_data = &db8500_wdt_pdata, + .pdata_size = sizeof(db8500_wdt_pdata), + .id = -1, + }, +}; + +static const struct mfd_cell db8500_prcmu_devs[] = { + { + .name = "db8500-prcmu-regulators", + .of_compatible = "stericsson,db8500-prcmu-regulator", + .platform_data = &db8500_regulators, + .pdata_size = sizeof(db8500_regulators), + }, + { + .name = "cpufreq-ux500", + .of_compatible = "stericsson,cpufreq-ux500", + .platform_data = &db8500_cpufreq_table, + .pdata_size = sizeof(db8500_cpufreq_table), + }, + { + .name = "cpuidle-dbx500", + .of_compatible = "stericsson,cpuidle-dbx500", + }, + { + .name = "db8500-thermal", + .num_resources = ARRAY_SIZE(db8500_thsens_resources), + .resources = db8500_thsens_resources, + .platform_data = &db8500_thsens_data, + .pdata_size = sizeof(db8500_thsens_data), + }, +}; + +static void db8500_prcmu_update_cpufreq(void) +{ + if (prcmu_has_arm_maxopp()) { + db8500_cpufreq_table[3].frequency = 1000000; + db8500_cpufreq_table[3].driver_data = ARM_MAX_OPP; + } +} + +static int db8500_prcmu_register_ab8500(struct device *parent, + struct ab8500_platform_data *pdata) +{ + struct device_node *np; + struct resource ab8500_resource; + const struct mfd_cell ab8500_cell = { + .name = "ab8500-core", + .of_compatible = "stericsson,ab8500", + .id = AB8500_VERSION_AB8500, + .platform_data = pdata, + .pdata_size = sizeof(struct ab8500_platform_data), + .resources = &ab8500_resource, + .num_resources = 1, + }; + + if (!parent->of_node) + return -ENODEV; + + /* Look up the device node, sneak the IRQ out of it */ + for_each_child_of_node(parent->of_node, np) { + if (of_device_is_compatible(np, ab8500_cell.of_compatible)) + break; + } + if (!np) { + dev_info(parent, "could not find AB8500 node in the device tree\n"); + return -ENODEV; + } + of_irq_to_resource_table(np, &ab8500_resource, 1); + + return mfd_add_devices(parent, 0, &ab8500_cell, 1, NULL, 0, NULL); +} + +/** + * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic + * + */ +static int db8500_prcmu_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct prcmu_pdata *pdata = dev_get_platdata(&pdev->dev); + int irq = 0, err = 0; + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "prcmu"); + if (!res) { + dev_err(&pdev->dev, "no prcmu memory region provided\n"); + return -EINVAL; + } + prcmu_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!prcmu_base) { + dev_err(&pdev->dev, + "failed to ioremap prcmu register memory\n"); + return -ENOMEM; + } + init_prcm_registers(); + dbx500_fw_version_init(pdev, pdata->version_offset); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "prcmu-tcdm"); + if (!res) { + dev_err(&pdev->dev, "no prcmu tcdm region provided\n"); + return -EINVAL; + } + tcdm_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tcdm_base) { + dev_err(&pdev->dev, + "failed to ioremap prcmu-tcdm register memory\n"); + return -ENOMEM; + } + + /* Clean up the mailbox interrupts after pre-kernel code. */ + writel(ALL_MBOX_BITS, PRCM_ARM_IT1_CLR); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "no prcmu irq provided\n"); + return irq; + } + + err = request_threaded_irq(irq, prcmu_irq_handler, + prcmu_irq_thread_fn, IRQF_NO_SUSPEND, "prcmu", NULL); + if (err < 0) { + pr_err("prcmu: Failed to allocate IRQ_DB8500_PRCMU1.\n"); + return err; + } + + db8500_irq_init(np); + + prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); + + db8500_prcmu_update_cpufreq(); + + err = mfd_add_devices(&pdev->dev, 0, common_prcmu_devs, + ARRAY_SIZE(common_prcmu_devs), NULL, 0, db8500_irq_domain); + if (err) { + pr_err("prcmu: Failed to add subdevices\n"); + return err; + } + + /* TODO: Remove restriction when clk definitions are available. */ + if (!of_machine_is_compatible("st-ericsson,u8540")) { + err = mfd_add_devices(&pdev->dev, 0, db8500_prcmu_devs, + ARRAY_SIZE(db8500_prcmu_devs), NULL, 0, + db8500_irq_domain); + if (err) { + mfd_remove_devices(&pdev->dev); + pr_err("prcmu: Failed to add subdevices\n"); + return err; + } + } + + err = db8500_prcmu_register_ab8500(&pdev->dev, pdata->ab_platdata); + if (err) { + mfd_remove_devices(&pdev->dev); + pr_err("prcmu: Failed to add ab8500 subdevice\n"); + return err; + } + + pr_info("DB8500 PRCMU initialized\n"); + return err; +} +static const struct of_device_id db8500_prcmu_match[] = { + { .compatible = "stericsson,db8500-prcmu"}, + { }, +}; + +static struct platform_driver db8500_prcmu_driver = { + .driver = { + .name = "db8500-prcmu", + .of_match_table = db8500_prcmu_match, + }, + .probe = db8500_prcmu_probe, +}; + +static int __init db8500_prcmu_init(void) +{ + return platform_driver_register(&db8500_prcmu_driver); +} + +core_initcall(db8500_prcmu_init); + +MODULE_AUTHOR("Mattias Nilsson <mattias.i.nilsson@stericsson.com>"); +MODULE_DESCRIPTION("DB8500 PRCM Unit driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/dbx500-prcmu-regs.h b/kernel/drivers/mfd/dbx500-prcmu-regs.h new file mode 100644 index 000000000..7cc32a8ff --- /dev/null +++ b/kernel/drivers/mfd/dbx500-prcmu-regs.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> + * Author: Sundar Iyer <sundar.iyer@stericsson.com> + * + * License Terms: GNU General Public License v2 + * + * PRCM Unit registers + */ + +#ifndef __DB8500_PRCMU_REGS_H +#define __DB8500_PRCMU_REGS_H + +#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#define PRCM_ACLK_MGT (0x004) +#define PRCM_SVAMMCSPCLK_MGT (0x008) +#define PRCM_SIAMMDSPCLK_MGT (0x00C) +#define PRCM_SGACLK_MGT (0x014) +#define PRCM_UARTCLK_MGT (0x018) +#define PRCM_MSP02CLK_MGT (0x01C) +#define PRCM_I2CCLK_MGT (0x020) +#define PRCM_SDMMCCLK_MGT (0x024) +#define PRCM_SLIMCLK_MGT (0x028) +#define PRCM_PER1CLK_MGT (0x02C) +#define PRCM_PER2CLK_MGT (0x030) +#define PRCM_PER3CLK_MGT (0x034) +#define PRCM_PER5CLK_MGT (0x038) +#define PRCM_PER6CLK_MGT (0x03C) +#define PRCM_PER7CLK_MGT (0x040) +#define PRCM_LCDCLK_MGT (0x044) +#define PRCM_BMLCLK_MGT (0x04C) +#define PRCM_HSITXCLK_MGT (0x050) +#define PRCM_HSIRXCLK_MGT (0x054) +#define PRCM_HDMICLK_MGT (0x058) +#define PRCM_APEATCLK_MGT (0x05C) +#define PRCM_APETRACECLK_MGT (0x060) +#define PRCM_MCDECLK_MGT (0x064) +#define PRCM_IPI2CCLK_MGT (0x068) +#define PRCM_DSIALTCLK_MGT (0x06C) +#define PRCM_DMACLK_MGT (0x074) +#define PRCM_B2R2CLK_MGT (0x078) +#define PRCM_TVCLK_MGT (0x07C) +#define PRCM_UNIPROCLK_MGT (0x278) +#define PRCM_SSPCLK_MGT (0x280) +#define PRCM_RNGCLK_MGT (0x284) +#define PRCM_UICCCLK_MGT (0x27C) +#define PRCM_MSP1CLK_MGT (0x288) + +#define PRCM_ARM_PLLDIVPS (prcmu_base + 0x118) +#define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE 0x3f +#define PRCM_ARM_PLLDIVPS_MAX_MASK 0xf + +#define PRCM_PLLARM_LOCKP (prcmu_base + 0x0a8) +#define PRCM_PLLARM_LOCKP_PRCM_PLLARM_LOCKP3 0x2 + +#define PRCM_ARM_CHGCLKREQ (prcmu_base + 0x114) +#define PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ BIT(0) +#define PRCM_ARM_CHGCLKREQ_PRCM_ARM_DIVSEL BIT(16) + +#define PRCM_PLLARM_ENABLE (prcmu_base + 0x98) +#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_ENABLE 0x1 +#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_COUNTON 0x100 + +#define PRCM_ARMCLKFIX_MGT (prcmu_base + 0x0) +#define PRCM_A9PL_FORCE_CLKEN (prcmu_base + 0x19C) +#define PRCM_A9_RESETN_CLR (prcmu_base + 0x1f4) +#define PRCM_A9_RESETN_SET (prcmu_base + 0x1f0) +#define PRCM_ARM_LS_CLAMP (prcmu_base + 0x30c) +#define PRCM_SRAM_A9 (prcmu_base + 0x308) + +#define PRCM_A9PL_FORCE_CLKEN_PRCM_A9PL_FORCE_CLKEN BIT(0) +#define PRCM_A9PL_FORCE_CLKEN_PRCM_A9AXI_FORCE_CLKEN BIT(1) + +/* CPU mailbox registers */ +#define PRCM_MBOX_CPU_VAL (prcmu_base + 0x0fc) +#define PRCM_MBOX_CPU_SET (prcmu_base + 0x100) +#define PRCM_MBOX_CPU_CLR (prcmu_base + 0x104) + +#define PRCM_HOSTACCESS_REQ (prcmu_base + 0x334) +#define PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ 0x1 +#define PRCM_HOSTACCESS_REQ_WAKE_REQ BIT(16) +#define ARM_WAKEUP_MODEM 0x1 + +#define PRCM_ARM_IT1_CLR (prcmu_base + 0x48C) +#define PRCM_ARM_IT1_VAL (prcmu_base + 0x494) +#define PRCM_HOLD_EVT (prcmu_base + 0x174) + +#define PRCM_MOD_AWAKE_STATUS (prcmu_base + 0x4A0) +#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE BIT(0) +#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE BIT(1) +#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_VMODEM_OFF_ISO BIT(2) + +#define PRCM_ITSTATUS0 (prcmu_base + 0x148) +#define PRCM_ITSTATUS1 (prcmu_base + 0x150) +#define PRCM_ITSTATUS2 (prcmu_base + 0x158) +#define PRCM_ITSTATUS3 (prcmu_base + 0x160) +#define PRCM_ITSTATUS4 (prcmu_base + 0x168) +#define PRCM_ITSTATUS5 (prcmu_base + 0x484) +#define PRCM_ITCLEAR5 (prcmu_base + 0x488) +#define PRCM_ARMIT_MASKXP70_IT (prcmu_base + 0x1018) + +/* System reset register */ +#define PRCM_APE_SOFTRST (prcmu_base + 0x228) + +/* Level shifter and clamp control registers */ +#define PRCM_MMIP_LS_CLAMP_SET (prcmu_base + 0x420) +#define PRCM_MMIP_LS_CLAMP_CLR (prcmu_base + 0x424) + +#define PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP BIT(11) +#define PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI BIT(22) + +/* PRCMU clock/PLL/reset registers */ +#define PRCM_PLLSOC0_FREQ (prcmu_base + 0x080) +#define PRCM_PLLSOC1_FREQ (prcmu_base + 0x084) +#define PRCM_PLLARM_FREQ (prcmu_base + 0x088) +#define PRCM_PLLDDR_FREQ (prcmu_base + 0x08C) +#define PRCM_PLL_FREQ_D_SHIFT 0 +#define PRCM_PLL_FREQ_D_MASK BITS(0, 7) +#define PRCM_PLL_FREQ_N_SHIFT 8 +#define PRCM_PLL_FREQ_N_MASK BITS(8, 13) +#define PRCM_PLL_FREQ_R_SHIFT 16 +#define PRCM_PLL_FREQ_R_MASK BITS(16, 18) +#define PRCM_PLL_FREQ_SELDIV2 BIT(24) +#define PRCM_PLL_FREQ_DIV2EN BIT(25) + +#define PRCM_PLLDSI_FREQ (prcmu_base + 0x500) +#define PRCM_PLLDSI_ENABLE (prcmu_base + 0x504) +#define PRCM_PLLDSI_LOCKP (prcmu_base + 0x508) +#define PRCM_DSI_PLLOUT_SEL (prcmu_base + 0x530) +#define PRCM_DSITVCLK_DIV (prcmu_base + 0x52C) +#define PRCM_PLLDSI_LOCKP (prcmu_base + 0x508) +#define PRCM_APE_RESETN_SET (prcmu_base + 0x1E4) +#define PRCM_APE_RESETN_CLR (prcmu_base + 0x1E8) + +#define PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE BIT(0) + +#define PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP10 BIT(0) +#define PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP3 BIT(1) + +#define PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_SHIFT 0 +#define PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_MASK BITS(0, 2) +#define PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_SHIFT 8 +#define PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_MASK BITS(8, 10) + +#define PRCM_DSI_PLLOUT_SEL_OFF 0 +#define PRCM_DSI_PLLOUT_SEL_PHI 1 +#define PRCM_DSI_PLLOUT_SEL_PHI_2 2 +#define PRCM_DSI_PLLOUT_SEL_PHI_4 3 + +#define PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_SHIFT 0 +#define PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_MASK BITS(0, 7) +#define PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_SHIFT 8 +#define PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_MASK BITS(8, 15) +#define PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_SHIFT 16 +#define PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_MASK BITS(16, 23) +#define PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_EN BIT(24) +#define PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_EN BIT(25) +#define PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_EN BIT(26) + +#define PRCM_APE_RESETN_DSIPLL_RESETN BIT(14) + +#define PRCM_CLKOCR (prcmu_base + 0x1CC) +#define PRCM_CLKOCR_CLKOUT0_REF_CLK (1 << 0) +#define PRCM_CLKOCR_CLKOUT0_MASK BITS(0, 13) +#define PRCM_CLKOCR_CLKOUT1_REF_CLK (1 << 16) +#define PRCM_CLKOCR_CLKOUT1_MASK BITS(16, 29) + +/* ePOD and memory power signal control registers */ +#define PRCM_EPOD_C_SET (prcmu_base + 0x410) +#define PRCM_SRAM_LS_SLEEP (prcmu_base + 0x304) + +/* Debug power control unit registers */ +#define PRCM_POWER_STATE_SET (prcmu_base + 0x254) + +/* Miscellaneous unit registers */ +#define PRCM_DSI_SW_RESET (prcmu_base + 0x324) +#define PRCM_GPIOCR (prcmu_base + 0x138) +#define PRCM_GPIOCR_DBG_STM_MOD_CMD1 0x800 +#define PRCM_GPIOCR_DBG_UARTMOD_CMD0 0x1 + +/* PRCMU HW semaphore */ +#define PRCM_SEM (prcmu_base + 0x400) +#define PRCM_SEM_PRCM_SEM BIT(0) + +#define PRCM_TCR (prcmu_base + 0x1C8) +#define PRCM_TCR_TENSEL_MASK BITS(0, 7) +#define PRCM_TCR_STOP_TIMERS BIT(16) +#define PRCM_TCR_DOZE_MODE BIT(17) + +#define PRCM_CLKOCR_CLKODIV0_SHIFT 0 +#define PRCM_CLKOCR_CLKODIV0_MASK BITS(0, 5) +#define PRCM_CLKOCR_CLKOSEL0_SHIFT 6 +#define PRCM_CLKOCR_CLKOSEL0_MASK BITS(6, 8) +#define PRCM_CLKOCR_CLKODIV1_SHIFT 16 +#define PRCM_CLKOCR_CLKODIV1_MASK BITS(16, 21) +#define PRCM_CLKOCR_CLKOSEL1_SHIFT 22 +#define PRCM_CLKOCR_CLKOSEL1_MASK BITS(22, 24) +#define PRCM_CLKOCR_CLK1TYPE BIT(28) + +#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) +#define PRCM_CLK_MGT_CLKPLLSW_SOC0 BIT(5) +#define PRCM_CLK_MGT_CLKPLLSW_SOC1 BIT(6) +#define PRCM_CLK_MGT_CLKPLLSW_DDR BIT(7) +#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) +#define PRCM_CLK_MGT_CLKEN BIT(8) +#define PRCM_CLK_MGT_CLK38 BIT(9) +#define PRCM_CLK_MGT_CLK38DIV BIT(11) +#define PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN BIT(12) + +/* GPIOCR register */ +#define PRCM_GPIOCR_SPI2_SELECT BIT(23) + +#define PRCM_DDR_SUBSYS_APE_MINBW (prcmu_base + 0x438) +#define PRCM_CGATING_BYPASS (prcmu_base + 0x134) +#define PRCM_CGATING_BYPASS_ICN2 BIT(6) + +/* Miscellaneous unit registers */ +#define PRCM_RESOUTN_SET (prcmu_base + 0x214) +#define PRCM_RESOUTN_CLR (prcmu_base + 0x218) + +/* System reset register */ +#define PRCM_APE_SOFTRST (prcmu_base + 0x228) + +#endif /* __DB8500_PRCMU_REGS_H */ diff --git a/kernel/drivers/mfd/dln2.c b/kernel/drivers/mfd/dln2.c new file mode 100644 index 000000000..704e189ca --- /dev/null +++ b/kernel/drivers/mfd/dln2.c @@ -0,0 +1,832 @@ +/* + * Driver for the Diolan DLN-2 USB adapter + * + * Copyright (c) 2014 Intel Corporation + * + * Derived from: + * i2c-diolan-u2c.c + * Copyright (c) 2010-2011 Ericsson AB + * + * 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, version 2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/dln2.h> +#include <linux/rculist.h> + +struct dln2_header { + __le16 size; + __le16 id; + __le16 echo; + __le16 handle; +}; + +struct dln2_response { + struct dln2_header hdr; + __le16 result; +}; + +#define DLN2_GENERIC_MODULE_ID 0x00 +#define DLN2_GENERIC_CMD(cmd) DLN2_CMD(cmd, DLN2_GENERIC_MODULE_ID) +#define CMD_GET_DEVICE_VER DLN2_GENERIC_CMD(0x30) +#define CMD_GET_DEVICE_SN DLN2_GENERIC_CMD(0x31) + +#define DLN2_HW_ID 0x200 +#define DLN2_USB_TIMEOUT 200 /* in ms */ +#define DLN2_MAX_RX_SLOTS 16 +#define DLN2_MAX_URBS 16 +#define DLN2_RX_BUF_SIZE 512 + +enum dln2_handle { + DLN2_HANDLE_EVENT = 0, /* don't change, hardware defined */ + DLN2_HANDLE_CTRL, + DLN2_HANDLE_GPIO, + DLN2_HANDLE_I2C, + DLN2_HANDLE_SPI, + DLN2_HANDLES +}; + +/* + * Receive context used between the receive demultiplexer and the transfer + * routine. While sending a request the transfer routine will look for a free + * receive context and use it to wait for a response and to receive the URB and + * thus the response data. + */ +struct dln2_rx_context { + /* completion used to wait for a response */ + struct completion done; + + /* if non-NULL the URB contains the response */ + struct urb *urb; + + /* if true then this context is used to wait for a response */ + bool in_use; +}; + +/* + * Receive contexts for a particular DLN2 module (i2c, gpio, etc.). We use the + * handle header field to identify the module in dln2_dev.mod_rx_slots and then + * the echo header field to index the slots field and find the receive context + * for a particular request. + */ +struct dln2_mod_rx_slots { + /* RX slots bitmap */ + DECLARE_BITMAP(bmap, DLN2_MAX_RX_SLOTS); + + /* used to wait for a free RX slot */ + wait_queue_head_t wq; + + /* used to wait for an RX operation to complete */ + struct dln2_rx_context slots[DLN2_MAX_RX_SLOTS]; + + /* avoid races between alloc/free_rx_slot and dln2_rx_transfer */ + spinlock_t lock; +}; + +struct dln2_dev { + struct usb_device *usb_dev; + struct usb_interface *interface; + u8 ep_in; + u8 ep_out; + + struct urb *rx_urb[DLN2_MAX_URBS]; + void *rx_buf[DLN2_MAX_URBS]; + + struct dln2_mod_rx_slots mod_rx_slots[DLN2_HANDLES]; + + struct list_head event_cb_list; + spinlock_t event_cb_lock; + + bool disconnect; + int active_transfers; + wait_queue_head_t disconnect_wq; + spinlock_t disconnect_lock; +}; + +struct dln2_event_cb_entry { + struct list_head list; + u16 id; + struct platform_device *pdev; + dln2_event_cb_t callback; +}; + +int dln2_register_event_cb(struct platform_device *pdev, u16 id, + dln2_event_cb_t event_cb) +{ + struct dln2_dev *dln2 = dev_get_drvdata(pdev->dev.parent); + struct dln2_event_cb_entry *i, *entry; + unsigned long flags; + int ret = 0; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->id = id; + entry->callback = event_cb; + entry->pdev = pdev; + + spin_lock_irqsave(&dln2->event_cb_lock, flags); + + list_for_each_entry(i, &dln2->event_cb_list, list) { + if (i->id == id) { + ret = -EBUSY; + break; + } + } + + if (!ret) + list_add_rcu(&entry->list, &dln2->event_cb_list); + + spin_unlock_irqrestore(&dln2->event_cb_lock, flags); + + if (ret) + kfree(entry); + + return ret; +} +EXPORT_SYMBOL(dln2_register_event_cb); + +void dln2_unregister_event_cb(struct platform_device *pdev, u16 id) +{ + struct dln2_dev *dln2 = dev_get_drvdata(pdev->dev.parent); + struct dln2_event_cb_entry *i; + unsigned long flags; + bool found = false; + + spin_lock_irqsave(&dln2->event_cb_lock, flags); + + list_for_each_entry(i, &dln2->event_cb_list, list) { + if (i->id == id) { + list_del_rcu(&i->list); + found = true; + break; + } + } + + spin_unlock_irqrestore(&dln2->event_cb_lock, flags); + + if (found) { + synchronize_rcu(); + kfree(i); + } +} +EXPORT_SYMBOL(dln2_unregister_event_cb); + +/* + * Returns true if a valid transfer slot is found. In this case the URB must not + * be resubmitted immediately in dln2_rx as we need the data when dln2_transfer + * is woke up. It will be resubmitted there. + */ +static bool dln2_transfer_complete(struct dln2_dev *dln2, struct urb *urb, + u16 handle, u16 rx_slot) +{ + struct device *dev = &dln2->interface->dev; + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle]; + struct dln2_rx_context *rxc; + bool valid_slot = false; + + if (rx_slot >= DLN2_MAX_RX_SLOTS) + goto out; + + rxc = &rxs->slots[rx_slot]; + + /* + * No need to disable interrupts as this lock is not taken in interrupt + * context elsewhere in this driver. This function (or its callers) are + * also not exported to other modules. + */ + spin_lock(&rxs->lock); + if (rxc->in_use && !rxc->urb) { + rxc->urb = urb; + complete(&rxc->done); + valid_slot = true; + } + spin_unlock(&rxs->lock); + +out: + if (!valid_slot) + dev_warn(dev, "bad/late response %d/%d\n", handle, rx_slot); + + return valid_slot; +} + +static void dln2_run_event_callbacks(struct dln2_dev *dln2, u16 id, u16 echo, + void *data, int len) +{ + struct dln2_event_cb_entry *i; + + rcu_read_lock(); + + list_for_each_entry_rcu(i, &dln2->event_cb_list, list) { + if (i->id == id) { + i->callback(i->pdev, echo, data, len); + break; + } + } + + rcu_read_unlock(); +} + +static void dln2_rx(struct urb *urb) +{ + struct dln2_dev *dln2 = urb->context; + struct dln2_header *hdr = urb->transfer_buffer; + struct device *dev = &dln2->interface->dev; + u16 id, echo, handle, size; + u8 *data; + int len; + int err; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EPIPE: + /* this urb is terminated, clean up */ + dev_dbg(dev, "urb shutting down with status %d\n", urb->status); + return; + default: + dev_dbg(dev, "nonzero urb status received %d\n", urb->status); + goto out; + } + + if (urb->actual_length < sizeof(struct dln2_header)) { + dev_err(dev, "short response: %d\n", urb->actual_length); + goto out; + } + + handle = le16_to_cpu(hdr->handle); + id = le16_to_cpu(hdr->id); + echo = le16_to_cpu(hdr->echo); + size = le16_to_cpu(hdr->size); + + if (size != urb->actual_length) { + dev_err(dev, "size mismatch: handle %x cmd %x echo %x size %d actual %d\n", + handle, id, echo, size, urb->actual_length); + goto out; + } + + if (handle >= DLN2_HANDLES) { + dev_warn(dev, "invalid handle %d\n", handle); + goto out; + } + + data = urb->transfer_buffer + sizeof(struct dln2_header); + len = urb->actual_length - sizeof(struct dln2_header); + + if (handle == DLN2_HANDLE_EVENT) { + dln2_run_event_callbacks(dln2, id, echo, data, len); + } else { + /* URB will be re-submitted in _dln2_transfer (free_rx_slot) */ + if (dln2_transfer_complete(dln2, urb, handle, echo)) + return; + } + +out: + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) + dev_err(dev, "failed to resubmit RX URB: %d\n", err); +} + +static void *dln2_prep_buf(u16 handle, u16 cmd, u16 echo, const void *obuf, + int *obuf_len, gfp_t gfp) +{ + int len; + void *buf; + struct dln2_header *hdr; + + len = *obuf_len + sizeof(*hdr); + buf = kmalloc(len, gfp); + if (!buf) + return NULL; + + hdr = (struct dln2_header *)buf; + hdr->id = cpu_to_le16(cmd); + hdr->size = cpu_to_le16(len); + hdr->echo = cpu_to_le16(echo); + hdr->handle = cpu_to_le16(handle); + + memcpy(buf + sizeof(*hdr), obuf, *obuf_len); + + *obuf_len = len; + + return buf; +} + +static int dln2_send_wait(struct dln2_dev *dln2, u16 handle, u16 cmd, u16 echo, + const void *obuf, int obuf_len) +{ + int ret = 0; + int len = obuf_len; + void *buf; + int actual; + + buf = dln2_prep_buf(handle, cmd, echo, obuf, &len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usb_bulk_msg(dln2->usb_dev, + usb_sndbulkpipe(dln2->usb_dev, dln2->ep_out), + buf, len, &actual, DLN2_USB_TIMEOUT); + + kfree(buf); + + return ret; +} + +static bool find_free_slot(struct dln2_dev *dln2, u16 handle, int *slot) +{ + struct dln2_mod_rx_slots *rxs; + unsigned long flags; + + if (dln2->disconnect) { + *slot = -ENODEV; + return true; + } + + rxs = &dln2->mod_rx_slots[handle]; + + spin_lock_irqsave(&rxs->lock, flags); + + *slot = find_first_zero_bit(rxs->bmap, DLN2_MAX_RX_SLOTS); + + if (*slot < DLN2_MAX_RX_SLOTS) { + struct dln2_rx_context *rxc = &rxs->slots[*slot]; + + set_bit(*slot, rxs->bmap); + rxc->in_use = true; + } + + spin_unlock_irqrestore(&rxs->lock, flags); + + return *slot < DLN2_MAX_RX_SLOTS; +} + +static int alloc_rx_slot(struct dln2_dev *dln2, u16 handle) +{ + int ret; + int slot; + + /* + * No need to timeout here, the wait is bounded by the timeout in + * _dln2_transfer. + */ + ret = wait_event_interruptible(dln2->mod_rx_slots[handle].wq, + find_free_slot(dln2, handle, &slot)); + if (ret < 0) + return ret; + + return slot; +} + +static void free_rx_slot(struct dln2_dev *dln2, u16 handle, int slot) +{ + struct dln2_mod_rx_slots *rxs; + struct urb *urb = NULL; + unsigned long flags; + struct dln2_rx_context *rxc; + + rxs = &dln2->mod_rx_slots[handle]; + + spin_lock_irqsave(&rxs->lock, flags); + + clear_bit(slot, rxs->bmap); + + rxc = &rxs->slots[slot]; + rxc->in_use = false; + urb = rxc->urb; + rxc->urb = NULL; + reinit_completion(&rxc->done); + + spin_unlock_irqrestore(&rxs->lock, flags); + + if (urb) { + int err; + struct device *dev = &dln2->interface->dev; + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) + dev_err(dev, "failed to resubmit RX URB: %d\n", err); + } + + wake_up_interruptible(&rxs->wq); +} + +static int _dln2_transfer(struct dln2_dev *dln2, u16 handle, u16 cmd, + const void *obuf, unsigned obuf_len, + void *ibuf, unsigned *ibuf_len) +{ + int ret = 0; + int rx_slot; + struct dln2_response *rsp; + struct dln2_rx_context *rxc; + struct device *dev = &dln2->interface->dev; + const unsigned long timeout = msecs_to_jiffies(DLN2_USB_TIMEOUT); + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle]; + int size; + + spin_lock(&dln2->disconnect_lock); + if (!dln2->disconnect) + dln2->active_transfers++; + else + ret = -ENODEV; + spin_unlock(&dln2->disconnect_lock); + + if (ret) + return ret; + + rx_slot = alloc_rx_slot(dln2, handle); + if (rx_slot < 0) { + ret = rx_slot; + goto out_decr; + } + + ret = dln2_send_wait(dln2, handle, cmd, rx_slot, obuf, obuf_len); + if (ret < 0) { + dev_err(dev, "USB write failed: %d\n", ret); + goto out_free_rx_slot; + } + + rxc = &rxs->slots[rx_slot]; + + ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout); + if (ret <= 0) { + if (!ret) + ret = -ETIMEDOUT; + goto out_free_rx_slot; + } else { + ret = 0; + } + + if (dln2->disconnect) { + ret = -ENODEV; + goto out_free_rx_slot; + } + + /* if we got here we know that the response header has been checked */ + rsp = rxc->urb->transfer_buffer; + size = le16_to_cpu(rsp->hdr.size); + + if (size < sizeof(*rsp)) { + ret = -EPROTO; + goto out_free_rx_slot; + } + + if (le16_to_cpu(rsp->result) > 0x80) { + dev_dbg(dev, "%d received response with error %d\n", + handle, le16_to_cpu(rsp->result)); + ret = -EREMOTEIO; + goto out_free_rx_slot; + } + + if (!ibuf) + goto out_free_rx_slot; + + if (*ibuf_len > size - sizeof(*rsp)) + *ibuf_len = size - sizeof(*rsp); + + memcpy(ibuf, rsp + 1, *ibuf_len); + +out_free_rx_slot: + free_rx_slot(dln2, handle, rx_slot); +out_decr: + spin_lock(&dln2->disconnect_lock); + dln2->active_transfers--; + spin_unlock(&dln2->disconnect_lock); + if (dln2->disconnect) + wake_up(&dln2->disconnect_wq); + + return ret; +} + +int dln2_transfer(struct platform_device *pdev, u16 cmd, + const void *obuf, unsigned obuf_len, + void *ibuf, unsigned *ibuf_len) +{ + struct dln2_platform_data *dln2_pdata; + struct dln2_dev *dln2; + u16 handle; + + dln2 = dev_get_drvdata(pdev->dev.parent); + dln2_pdata = dev_get_platdata(&pdev->dev); + handle = dln2_pdata->handle; + + return _dln2_transfer(dln2, handle, cmd, obuf, obuf_len, ibuf, + ibuf_len); +} +EXPORT_SYMBOL(dln2_transfer); + +static int dln2_check_hw(struct dln2_dev *dln2) +{ + int ret; + __le32 hw_type; + int len = sizeof(hw_type); + + ret = _dln2_transfer(dln2, DLN2_HANDLE_CTRL, CMD_GET_DEVICE_VER, + NULL, 0, &hw_type, &len); + if (ret < 0) + return ret; + if (len < sizeof(hw_type)) + return -EREMOTEIO; + + if (le32_to_cpu(hw_type) != DLN2_HW_ID) { + dev_err(&dln2->interface->dev, "Device ID 0x%x not supported\n", + le32_to_cpu(hw_type)); + return -ENODEV; + } + + return 0; +} + +static int dln2_print_serialno(struct dln2_dev *dln2) +{ + int ret; + __le32 serial_no; + int len = sizeof(serial_no); + struct device *dev = &dln2->interface->dev; + + ret = _dln2_transfer(dln2, DLN2_HANDLE_CTRL, CMD_GET_DEVICE_SN, NULL, 0, + &serial_no, &len); + if (ret < 0) + return ret; + if (len < sizeof(serial_no)) + return -EREMOTEIO; + + dev_info(dev, "Diolan DLN2 serial %u\n", le32_to_cpu(serial_no)); + + return 0; +} + +static int dln2_hw_init(struct dln2_dev *dln2) +{ + int ret; + + ret = dln2_check_hw(dln2); + if (ret < 0) + return ret; + + return dln2_print_serialno(dln2); +} + +static void dln2_free_rx_urbs(struct dln2_dev *dln2) +{ + int i; + + for (i = 0; i < DLN2_MAX_URBS; i++) { + usb_free_urb(dln2->rx_urb[i]); + kfree(dln2->rx_buf[i]); + } +} + +static void dln2_stop_rx_urbs(struct dln2_dev *dln2) +{ + int i; + + for (i = 0; i < DLN2_MAX_URBS; i++) + usb_kill_urb(dln2->rx_urb[i]); +} + +static void dln2_free(struct dln2_dev *dln2) +{ + dln2_free_rx_urbs(dln2); + usb_put_dev(dln2->usb_dev); + kfree(dln2); +} + +static int dln2_setup_rx_urbs(struct dln2_dev *dln2, + struct usb_host_interface *hostif) +{ + int i; + const int rx_max_size = DLN2_RX_BUF_SIZE; + + for (i = 0; i < DLN2_MAX_URBS; i++) { + dln2->rx_buf[i] = kmalloc(rx_max_size, GFP_KERNEL); + if (!dln2->rx_buf[i]) + return -ENOMEM; + + dln2->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!dln2->rx_urb[i]) + return -ENOMEM; + + usb_fill_bulk_urb(dln2->rx_urb[i], dln2->usb_dev, + usb_rcvbulkpipe(dln2->usb_dev, dln2->ep_in), + dln2->rx_buf[i], rx_max_size, dln2_rx, dln2); + } + + return 0; +} + +static int dln2_start_rx_urbs(struct dln2_dev *dln2, gfp_t gfp) +{ + struct device *dev = &dln2->interface->dev; + int ret; + int i; + + for (i = 0; i < DLN2_MAX_URBS; i++) { + ret = usb_submit_urb(dln2->rx_urb[i], gfp); + if (ret < 0) { + dev_err(dev, "failed to submit RX URB: %d\n", ret); + return ret; + } + } + + return 0; +} + +static struct dln2_platform_data dln2_pdata_gpio = { + .handle = DLN2_HANDLE_GPIO, +}; + +/* Only one I2C port seems to be supported on current hardware */ +static struct dln2_platform_data dln2_pdata_i2c = { + .handle = DLN2_HANDLE_I2C, + .port = 0, +}; + +/* Only one SPI port supported */ +static struct dln2_platform_data dln2_pdata_spi = { + .handle = DLN2_HANDLE_SPI, + .port = 0, +}; + +static const struct mfd_cell dln2_devs[] = { + { + .name = "dln2-gpio", + .platform_data = &dln2_pdata_gpio, + .pdata_size = sizeof(struct dln2_platform_data), + }, + { + .name = "dln2-i2c", + .platform_data = &dln2_pdata_i2c, + .pdata_size = sizeof(struct dln2_platform_data), + }, + { + .name = "dln2-spi", + .platform_data = &dln2_pdata_spi, + .pdata_size = sizeof(struct dln2_platform_data), + }, +}; + +static void dln2_stop(struct dln2_dev *dln2) +{ + int i, j; + + /* don't allow starting new transfers */ + spin_lock(&dln2->disconnect_lock); + dln2->disconnect = true; + spin_unlock(&dln2->disconnect_lock); + + /* cancel in progress transfers */ + for (i = 0; i < DLN2_HANDLES; i++) { + struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[i]; + unsigned long flags; + + spin_lock_irqsave(&rxs->lock, flags); + + /* cancel all response waiters */ + for (j = 0; j < DLN2_MAX_RX_SLOTS; j++) { + struct dln2_rx_context *rxc = &rxs->slots[j]; + + if (rxc->in_use) + complete(&rxc->done); + } + + spin_unlock_irqrestore(&rxs->lock, flags); + } + + /* wait for transfers to end */ + wait_event(dln2->disconnect_wq, !dln2->active_transfers); + + dln2_stop_rx_urbs(dln2); +} + +static void dln2_disconnect(struct usb_interface *interface) +{ + struct dln2_dev *dln2 = usb_get_intfdata(interface); + + dln2_stop(dln2); + + mfd_remove_devices(&interface->dev); + + dln2_free(dln2); +} + +static int dln2_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + struct usb_host_interface *hostif = interface->cur_altsetting; + struct device *dev = &interface->dev; + struct dln2_dev *dln2; + int ret; + int i, j; + + if (hostif->desc.bInterfaceNumber != 0 || + hostif->desc.bNumEndpoints < 2) + return -ENODEV; + + dln2 = kzalloc(sizeof(*dln2), GFP_KERNEL); + if (!dln2) + return -ENOMEM; + + dln2->ep_out = hostif->endpoint[0].desc.bEndpointAddress; + dln2->ep_in = hostif->endpoint[1].desc.bEndpointAddress; + dln2->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + dln2->interface = interface; + usb_set_intfdata(interface, dln2); + init_waitqueue_head(&dln2->disconnect_wq); + + for (i = 0; i < DLN2_HANDLES; i++) { + init_waitqueue_head(&dln2->mod_rx_slots[i].wq); + spin_lock_init(&dln2->mod_rx_slots[i].lock); + for (j = 0; j < DLN2_MAX_RX_SLOTS; j++) + init_completion(&dln2->mod_rx_slots[i].slots[j].done); + } + + spin_lock_init(&dln2->event_cb_lock); + spin_lock_init(&dln2->disconnect_lock); + INIT_LIST_HEAD(&dln2->event_cb_list); + + ret = dln2_setup_rx_urbs(dln2, hostif); + if (ret) + goto out_free; + + ret = dln2_start_rx_urbs(dln2, GFP_KERNEL); + if (ret) + goto out_stop_rx; + + ret = dln2_hw_init(dln2); + if (ret < 0) { + dev_err(dev, "failed to initialize hardware\n"); + goto out_stop_rx; + } + + ret = mfd_add_hotplug_devices(dev, dln2_devs, ARRAY_SIZE(dln2_devs)); + if (ret != 0) { + dev_err(dev, "failed to add mfd devices to core\n"); + goto out_stop_rx; + } + + return 0; + +out_stop_rx: + dln2_stop_rx_urbs(dln2); + +out_free: + dln2_free(dln2); + + return ret; +} + +static int dln2_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct dln2_dev *dln2 = usb_get_intfdata(iface); + + dln2_stop(dln2); + + return 0; +} + +static int dln2_resume(struct usb_interface *iface) +{ + struct dln2_dev *dln2 = usb_get_intfdata(iface); + + dln2->disconnect = false; + + return dln2_start_rx_urbs(dln2, GFP_NOIO); +} + +static const struct usb_device_id dln2_table[] = { + { USB_DEVICE(0xa257, 0x2013) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, dln2_table); + +static struct usb_driver dln2_driver = { + .name = "dln2", + .probe = dln2_probe, + .disconnect = dln2_disconnect, + .id_table = dln2_table, + .suspend = dln2_suspend, + .resume = dln2_resume, +}; + +module_usb_driver(dln2_driver); + +MODULE_AUTHOR("Octavian Purdila <octavian.purdila@intel.com>"); +MODULE_DESCRIPTION("Core driver for the Diolan DLN2 interface adapter"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/dm355evm_msp.c b/kernel/drivers/mfd/dm355evm_msp.c new file mode 100644 index 000000000..4c826f78a --- /dev/null +++ b/kernel/drivers/mfd/dm355evm_msp.c @@ -0,0 +1,437 @@ +/* + * dm355evm_msp.c - driver for MSP430 firmware on DM355EVM board + * + * Copyright (C) 2008 David Brownell + * + * 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/init.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/leds.h> +#include <linux/i2c.h> +#include <linux/i2c/dm355evm_msp.h> + + +/* + * The DM355 is a DaVinci chip with video support but no C64+ DSP. Its + * EVM board has an MSP430 programmed with firmware for various board + * support functions. This driver exposes some of them directly, and + * supports other drivers (e.g. RTC, input) for more complex access. + * + * Because this firmware is entirely board-specific, this file embeds + * knowledge that would be passed as platform_data in a generic driver. + * + * This driver was tested with firmware revision A4. + */ + +#if defined(CONFIG_INPUT_DM355EVM) || defined(CONFIG_INPUT_DM355EVM_MODULE) +#define msp_has_keyboard() true +#else +#define msp_has_keyboard() false +#endif + +#if defined(CONFIG_LEDS_GPIO) || defined(CONFIG_LEDS_GPIO_MODULE) +#define msp_has_leds() true +#else +#define msp_has_leds() false +#endif + +#if defined(CONFIG_RTC_DRV_DM355EVM) || defined(CONFIG_RTC_DRV_DM355EVM_MODULE) +#define msp_has_rtc() true +#else +#define msp_has_rtc() false +#endif + +#if defined(CONFIG_VIDEO_TVP514X) || defined(CONFIG_VIDEO_TVP514X_MODULE) +#define msp_has_tvp() true +#else +#define msp_has_tvp() false +#endif + + +/*----------------------------------------------------------------------*/ + +/* REVISIT for paranoia's sake, retry reads/writes on error */ + +static struct i2c_client *msp430; + +/** + * dm355evm_msp_write - Writes a register in dm355evm_msp + * @value: the value to be written + * @reg: register address + * + * Returns result of operation - 0 is success, else negative errno + */ +int dm355evm_msp_write(u8 value, u8 reg) +{ + return i2c_smbus_write_byte_data(msp430, reg, value); +} +EXPORT_SYMBOL(dm355evm_msp_write); + +/** + * dm355evm_msp_read - Reads a register from dm355evm_msp + * @reg: register address + * + * Returns result of operation - value, or negative errno + */ +int dm355evm_msp_read(u8 reg) +{ + return i2c_smbus_read_byte_data(msp430, reg); +} +EXPORT_SYMBOL(dm355evm_msp_read); + +/*----------------------------------------------------------------------*/ + +/* + * Many of the msp430 pins are just used as fixed-direction GPIOs. + * We could export a few more of them this way, if we wanted. + */ +#define MSP_GPIO(bit, reg) ((DM355EVM_MSP_ ## reg) << 3 | (bit)) + +static const u8 msp_gpios[] = { + /* eight leds */ + MSP_GPIO(0, LED), MSP_GPIO(1, LED), + MSP_GPIO(2, LED), MSP_GPIO(3, LED), + MSP_GPIO(4, LED), MSP_GPIO(5, LED), + MSP_GPIO(6, LED), MSP_GPIO(7, LED), + /* SW6 and the NTSC/nPAL jumper */ + MSP_GPIO(0, SWITCH1), MSP_GPIO(1, SWITCH1), + MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1), + MSP_GPIO(4, SWITCH1), + /* switches on MMC/SD sockets */ + /* + * Note: EVMDM355_ECP_VA4.pdf suggests that Bit 2 and 4 should be + * checked for card detection. However on the EVM bit 1 and 3 gives + * this status, for 0 and 1 instance respectively. The pdf also + * suggests that Bit 1 and 3 should be checked for write protection. + * However on the EVM bit 2 and 4 gives this status,for 0 and 1 + * instance respectively. + */ + MSP_GPIO(2, SDMMC), MSP_GPIO(1, SDMMC), /* mmc0 WP, nCD */ + MSP_GPIO(4, SDMMC), MSP_GPIO(3, SDMMC), /* mmc1 WP, nCD */ +}; + +#define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3) +#define MSP_GPIO_MASK(offset) BIT(msp_gpios[(offset)] & 0x07) + +static int msp_gpio_in(struct gpio_chip *chip, unsigned offset) +{ + switch (MSP_GPIO_REG(offset)) { + case DM355EVM_MSP_SWITCH1: + case DM355EVM_MSP_SWITCH2: + case DM355EVM_MSP_SDMMC: + return 0; + default: + return -EINVAL; + } +} + +static u8 msp_led_cache; + +static int msp_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + int reg, status; + + reg = MSP_GPIO_REG(offset); + status = dm355evm_msp_read(reg); + if (status < 0) + return status; + if (reg == DM355EVM_MSP_LED) + msp_led_cache = status; + return status & MSP_GPIO_MASK(offset); +} + +static int msp_gpio_out(struct gpio_chip *chip, unsigned offset, int value) +{ + int mask, bits; + + /* NOTE: there are some other signals that could be + * packaged as output GPIOs, but they aren't as useful + * as the LEDs ... so for now we don't. + */ + if (MSP_GPIO_REG(offset) != DM355EVM_MSP_LED) + return -EINVAL; + + mask = MSP_GPIO_MASK(offset); + bits = msp_led_cache; + + bits &= ~mask; + if (value) + bits |= mask; + msp_led_cache = bits; + + return dm355evm_msp_write(bits, DM355EVM_MSP_LED); +} + +static void msp_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + msp_gpio_out(chip, offset, value); +} + +static struct gpio_chip dm355evm_msp_gpio = { + .label = "dm355evm_msp", + .owner = THIS_MODULE, + .direction_input = msp_gpio_in, + .get = msp_gpio_get, + .direction_output = msp_gpio_out, + .set = msp_gpio_set, + .base = -EINVAL, /* dynamic assignment */ + .ngpio = ARRAY_SIZE(msp_gpios), + .can_sleep = true, +}; + +/*----------------------------------------------------------------------*/ + +static struct device *add_child(struct i2c_client *client, const char *name, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq) +{ + struct platform_device *pdev; + int status; + + pdev = platform_device_alloc(name, -1); + if (!pdev) { + dev_dbg(&client->dev, "can't alloc dev\n"); + status = -ENOMEM; + goto err; + } + + device_init_wakeup(&pdev->dev, can_wakeup); + pdev->dev.parent = &client->dev; + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add platform_data\n"); + goto err; + } + } + + if (irq) { + struct resource r = { + .start = irq, + .flags = IORESOURCE_IRQ, + }; + + status = platform_device_add_resources(pdev, &r, 1); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add irq\n"); + goto err; + } + } + + status = platform_device_add(pdev); + +err: + if (status < 0) { + platform_device_put(pdev); + dev_err(&client->dev, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; +} + +static int add_children(struct i2c_client *client) +{ + static const struct { + int offset; + char *label; + } config_inputs[] = { + /* 8 == right after the LEDs */ + { 8 + 0, "sw6_1", }, + { 8 + 1, "sw6_2", }, + { 8 + 2, "sw6_3", }, + { 8 + 3, "sw6_4", }, + { 8 + 4, "NTSC/nPAL", }, + }; + + struct device *child; + int status; + int i; + + /* GPIO-ish stuff */ + dm355evm_msp_gpio.dev = &client->dev; + status = gpiochip_add(&dm355evm_msp_gpio); + if (status < 0) + return status; + + /* LED output */ + if (msp_has_leds()) { +#define GPIO_LED(l) .name = l, .active_low = true + static struct gpio_led evm_leds[] = { + { GPIO_LED("dm355evm::ds14"), + .default_trigger = "heartbeat", }, + { GPIO_LED("dm355evm::ds15"), + .default_trigger = "mmc0", }, + { GPIO_LED("dm355evm::ds16"), + /* could also be a CE-ATA drive */ + .default_trigger = "mmc1", }, + { GPIO_LED("dm355evm::ds17"), + .default_trigger = "nand-disk", }, + { GPIO_LED("dm355evm::ds18"), }, + { GPIO_LED("dm355evm::ds19"), }, + { GPIO_LED("dm355evm::ds20"), }, + { GPIO_LED("dm355evm::ds21"), }, + }; +#undef GPIO_LED + + struct gpio_led_platform_data evm_led_data = { + .num_leds = ARRAY_SIZE(evm_leds), + .leds = evm_leds, + }; + + for (i = 0; i < ARRAY_SIZE(evm_leds); i++) + evm_leds[i].gpio = i + dm355evm_msp_gpio.base; + + /* NOTE: these are the only fully programmable LEDs + * on the board, since GPIO-61/ds22 (and many signals + * going to DC7) must be used for AEMIF address lines + * unless the top 1 GB of NAND is unused... + */ + child = add_child(client, "leds-gpio", + &evm_led_data, sizeof(evm_led_data), + false, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* configuration inputs */ + for (i = 0; i < ARRAY_SIZE(config_inputs); i++) { + int gpio = dm355evm_msp_gpio.base + config_inputs[i].offset; + + gpio_request_one(gpio, GPIOF_IN, config_inputs[i].label); + + /* make it easy for userspace to see these */ + gpio_export(gpio, false); + } + + /* MMC/SD inputs -- right after the last config input */ + if (dev_get_platdata(&client->dev)) { + void (*mmcsd_setup)(unsigned) = dev_get_platdata(&client->dev); + + mmcsd_setup(dm355evm_msp_gpio.base + 8 + 5); + } + + /* RTC is a 32 bit counter, no alarm */ + if (msp_has_rtc()) { + child = add_child(client, "rtc-dm355evm", + NULL, 0, false, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* input from buttons and IR remote (uses the IRQ) */ + if (msp_has_keyboard()) { + child = add_child(client, "dm355evm_keys", + NULL, 0, true, client->irq); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + return 0; +} + +/*----------------------------------------------------------------------*/ + +static void dm355evm_command(unsigned command) +{ + int status; + + status = dm355evm_msp_write(command, DM355EVM_MSP_COMMAND); + if (status < 0) + dev_err(&msp430->dev, "command %d failure %d\n", + command, status); +} + +static void dm355evm_power_off(void) +{ + dm355evm_command(MSP_COMMAND_POWEROFF); +} + +static int dm355evm_msp_remove(struct i2c_client *client) +{ + pm_power_off = NULL; + msp430 = NULL; + return 0; +} + +static int +dm355evm_msp_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int status; + const char *video = msp_has_tvp() ? "TVP5146" : "imager"; + + if (msp430) + return -EBUSY; + msp430 = client; + + /* display revision status; doubles as sanity check */ + status = dm355evm_msp_read(DM355EVM_MSP_FIRMREV); + if (status < 0) + goto fail; + dev_info(&client->dev, "firmware v.%02X, %s as video-in\n", + status, video); + + /* mux video input: either tvp5146 or some external imager */ + status = dm355evm_msp_write(msp_has_tvp() ? 0 : MSP_VIDEO_IMAGER, + DM355EVM_MSP_VIDEO_IN); + if (status < 0) + dev_warn(&client->dev, "error %d muxing %s as video-in\n", + status, video); + + /* init LED cache, and turn off the LEDs */ + msp_led_cache = 0xff; + dm355evm_msp_write(msp_led_cache, DM355EVM_MSP_LED); + + /* export capabilities we support */ + status = add_children(client); + if (status < 0) + goto fail; + + /* PM hookup */ + pm_power_off = dm355evm_power_off; + + return 0; + +fail: + /* FIXME remove children ... */ + dm355evm_msp_remove(client); + return status; +} + +static const struct i2c_device_id dm355evm_msp_ids[] = { + { "dm355evm_msp", 0 }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(i2c, dm355evm_msp_ids); + +static struct i2c_driver dm355evm_msp_driver = { + .driver.name = "dm355evm_msp", + .id_table = dm355evm_msp_ids, + .probe = dm355evm_msp_probe, + .remove = dm355evm_msp_remove, +}; + +static int __init dm355evm_msp_init(void) +{ + return i2c_add_driver(&dm355evm_msp_driver); +} +subsys_initcall(dm355evm_msp_init); + +static void __exit dm355evm_msp_exit(void) +{ + i2c_del_driver(&dm355evm_msp_driver); +} +module_exit(dm355evm_msp_exit); + +MODULE_DESCRIPTION("Interface to MSP430 firmware on DM355EVM"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/ezx-pcap.c b/kernel/drivers/mfd/ezx-pcap.c new file mode 100644 index 000000000..5991faddd --- /dev/null +++ b/kernel/drivers/mfd/ezx-pcap.c @@ -0,0 +1,541 @@ +/* + * Driver for Motorola PCAP2 as present in EZX phones + * + * Copyright (C) 2006 Harald Welte <laforge@openezx.org> + * Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/ezx-pcap.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +#define PCAP_ADC_MAXQ 8 +struct pcap_adc_request { + u8 bank; + u8 ch[2]; + u32 flags; + void (*callback)(void *, u16[]); + void *data; +}; + +struct pcap_adc_sync_request { + u16 res[2]; + struct completion completion; +}; + +struct pcap_chip { + struct spi_device *spi; + + /* IO */ + u32 buf; + struct mutex io_mutex; + + /* IRQ */ + unsigned int irq_base; + u32 msr; + struct work_struct isr_work; + struct work_struct msr_work; + struct workqueue_struct *workqueue; + + /* ADC */ + struct pcap_adc_request *adc_queue[PCAP_ADC_MAXQ]; + u8 adc_head; + u8 adc_tail; + struct mutex adc_mutex; +}; + +/* IO */ +static int ezx_pcap_putget(struct pcap_chip *pcap, u32 *data) +{ + struct spi_transfer t; + struct spi_message m; + int status; + + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + t.len = sizeof(u32); + spi_message_add_tail(&t, &m); + + pcap->buf = *data; + t.tx_buf = (u8 *) &pcap->buf; + t.rx_buf = (u8 *) &pcap->buf; + status = spi_sync(pcap->spi, &m); + + if (status == 0) + *data = pcap->buf; + + return status; +} + +int ezx_pcap_write(struct pcap_chip *pcap, u8 reg_num, u32 value) +{ + int ret; + + mutex_lock(&pcap->io_mutex); + value &= PCAP_REGISTER_VALUE_MASK; + value |= PCAP_REGISTER_WRITE_OP_BIT + | (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + ret = ezx_pcap_putget(pcap, &value); + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_write); + +int ezx_pcap_read(struct pcap_chip *pcap, u8 reg_num, u32 *value) +{ + int ret; + + mutex_lock(&pcap->io_mutex); + *value = PCAP_REGISTER_READ_OP_BIT + | (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + ret = ezx_pcap_putget(pcap, value); + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_read); + +int ezx_pcap_set_bits(struct pcap_chip *pcap, u8 reg_num, u32 mask, u32 val) +{ + int ret; + u32 tmp = PCAP_REGISTER_READ_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + mutex_lock(&pcap->io_mutex); + ret = ezx_pcap_putget(pcap, &tmp); + if (ret) + goto out_unlock; + + tmp &= (PCAP_REGISTER_VALUE_MASK & ~mask); + tmp |= (val & mask) | PCAP_REGISTER_WRITE_OP_BIT | + (reg_num << PCAP_REGISTER_ADDRESS_SHIFT); + + ret = ezx_pcap_putget(pcap, &tmp); +out_unlock: + mutex_unlock(&pcap->io_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(ezx_pcap_set_bits); + +/* IRQ */ +int irq_to_pcap(struct pcap_chip *pcap, int irq) +{ + return irq - pcap->irq_base; +} +EXPORT_SYMBOL_GPL(irq_to_pcap); + +int pcap_to_irq(struct pcap_chip *pcap, int irq) +{ + return pcap->irq_base + irq; +} +EXPORT_SYMBOL_GPL(pcap_to_irq); + +static void pcap_mask_irq(struct irq_data *d) +{ + struct pcap_chip *pcap = irq_data_get_irq_chip_data(d); + + pcap->msr |= 1 << irq_to_pcap(pcap, d->irq); + queue_work(pcap->workqueue, &pcap->msr_work); +} + +static void pcap_unmask_irq(struct irq_data *d) +{ + struct pcap_chip *pcap = irq_data_get_irq_chip_data(d); + + pcap->msr &= ~(1 << irq_to_pcap(pcap, d->irq)); + queue_work(pcap->workqueue, &pcap->msr_work); +} + +static struct irq_chip pcap_irq_chip = { + .name = "pcap", + .irq_disable = pcap_mask_irq, + .irq_mask = pcap_mask_irq, + .irq_unmask = pcap_unmask_irq, +}; + +static void pcap_msr_work(struct work_struct *work) +{ + struct pcap_chip *pcap = container_of(work, struct pcap_chip, msr_work); + + ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr); +} + +static void pcap_isr_work(struct work_struct *work) +{ + struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work); + struct pcap_platform_data *pdata = dev_get_platdata(&pcap->spi->dev); + u32 msr, isr, int_sel, service; + int irq; + + do { + ezx_pcap_read(pcap, PCAP_REG_MSR, &msr); + ezx_pcap_read(pcap, PCAP_REG_ISR, &isr); + + /* We can't service/ack irqs that are assigned to port 2 */ + if (!(pdata->config & PCAP_SECOND_PORT)) { + ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel); + isr &= ~int_sel; + } + + ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr); + ezx_pcap_write(pcap, PCAP_REG_ISR, isr); + + local_irq_disable(); + service = isr & ~msr; + for (irq = pcap->irq_base; service; service >>= 1, irq++) { + if (service & 1) + generic_handle_irq(irq); + } + local_irq_enable(); + ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr); + } while (gpio_get_value(pdata->gpio)); +} + +static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct pcap_chip *pcap = irq_get_handler_data(irq); + + desc->irq_data.chip->irq_ack(&desc->irq_data); + queue_work(pcap->workqueue, &pcap->isr_work); +} + +/* ADC */ +void pcap_set_ts_bits(struct pcap_chip *pcap, u32 bits) +{ + u32 tmp; + + mutex_lock(&pcap->adc_mutex); + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= bits & (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); +} +EXPORT_SYMBOL_GPL(pcap_set_ts_bits); + +static void pcap_disable_adc(struct pcap_chip *pcap) +{ + u32 tmp; + + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_ADEN|PCAP_ADC_BATT_I_ADC|PCAP_ADC_BATT_I_POLARITY); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); +} + +static void pcap_adc_trigger(struct pcap_chip *pcap) +{ + u32 tmp; + u8 head; + + mutex_lock(&pcap->adc_mutex); + head = pcap->adc_head; + if (!pcap->adc_queue[head]) { + /* queue is empty, save power */ + pcap_disable_adc(pcap); + mutex_unlock(&pcap->adc_mutex); + return; + } + /* start conversion on requested bank, save TS_M bits */ + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR); + tmp |= pcap->adc_queue[head]->flags | PCAP_ADC_ADEN; + + if (pcap->adc_queue[head]->bank == PCAP_ADC_BANK_1) + tmp |= PCAP_ADC_AD_SEL1; + + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + mutex_unlock(&pcap->adc_mutex); + ezx_pcap_write(pcap, PCAP_REG_ADR, PCAP_ADR_ASC); +} + +static irqreturn_t pcap_adc_irq(int irq, void *_pcap) +{ + struct pcap_chip *pcap = _pcap; + struct pcap_adc_request *req; + u16 res[2]; + u32 tmp; + + mutex_lock(&pcap->adc_mutex); + req = pcap->adc_queue[pcap->adc_head]; + + if (WARN(!req, "adc irq without pending request\n")) { + mutex_unlock(&pcap->adc_mutex); + return IRQ_HANDLED; + } + + /* read requested channels results */ + ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_ADA1_MASK | PCAP_ADC_ADA2_MASK); + tmp |= (req->ch[0] << PCAP_ADC_ADA1_SHIFT); + tmp |= (req->ch[1] << PCAP_ADC_ADA2_SHIFT); + ezx_pcap_write(pcap, PCAP_REG_ADC, tmp); + ezx_pcap_read(pcap, PCAP_REG_ADR, &tmp); + res[0] = (tmp & PCAP_ADR_ADD1_MASK) >> PCAP_ADR_ADD1_SHIFT; + res[1] = (tmp & PCAP_ADR_ADD2_MASK) >> PCAP_ADR_ADD2_SHIFT; + + pcap->adc_queue[pcap->adc_head] = NULL; + pcap->adc_head = (pcap->adc_head + 1) & (PCAP_ADC_MAXQ - 1); + mutex_unlock(&pcap->adc_mutex); + + /* pass the results and release memory */ + req->callback(req->data, res); + kfree(req); + + /* trigger next conversion (if any) on queue */ + pcap_adc_trigger(pcap); + + return IRQ_HANDLED; +} + +int pcap_adc_async(struct pcap_chip *pcap, u8 bank, u32 flags, u8 ch[], + void *callback, void *data) +{ + struct pcap_adc_request *req; + + /* This will be freed after we have a result */ + req = kmalloc(sizeof(struct pcap_adc_request), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->bank = bank; + req->flags = flags; + req->ch[0] = ch[0]; + req->ch[1] = ch[1]; + req->callback = callback; + req->data = data; + + mutex_lock(&pcap->adc_mutex); + if (pcap->adc_queue[pcap->adc_tail]) { + mutex_unlock(&pcap->adc_mutex); + kfree(req); + return -EBUSY; + } + pcap->adc_queue[pcap->adc_tail] = req; + pcap->adc_tail = (pcap->adc_tail + 1) & (PCAP_ADC_MAXQ - 1); + mutex_unlock(&pcap->adc_mutex); + + /* start conversion */ + pcap_adc_trigger(pcap); + + return 0; +} +EXPORT_SYMBOL_GPL(pcap_adc_async); + +static void pcap_adc_sync_cb(void *param, u16 res[]) +{ + struct pcap_adc_sync_request *req = param; + + req->res[0] = res[0]; + req->res[1] = res[1]; + complete(&req->completion); +} + +int pcap_adc_sync(struct pcap_chip *pcap, u8 bank, u32 flags, u8 ch[], + u16 res[]) +{ + struct pcap_adc_sync_request sync_data; + int ret; + + init_completion(&sync_data.completion); + ret = pcap_adc_async(pcap, bank, flags, ch, pcap_adc_sync_cb, + &sync_data); + if (ret) + return ret; + wait_for_completion(&sync_data.completion); + res[0] = sync_data.res[0]; + res[1] = sync_data.res[1]; + + return 0; +} +EXPORT_SYMBOL_GPL(pcap_adc_sync); + +/* subdevs */ +static int pcap_remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int pcap_add_subdev(struct pcap_chip *pcap, + struct pcap_subdev *subdev) +{ + struct platform_device *pdev; + int ret; + + pdev = platform_device_alloc(subdev->name, subdev->id); + if (!pdev) + return -ENOMEM; + + pdev->dev.parent = &pcap->spi->dev; + pdev->dev.platform_data = subdev->platform_data; + + ret = platform_device_add(pdev); + if (ret) + platform_device_put(pdev); + + return ret; +} + +static int ezx_pcap_remove(struct spi_device *spi) +{ + struct pcap_chip *pcap = spi_get_drvdata(spi); + int i; + + /* remove all registered subdevs */ + device_for_each_child(&spi->dev, NULL, pcap_remove_subdev); + + /* cleanup ADC */ + mutex_lock(&pcap->adc_mutex); + for (i = 0; i < PCAP_ADC_MAXQ; i++) + kfree(pcap->adc_queue[i]); + mutex_unlock(&pcap->adc_mutex); + + /* cleanup irqchip */ + for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) + irq_set_chip_and_handler(i, NULL, NULL); + + destroy_workqueue(pcap->workqueue); + + return 0; +} + +static int ezx_pcap_probe(struct spi_device *spi) +{ + struct pcap_platform_data *pdata = dev_get_platdata(&spi->dev); + struct pcap_chip *pcap; + int i, adc_irq; + int ret = -ENODEV; + + /* platform data is required */ + if (!pdata) + goto ret; + + pcap = devm_kzalloc(&spi->dev, sizeof(*pcap), GFP_KERNEL); + if (!pcap) { + ret = -ENOMEM; + goto ret; + } + + mutex_init(&pcap->io_mutex); + mutex_init(&pcap->adc_mutex); + INIT_WORK(&pcap->isr_work, pcap_isr_work); + INIT_WORK(&pcap->msr_work, pcap_msr_work); + spi_set_drvdata(spi, pcap); + + /* setup spi */ + spi->bits_per_word = 32; + spi->mode = SPI_MODE_0 | (pdata->config & PCAP_CS_AH ? SPI_CS_HIGH : 0); + ret = spi_setup(spi); + if (ret) + goto ret; + + pcap->spi = spi; + + /* setup irq */ + pcap->irq_base = pdata->irq_base; + pcap->workqueue = create_singlethread_workqueue("pcapd"); + if (!pcap->workqueue) { + ret = -ENOMEM; + dev_err(&spi->dev, "can't create pcap thread\n"); + goto ret; + } + + /* redirect interrupts to AP, except adcdone2 */ + if (!(pdata->config & PCAP_SECOND_PORT)) + ezx_pcap_write(pcap, PCAP_REG_INT_SEL, + (1 << PCAP_IRQ_ADCDONE2)); + + /* setup irq chip */ + for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) { + irq_set_chip_and_handler(i, &pcap_irq_chip, handle_simple_irq); + irq_set_chip_data(i, pcap); +#ifdef CONFIG_ARM + set_irq_flags(i, IRQF_VALID); +#else + irq_set_noprobe(i); +#endif + } + + /* mask/ack all PCAP interrupts */ + ezx_pcap_write(pcap, PCAP_REG_MSR, PCAP_MASK_ALL_INTERRUPT); + ezx_pcap_write(pcap, PCAP_REG_ISR, PCAP_CLEAR_INTERRUPT_REGISTER); + pcap->msr = PCAP_MASK_ALL_INTERRUPT; + + irq_set_irq_type(spi->irq, IRQ_TYPE_EDGE_RISING); + irq_set_handler_data(spi->irq, pcap); + irq_set_chained_handler(spi->irq, pcap_irq_handler); + irq_set_irq_wake(spi->irq, 1); + + /* ADC */ + adc_irq = pcap_to_irq(pcap, (pdata->config & PCAP_SECOND_PORT) ? + PCAP_IRQ_ADCDONE2 : PCAP_IRQ_ADCDONE); + + ret = devm_request_irq(&spi->dev, adc_irq, pcap_adc_irq, 0, "ADC", + pcap); + if (ret) + goto free_irqchip; + + /* setup subdevs */ + for (i = 0; i < pdata->num_subdevs; i++) { + ret = pcap_add_subdev(pcap, &pdata->subdevs[i]); + if (ret) + goto remove_subdevs; + } + + /* board specific quirks */ + if (pdata->init) + pdata->init(pcap); + + return 0; + +remove_subdevs: + device_for_each_child(&spi->dev, NULL, pcap_remove_subdev); +free_irqchip: + for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++) + irq_set_chip_and_handler(i, NULL, NULL); +/* destroy_workqueue: */ + destroy_workqueue(pcap->workqueue); +ret: + return ret; +} + +static struct spi_driver ezxpcap_driver = { + .probe = ezx_pcap_probe, + .remove = ezx_pcap_remove, + .driver = { + .name = "ezx-pcap", + .owner = THIS_MODULE, + }, +}; + +static int __init ezx_pcap_init(void) +{ + return spi_register_driver(&ezxpcap_driver); +} + +static void __exit ezx_pcap_exit(void) +{ + spi_unregister_driver(&ezxpcap_driver); +} + +subsys_initcall(ezx_pcap_init); +module_exit(ezx_pcap_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Ribeiro / Harald Welte"); +MODULE_DESCRIPTION("Motorola PCAP2 ASIC Driver"); +MODULE_ALIAS("spi:ezx-pcap"); diff --git a/kernel/drivers/mfd/hi6421-pmic-core.c b/kernel/drivers/mfd/hi6421-pmic-core.c new file mode 100644 index 000000000..95b2ff8f2 --- /dev/null +++ b/kernel/drivers/mfd/hi6421-pmic-core.c @@ -0,0 +1,113 @@ +/* + * Device driver for Hi6421 IC + * + * Copyright (c) <2011-2014> HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright (c) <2013-2014> Linaro Ltd. + * http://www.linaro.org + * + * Author: Guodong Xu <guodong.xu@linaro.org> + * + * 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 program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/hi6421-pmic.h> + +static const struct mfd_cell hi6421_devs[] = { + { .name = "hi6421-regulator", }, +}; + +static const struct regmap_config hi6421_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 8, + .max_register = HI6421_REG_TO_BUS_ADDR(HI6421_REG_MAX), +}; + +static int hi6421_pmic_probe(struct platform_device *pdev) +{ + struct hi6421_pmic *pmic; + struct resource *res; + void __iomem *base; + int ret; + + pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + pmic->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base, + &hi6421_regmap_config); + if (IS_ERR(pmic->regmap)) { + dev_err(&pdev->dev, + "regmap init failed: %ld\n", PTR_ERR(pmic->regmap)); + return PTR_ERR(pmic->regmap); + } + + /* set over-current protection debounce 8ms */ + regmap_update_bits(pmic->regmap, HI6421_OCP_DEB_CTRL_REG, + (HI6421_OCP_DEB_SEL_MASK + | HI6421_OCP_EN_DEBOUNCE_MASK + | HI6421_OCP_AUTO_STOP_MASK), + (HI6421_OCP_DEB_SEL_8MS + | HI6421_OCP_EN_DEBOUNCE_ENABLE)); + + platform_set_drvdata(pdev, pmic); + + ret = mfd_add_devices(&pdev->dev, 0, hi6421_devs, + ARRAY_SIZE(hi6421_devs), NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "add mfd devices failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int hi6421_pmic_remove(struct platform_device *pdev) +{ + mfd_remove_devices(&pdev->dev); + + return 0; +} + +static const struct of_device_id of_hi6421_pmic_match_tbl[] = { + { .compatible = "hisilicon,hi6421-pmic", }, + { }, +}; + +static struct platform_driver hi6421_pmic_driver = { + .driver = { + .name = "hi6421_pmic", + .of_match_table = of_hi6421_pmic_match_tbl, + }, + .probe = hi6421_pmic_probe, + .remove = hi6421_pmic_remove, +}; +module_platform_driver(hi6421_pmic_driver); + +MODULE_AUTHOR("Guodong Xu <guodong.xu@linaro.org>"); +MODULE_DESCRIPTION("Hi6421 PMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/htc-egpio.c b/kernel/drivers/mfd/htc-egpio.c new file mode 100644 index 000000000..49f39feca --- /dev/null +++ b/kernel/drivers/mfd/htc-egpio.c @@ -0,0 +1,440 @@ +/* + * Support for the GPIO/IRQ expander chips present on several HTC phones. + * These are implemented in CPLD chips present on the board. + * + * Copyright (c) 2007 Kevin O'Connor <kevin@koconnor.net> + * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com> + * + * This file may be distributed under the terms of the GNU GPL license. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mfd/htc-egpio.h> + +struct egpio_chip { + int reg_start; + int cached_values; + unsigned long is_out; + struct device *dev; + struct gpio_chip chip; +}; + +struct egpio_info { + spinlock_t lock; + + /* iomem info */ + void __iomem *base_addr; + int bus_shift; /* byte shift */ + int reg_shift; /* bit shift */ + int reg_mask; + + /* irq info */ + int ack_register; + int ack_write; + u16 irqs_enabled; + uint irq_start; + int nirqs; + uint chained_irq; + + /* egpio info */ + struct egpio_chip *chip; + int nchips; +}; + +static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg) +{ + writew(value, ei->base_addr + (reg << ei->bus_shift)); +} + +static inline u16 egpio_readw(struct egpio_info *ei, int reg) +{ + return readw(ei->base_addr + (reg << ei->bus_shift)); +} + +/* + * IRQs + */ + +static inline void ack_irqs(struct egpio_info *ei) +{ + egpio_writew(ei->ack_write, ei, ei->ack_register); + pr_debug("EGPIO ack - write %x to base+%x\n", + ei->ack_write, ei->ack_register << ei->bus_shift); +} + +static void egpio_ack(struct irq_data *data) +{ +} + +/* There does not appear to be a way to proactively mask interrupts + * on the egpio chip itself. So, we simply ignore interrupts that + * aren't desired. */ +static void egpio_mask(struct irq_data *data) +{ + struct egpio_info *ei = irq_data_get_irq_chip_data(data); + ei->irqs_enabled &= ~(1 << (data->irq - ei->irq_start)); + pr_debug("EGPIO mask %d %04x\n", data->irq, ei->irqs_enabled); +} + +static void egpio_unmask(struct irq_data *data) +{ + struct egpio_info *ei = irq_data_get_irq_chip_data(data); + ei->irqs_enabled |= 1 << (data->irq - ei->irq_start); + pr_debug("EGPIO unmask %d %04x\n", data->irq, ei->irqs_enabled); +} + +static struct irq_chip egpio_muxed_chip = { + .name = "htc-egpio", + .irq_ack = egpio_ack, + .irq_mask = egpio_mask, + .irq_unmask = egpio_unmask, +}; + +static void egpio_handler(unsigned int irq, struct irq_desc *desc) +{ + struct egpio_info *ei = irq_desc_get_handler_data(desc); + int irqpin; + + /* Read current pins. */ + unsigned long readval = egpio_readw(ei, ei->ack_register); + pr_debug("IRQ reg: %x\n", (unsigned int)readval); + /* Ack/unmask interrupts. */ + ack_irqs(ei); + /* Process all set pins. */ + readval &= ei->irqs_enabled; + for_each_set_bit(irqpin, &readval, ei->nirqs) { + /* Run irq handler */ + pr_debug("got IRQ %d\n", irqpin); + generic_handle_irq(ei->irq_start + irqpin); + } +} + +int htc_egpio_get_wakeup_irq(struct device *dev) +{ + struct egpio_info *ei = dev_get_drvdata(dev); + + /* Read current pins. */ + u16 readval = egpio_readw(ei, ei->ack_register); + /* Ack/unmask interrupts. */ + ack_irqs(ei); + /* Return first set pin. */ + readval &= ei->irqs_enabled; + return ei->irq_start + ffs(readval) - 1; +} +EXPORT_SYMBOL(htc_egpio_get_wakeup_irq); + +static inline int egpio_pos(struct egpio_info *ei, int bit) +{ + return bit >> ei->reg_shift; +} + +static inline int egpio_bit(struct egpio_info *ei, int bit) +{ + return 1 << (bit & ((1 << ei->reg_shift)-1)); +} + +/* + * Input pins + */ + +static int egpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct egpio_chip *egpio; + struct egpio_info *ei; + unsigned bit; + int reg; + int value; + + pr_debug("egpio_get_value(%d)\n", chip->base + offset); + + egpio = container_of(chip, struct egpio_chip, chip); + ei = dev_get_drvdata(egpio->dev); + bit = egpio_bit(ei, offset); + reg = egpio->reg_start + egpio_pos(ei, offset); + + value = egpio_readw(ei, reg); + pr_debug("readw(%p + %x) = %x\n", + ei->base_addr, reg << ei->bus_shift, value); + return value & bit; +} + +static int egpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct egpio_chip *egpio; + + egpio = container_of(chip, struct egpio_chip, chip); + return test_bit(offset, &egpio->is_out) ? -EINVAL : 0; +} + + +/* + * Output pins + */ + +static void egpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + unsigned long flag; + struct egpio_chip *egpio; + struct egpio_info *ei; + unsigned bit; + int pos; + int reg; + int shift; + + pr_debug("egpio_set(%s, %d(%d), %d)\n", + chip->label, offset, offset+chip->base, value); + + egpio = container_of(chip, struct egpio_chip, chip); + ei = dev_get_drvdata(egpio->dev); + bit = egpio_bit(ei, offset); + pos = egpio_pos(ei, offset); + reg = egpio->reg_start + pos; + shift = pos << ei->reg_shift; + + pr_debug("egpio %s: reg %d = 0x%04x\n", value ? "set" : "clear", + reg, (egpio->cached_values >> shift) & ei->reg_mask); + + spin_lock_irqsave(&ei->lock, flag); + if (value) + egpio->cached_values |= (1 << offset); + else + egpio->cached_values &= ~(1 << offset); + egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg); + spin_unlock_irqrestore(&ei->lock, flag); +} + +static int egpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct egpio_chip *egpio; + + egpio = container_of(chip, struct egpio_chip, chip); + if (test_bit(offset, &egpio->is_out)) { + egpio_set(chip, offset, value); + return 0; + } else { + return -EINVAL; + } +} + +static void egpio_write_cache(struct egpio_info *ei) +{ + int i; + struct egpio_chip *egpio; + int shift; + + for (i = 0; i < ei->nchips; i++) { + egpio = &(ei->chip[i]); + if (!egpio->is_out) + continue; + + for (shift = 0; shift < egpio->chip.ngpio; + shift += (1<<ei->reg_shift)) { + + int reg = egpio->reg_start + egpio_pos(ei, shift); + + if (!((egpio->is_out >> shift) & ei->reg_mask)) + continue; + + pr_debug("EGPIO: setting %x to %x, was %x\n", reg, + (egpio->cached_values >> shift) & ei->reg_mask, + egpio_readw(ei, reg)); + + egpio_writew((egpio->cached_values >> shift) + & ei->reg_mask, ei, reg); + } + } +} + + +/* + * Setup + */ + +static int __init egpio_probe(struct platform_device *pdev) +{ + struct htc_egpio_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct resource *res; + struct egpio_info *ei; + struct gpio_chip *chip; + unsigned int irq, irq_end; + int i; + int ret; + + /* Initialize ei data structure. */ + ei = devm_kzalloc(&pdev->dev, sizeof(*ei), GFP_KERNEL); + if (!ei) + return -ENOMEM; + + spin_lock_init(&ei->lock); + + /* Find chained irq */ + ret = -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) + ei->chained_irq = res->start; + + /* Map egpio chip into virtual address space. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto fail; + ei->base_addr = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!ei->base_addr) + goto fail; + pr_debug("EGPIO phys=%08x virt=%p\n", (u32)res->start, ei->base_addr); + + if ((pdata->bus_width != 16) && (pdata->bus_width != 32)) + goto fail; + ei->bus_shift = fls(pdata->bus_width - 1) - 3; + pr_debug("bus_shift = %d\n", ei->bus_shift); + + if ((pdata->reg_width != 8) && (pdata->reg_width != 16)) + goto fail; + ei->reg_shift = fls(pdata->reg_width - 1); + pr_debug("reg_shift = %d\n", ei->reg_shift); + + ei->reg_mask = (1 << pdata->reg_width) - 1; + + platform_set_drvdata(pdev, ei); + + ei->nchips = pdata->num_chips; + ei->chip = devm_kzalloc(&pdev->dev, + sizeof(struct egpio_chip) * ei->nchips, + GFP_KERNEL); + if (!ei->chip) { + ret = -ENOMEM; + goto fail; + } + for (i = 0; i < ei->nchips; i++) { + ei->chip[i].reg_start = pdata->chip[i].reg_start; + ei->chip[i].cached_values = pdata->chip[i].initial_values; + ei->chip[i].is_out = pdata->chip[i].direction; + ei->chip[i].dev = &(pdev->dev); + chip = &(ei->chip[i].chip); + chip->label = "htc-egpio"; + chip->dev = &pdev->dev; + chip->owner = THIS_MODULE; + chip->get = egpio_get; + chip->set = egpio_set; + chip->direction_input = egpio_direction_input; + chip->direction_output = egpio_direction_output; + chip->base = pdata->chip[i].gpio_base; + chip->ngpio = pdata->chip[i].num_gpios; + + gpiochip_add(chip); + } + + /* Set initial pin values */ + egpio_write_cache(ei); + + ei->irq_start = pdata->irq_base; + ei->nirqs = pdata->num_irqs; + ei->ack_register = pdata->ack_register; + + if (ei->chained_irq) { + /* Setup irq handlers */ + ei->ack_write = 0xFFFF; + if (pdata->invert_acks) + ei->ack_write = 0; + irq_end = ei->irq_start + ei->nirqs; + for (irq = ei->irq_start; irq < irq_end; irq++) { + irq_set_chip_and_handler(irq, &egpio_muxed_chip, + handle_simple_irq); + irq_set_chip_data(irq, ei); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + irq_set_irq_type(ei->chained_irq, IRQ_TYPE_EDGE_RISING); + irq_set_handler_data(ei->chained_irq, ei); + irq_set_chained_handler(ei->chained_irq, egpio_handler); + ack_irqs(ei); + + device_init_wakeup(&pdev->dev, 1); + } + + return 0; + +fail: + printk(KERN_ERR "EGPIO failed to setup\n"); + return ret; +} + +static int __exit egpio_remove(struct platform_device *pdev) +{ + struct egpio_info *ei = platform_get_drvdata(pdev); + unsigned int irq, irq_end; + + if (ei->chained_irq) { + irq_end = ei->irq_start + ei->nirqs; + for (irq = ei->irq_start; irq < irq_end; irq++) { + irq_set_chip_and_handler(irq, NULL, NULL); + set_irq_flags(irq, 0); + } + irq_set_chained_handler(ei->chained_irq, NULL); + device_init_wakeup(&pdev->dev, 0); + } + + return 0; +} + +#ifdef CONFIG_PM +static int egpio_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct egpio_info *ei = platform_get_drvdata(pdev); + + if (ei->chained_irq && device_may_wakeup(&pdev->dev)) + enable_irq_wake(ei->chained_irq); + return 0; +} + +static int egpio_resume(struct platform_device *pdev) +{ + struct egpio_info *ei = platform_get_drvdata(pdev); + + if (ei->chained_irq && device_may_wakeup(&pdev->dev)) + disable_irq_wake(ei->chained_irq); + + /* Update registers from the cache, in case + the CPLD was powered off during suspend */ + egpio_write_cache(ei); + return 0; +} +#else +#define egpio_suspend NULL +#define egpio_resume NULL +#endif + + +static struct platform_driver egpio_driver = { + .driver = { + .name = "htc-egpio", + }, + .remove = __exit_p(egpio_remove), + .suspend = egpio_suspend, + .resume = egpio_resume, +}; + +static int __init egpio_init(void) +{ + return platform_driver_probe(&egpio_driver, egpio_probe); +} + +static void __exit egpio_exit(void) +{ + platform_driver_unregister(&egpio_driver); +} + +/* start early for dependencies */ +subsys_initcall(egpio_init); +module_exit(egpio_exit) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kevin O'Connor <kevin@koconnor.net>"); diff --git a/kernel/drivers/mfd/htc-i2cpld.c b/kernel/drivers/mfd/htc-i2cpld.c new file mode 100644 index 000000000..ebb9cf19e --- /dev/null +++ b/kernel/drivers/mfd/htc-i2cpld.c @@ -0,0 +1,668 @@ +/* + * htc-i2cpld.c + * Chip driver for an unknown CPLD chip found on omap850 HTC devices like + * the HTC Wizard and HTC Herald. + * The cpld is located on the i2c bus and acts as an input/output GPIO + * extender. + * + * Copyright (C) 2009 Cory Maccarrone <darkstar6262@gmail.com> + * + * Based on work done in the linwizard project + * Copyright (C) 2008-2009 Angelo Arrifano <miknix@gmail.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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/htcpld.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +struct htcpld_chip { + spinlock_t lock; + + /* chip info */ + u8 reset; + u8 addr; + struct device *dev; + struct i2c_client *client; + + /* Output details */ + u8 cache_out; + struct gpio_chip chip_out; + + /* Input details */ + u8 cache_in; + struct gpio_chip chip_in; + + u16 irqs_enabled; + uint irq_start; + int nirqs; + + unsigned int flow_type; + /* + * Work structure to allow for setting values outside of any + * possible interrupt context + */ + struct work_struct set_val_work; +}; + +struct htcpld_data { + /* irq info */ + u16 irqs_enabled; + uint irq_start; + int nirqs; + uint chained_irq; + unsigned int int_reset_gpio_hi; + unsigned int int_reset_gpio_lo; + + /* htcpld info */ + struct htcpld_chip *chip; + unsigned int nchips; +}; + +/* There does not appear to be a way to proactively mask interrupts + * on the htcpld chip itself. So, we simply ignore interrupts that + * aren't desired. */ +static void htcpld_mask(struct irq_data *data) +{ + struct htcpld_chip *chip = irq_data_get_irq_chip_data(data); + chip->irqs_enabled &= ~(1 << (data->irq - chip->irq_start)); + pr_debug("HTCPLD mask %d %04x\n", data->irq, chip->irqs_enabled); +} +static void htcpld_unmask(struct irq_data *data) +{ + struct htcpld_chip *chip = irq_data_get_irq_chip_data(data); + chip->irqs_enabled |= 1 << (data->irq - chip->irq_start); + pr_debug("HTCPLD unmask %d %04x\n", data->irq, chip->irqs_enabled); +} + +static int htcpld_set_type(struct irq_data *data, unsigned int flags) +{ + struct htcpld_chip *chip = irq_data_get_irq_chip_data(data); + + if (flags & ~IRQ_TYPE_SENSE_MASK) + return -EINVAL; + + /* We only allow edge triggering */ + if (flags & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)) + return -EINVAL; + + chip->flow_type = flags; + return 0; +} + +static struct irq_chip htcpld_muxed_chip = { + .name = "htcpld", + .irq_mask = htcpld_mask, + .irq_unmask = htcpld_unmask, + .irq_set_type = htcpld_set_type, +}; + +/* To properly dispatch IRQ events, we need to read from the + * chip. This is an I2C action that could possibly sleep + * (which is bad in interrupt context) -- so we use a threaded + * interrupt handler to get around that. + */ +static irqreturn_t htcpld_handler(int irq, void *dev) +{ + struct htcpld_data *htcpld = dev; + unsigned int i; + unsigned long flags; + int irqpin; + + if (!htcpld) { + pr_debug("htcpld is null in ISR\n"); + return IRQ_HANDLED; + } + + /* + * For each chip, do a read of the chip and trigger any interrupts + * desired. The interrupts will be triggered from LSB to MSB (i.e. + * bit 0 first, then bit 1, etc.) + * + * For chips that have no interrupt range specified, just skip 'em. + */ + for (i = 0; i < htcpld->nchips; i++) { + struct htcpld_chip *chip = &htcpld->chip[i]; + struct i2c_client *client; + int val; + unsigned long uval, old_val; + + if (!chip) { + pr_debug("chip %d is null in ISR\n", i); + continue; + } + + if (chip->nirqs == 0) + continue; + + client = chip->client; + if (!client) { + pr_debug("client %d is null in ISR\n", i); + continue; + } + + /* Scan the chip */ + val = i2c_smbus_read_byte_data(client, chip->cache_out); + if (val < 0) { + /* Throw a warning and skip this chip */ + dev_warn(chip->dev, "Unable to read from chip: %d\n", + val); + continue; + } + + uval = (unsigned long)val; + + spin_lock_irqsave(&chip->lock, flags); + + /* Save away the old value so we can compare it */ + old_val = chip->cache_in; + + /* Write the new value */ + chip->cache_in = uval; + + spin_unlock_irqrestore(&chip->lock, flags); + + /* + * For each bit in the data (starting at bit 0), trigger + * associated interrupts. + */ + for (irqpin = 0; irqpin < chip->nirqs; irqpin++) { + unsigned oldb, newb, type = chip->flow_type; + + irq = chip->irq_start + irqpin; + + /* Run the IRQ handler, but only if the bit value + * changed, and the proper flags are set */ + oldb = (old_val >> irqpin) & 1; + newb = (uval >> irqpin) & 1; + + if ((!oldb && newb && (type & IRQ_TYPE_EDGE_RISING)) || + (oldb && !newb && (type & IRQ_TYPE_EDGE_FALLING))) { + pr_debug("fire IRQ %d\n", irqpin); + generic_handle_irq(irq); + } + } + } + + /* + * In order to continue receiving interrupts, the int_reset_gpio must + * be asserted. + */ + if (htcpld->int_reset_gpio_hi) + gpio_set_value(htcpld->int_reset_gpio_hi, 1); + if (htcpld->int_reset_gpio_lo) + gpio_set_value(htcpld->int_reset_gpio_lo, 0); + + return IRQ_HANDLED; +} + +/* + * The GPIO set routines can be called from interrupt context, especially if, + * for example they're attached to the led-gpio framework and a trigger is + * enabled. As such, we declared work above in the htcpld_chip structure, + * and that work is scheduled in the set routine. The kernel can then run + * the I2C functions, which will sleep, in process context. + */ +static void htcpld_chip_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct i2c_client *client; + struct htcpld_chip *chip_data = + container_of(chip, struct htcpld_chip, chip_out); + unsigned long flags; + + client = chip_data->client; + if (!client) + return; + + spin_lock_irqsave(&chip_data->lock, flags); + if (val) + chip_data->cache_out |= (1 << offset); + else + chip_data->cache_out &= ~(1 << offset); + spin_unlock_irqrestore(&chip_data->lock, flags); + + schedule_work(&(chip_data->set_val_work)); +} + +static void htcpld_chip_set_ni(struct work_struct *work) +{ + struct htcpld_chip *chip_data; + struct i2c_client *client; + + chip_data = container_of(work, struct htcpld_chip, set_val_work); + client = chip_data->client; + i2c_smbus_read_byte_data(client, chip_data->cache_out); +} + +static int htcpld_chip_get(struct gpio_chip *chip, unsigned offset) +{ + struct htcpld_chip *chip_data; + u8 cache; + + if (!strncmp(chip->label, "htcpld-out", 10)) { + chip_data = container_of(chip, struct htcpld_chip, chip_out); + cache = chip_data->cache_out; + } else if (!strncmp(chip->label, "htcpld-in", 9)) { + chip_data = container_of(chip, struct htcpld_chip, chip_in); + cache = chip_data->cache_in; + } else + return -EINVAL; + + return (cache >> offset) & 1; +} + +static int htcpld_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + htcpld_chip_set(chip, offset, value); + return 0; +} + +static int htcpld_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + /* + * No-op: this function can only be called on the input chip. + * We do however make sure the offset is within range. + */ + return (offset < chip->ngpio) ? 0 : -EINVAL; +} + +static int htcpld_chip_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct htcpld_chip *chip_data; + + chip_data = container_of(chip, struct htcpld_chip, chip_in); + + if (offset < chip_data->nirqs) + return chip_data->irq_start + offset; + else + return -EINVAL; +} + +static void htcpld_chip_reset(struct i2c_client *client) +{ + struct htcpld_chip *chip_data = i2c_get_clientdata(client); + if (!chip_data) + return; + + i2c_smbus_read_byte_data( + client, (chip_data->cache_out = chip_data->reset)); +} + +static int htcpld_setup_chip_irq( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct htcpld_chip *chip; + unsigned int irq, irq_end; + int ret = 0; + + /* Get the platform and driver data */ + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + + /* Setup irq handlers */ + irq_end = chip->irq_start + chip->nirqs; + for (irq = chip->irq_start; irq < irq_end; irq++) { + irq_set_chip_and_handler(irq, &htcpld_muxed_chip, + handle_simple_irq); + irq_set_chip_data(irq, chip); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); +#else + irq_set_probe(irq); +#endif + } + + return ret; +} + +static int htcpld_register_chip_i2c( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct htcpld_chip *chip; + struct htcpld_chip_platform_data *plat_chip_data; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct i2c_board_info info; + + /* Get the platform and driver data */ + pdata = dev_get_platdata(dev); + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + adapter = i2c_get_adapter(pdata->i2c_adapter_id); + if (!adapter) { + /* Eek, no such I2C adapter! Bail out. */ + dev_warn(dev, "Chip at i2c address 0x%x: Invalid i2c adapter %d\n", + plat_chip_data->addr, pdata->i2c_adapter_id); + return -ENODEV; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + dev_warn(dev, "i2c adapter %d non-functional\n", + pdata->i2c_adapter_id); + return -EINVAL; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = plat_chip_data->addr; + strlcpy(info.type, "htcpld-chip", I2C_NAME_SIZE); + info.platform_data = chip; + + /* Add the I2C device. This calls the probe() function. */ + client = i2c_new_device(adapter, &info); + if (!client) { + /* I2C device registration failed, contineu with the next */ + dev_warn(dev, "Unable to add I2C device for 0x%x\n", + plat_chip_data->addr); + return -ENODEV; + } + + i2c_set_clientdata(client, chip); + snprintf(client->name, I2C_NAME_SIZE, "Chip_0x%x", client->addr); + chip->client = client; + + /* Reset the chip */ + htcpld_chip_reset(client); + chip->cache_in = i2c_smbus_read_byte_data(client, chip->cache_out); + + return 0; +} + +static void htcpld_unregister_chip_i2c( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct htcpld_chip *chip; + + /* Get the platform and driver data */ + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + + if (chip->client) + i2c_unregister_device(chip->client); +} + +static int htcpld_register_chip_gpio( + struct platform_device *pdev, + int chip_index) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct htcpld_chip *chip; + struct htcpld_chip_platform_data *plat_chip_data; + struct gpio_chip *gpio_chip; + int ret = 0; + + /* Get the platform and driver data */ + pdata = dev_get_platdata(dev); + htcpld = platform_get_drvdata(pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + /* Setup the GPIO chips */ + gpio_chip = &(chip->chip_out); + gpio_chip->label = "htcpld-out"; + gpio_chip->dev = dev; + gpio_chip->owner = THIS_MODULE; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set = htcpld_chip_set; + gpio_chip->direction_input = NULL; + gpio_chip->direction_output = htcpld_direction_output; + gpio_chip->base = plat_chip_data->gpio_out_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + + gpio_chip = &(chip->chip_in); + gpio_chip->label = "htcpld-in"; + gpio_chip->dev = dev; + gpio_chip->owner = THIS_MODULE; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set = NULL; + gpio_chip->direction_input = htcpld_direction_input; + gpio_chip->direction_output = NULL; + gpio_chip->to_irq = htcpld_chip_to_irq; + gpio_chip->base = plat_chip_data->gpio_in_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + + /* Add the GPIO chips */ + ret = gpiochip_add(&(chip->chip_out)); + if (ret) { + dev_warn(dev, "Unable to register output GPIOs for 0x%x: %d\n", + plat_chip_data->addr, ret); + return ret; + } + + ret = gpiochip_add(&(chip->chip_in)); + if (ret) { + dev_warn(dev, "Unable to register input GPIOs for 0x%x: %d\n", + plat_chip_data->addr, ret); + gpiochip_remove(&(chip->chip_out)); + return ret; + } + + return 0; +} + +static int htcpld_setup_chips(struct platform_device *pdev) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + int i; + + /* Get the platform and driver data */ + pdata = dev_get_platdata(dev); + htcpld = platform_get_drvdata(pdev); + + /* Setup each chip's output GPIOs */ + htcpld->nchips = pdata->num_chip; + htcpld->chip = devm_kzalloc(dev, sizeof(struct htcpld_chip) * htcpld->nchips, + GFP_KERNEL); + if (!htcpld->chip) { + dev_warn(dev, "Unable to allocate memory for chips\n"); + return -ENOMEM; + } + + /* Add the chips as best we can */ + for (i = 0; i < htcpld->nchips; i++) { + int ret; + + /* Setup the HTCPLD chips */ + htcpld->chip[i].reset = pdata->chip[i].reset; + htcpld->chip[i].cache_out = pdata->chip[i].reset; + htcpld->chip[i].cache_in = 0; + htcpld->chip[i].dev = dev; + htcpld->chip[i].irq_start = pdata->chip[i].irq_base; + htcpld->chip[i].nirqs = pdata->chip[i].num_irqs; + + INIT_WORK(&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni); + spin_lock_init(&(htcpld->chip[i].lock)); + + /* Setup the interrupts for the chip */ + if (htcpld->chained_irq) { + ret = htcpld_setup_chip_irq(pdev, i); + if (ret) + continue; + } + + /* Register the chip with I2C */ + ret = htcpld_register_chip_i2c(pdev, i); + if (ret) + continue; + + + /* Register the chips with the GPIO subsystem */ + ret = htcpld_register_chip_gpio(pdev, i); + if (ret) { + /* Unregister the chip from i2c and continue */ + htcpld_unregister_chip_i2c(pdev, i); + continue; + } + + dev_info(dev, "Registered chip at 0x%x\n", pdata->chip[i].addr); + } + + return 0; +} + +static int htcpld_core_probe(struct platform_device *pdev) +{ + struct htcpld_data *htcpld; + struct device *dev = &pdev->dev; + struct htcpld_core_platform_data *pdata; + struct resource *res; + int ret = 0; + + if (!dev) + return -ENODEV; + + pdata = dev_get_platdata(dev); + if (!pdata) { + dev_warn(dev, "Platform data not found for htcpld core!\n"); + return -ENXIO; + } + + htcpld = devm_kzalloc(dev, sizeof(struct htcpld_data), GFP_KERNEL); + if (!htcpld) + return -ENOMEM; + + /* Find chained irq */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) { + int flags; + htcpld->chained_irq = res->start; + + /* Setup the chained interrupt handler */ + flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + ret = request_threaded_irq(htcpld->chained_irq, + NULL, htcpld_handler, + flags, pdev->name, htcpld); + if (ret) { + dev_warn(dev, "Unable to setup chained irq handler: %d\n", ret); + return ret; + } else + device_init_wakeup(dev, 0); + } + + /* Set the driver data */ + platform_set_drvdata(pdev, htcpld); + + /* Setup the htcpld chips */ + ret = htcpld_setup_chips(pdev); + if (ret) + return ret; + + /* Request the GPIO(s) for the int reset and set them up */ + if (pdata->int_reset_gpio_hi) { + ret = gpio_request(pdata->int_reset_gpio_hi, "htcpld-core"); + if (ret) { + /* + * If it failed, that sucks, but we can probably + * continue on without it. + */ + dev_warn(dev, "Unable to request int_reset_gpio_hi -- interrupts may not work\n"); + htcpld->int_reset_gpio_hi = 0; + } else { + htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi; + gpio_set_value(htcpld->int_reset_gpio_hi, 1); + } + } + + if (pdata->int_reset_gpio_lo) { + ret = gpio_request(pdata->int_reset_gpio_lo, "htcpld-core"); + if (ret) { + /* + * If it failed, that sucks, but we can probably + * continue on without it. + */ + dev_warn(dev, "Unable to request int_reset_gpio_lo -- interrupts may not work\n"); + htcpld->int_reset_gpio_lo = 0; + } else { + htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo; + gpio_set_value(htcpld->int_reset_gpio_lo, 0); + } + } + + dev_info(dev, "Initialized successfully\n"); + return 0; +} + +/* The I2C Driver -- used internally */ +static const struct i2c_device_id htcpld_chip_id[] = { + { "htcpld-chip", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, htcpld_chip_id); + + +static struct i2c_driver htcpld_chip_driver = { + .driver = { + .name = "htcpld-chip", + }, + .id_table = htcpld_chip_id, +}; + +/* The Core Driver */ +static struct platform_driver htcpld_core_driver = { + .driver = { + .name = "i2c-htcpld", + }, +}; + +static int __init htcpld_core_init(void) +{ + int ret; + + /* Register the I2C Chip driver */ + ret = i2c_add_driver(&htcpld_chip_driver); + if (ret) + return ret; + + /* Probe for our chips */ + return platform_driver_probe(&htcpld_core_driver, htcpld_core_probe); +} + +static void __exit htcpld_core_exit(void) +{ + i2c_del_driver(&htcpld_chip_driver); + platform_driver_unregister(&htcpld_core_driver); +} + +module_init(htcpld_core_init); +module_exit(htcpld_core_exit); + +MODULE_AUTHOR("Cory Maccarrone <darkstar6262@gmail.com>"); +MODULE_DESCRIPTION("I2C HTC PLD Driver"); +MODULE_LICENSE("GPL"); + diff --git a/kernel/drivers/mfd/htc-pasic3.c b/kernel/drivers/mfd/htc-pasic3.c new file mode 100644 index 000000000..e88d4f6fe --- /dev/null +++ b/kernel/drivers/mfd/htc-pasic3.c @@ -0,0 +1,213 @@ +/* + * Core driver for HTC PASIC3 LED/DS1WM chip. + * + * Copyright (C) 2006 Philipp Zabel <philipp.zabel@gmail.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; version 2 of the License. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ds1wm.h> +#include <linux/mfd/htc-pasic3.h> +#include <linux/slab.h> + +struct pasic3_data { + void __iomem *mapping; + unsigned int bus_shift; +}; + +#define REG_ADDR 5 +#define REG_DATA 6 + +#define READ_MODE 0x80 + +/* + * write to a secondary register on the PASIC3 + */ +void pasic3_write_register(struct device *dev, u32 reg, u8 val) +{ + struct pasic3_data *asic = dev_get_drvdata(dev); + int bus_shift = asic->bus_shift; + void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); + void __iomem *data = asic->mapping + (REG_DATA << bus_shift); + + __raw_writeb(~READ_MODE & reg, addr); + __raw_writeb(val, data); +} +EXPORT_SYMBOL(pasic3_write_register); /* for leds-pasic3 */ + +/* + * read from a secondary register on the PASIC3 + */ +u8 pasic3_read_register(struct device *dev, u32 reg) +{ + struct pasic3_data *asic = dev_get_drvdata(dev); + int bus_shift = asic->bus_shift; + void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); + void __iomem *data = asic->mapping + (REG_DATA << bus_shift); + + __raw_writeb(READ_MODE | reg, addr); + return __raw_readb(data); +} +EXPORT_SYMBOL(pasic3_read_register); /* for leds-pasic3 */ + +/* + * LEDs + */ + +static struct mfd_cell led_cell __initdata = { + .name = "leds-pasic3", +}; + +/* + * DS1WM + */ + +static int ds1wm_enable(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + int c; + + c = pasic3_read_register(dev, 0x28); + pasic3_write_register(dev, 0x28, c & 0x7f); + + dev_dbg(dev, "DS1WM OWM_EN low (active) %02x\n", c & 0x7f); + return 0; +} + +static int ds1wm_disable(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + int c; + + c = pasic3_read_register(dev, 0x28); + pasic3_write_register(dev, 0x28, c | 0x80); + + dev_dbg(dev, "DS1WM OWM_EN high (inactive) %02x\n", c | 0x80); + return 0; +} + +static struct ds1wm_driver_data ds1wm_pdata = { + .active_high = 0, + .reset_recover_delay = 1, +}; + +static struct resource ds1wm_resources[] __initdata = { + [0] = { + .start = 0, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell ds1wm_cell __initconst = { + .name = "ds1wm", + .enable = ds1wm_enable, + .disable = ds1wm_disable, + .platform_data = &ds1wm_pdata, + .pdata_size = sizeof(ds1wm_pdata), + .num_resources = 2, + .resources = ds1wm_resources, +}; + +static int __init pasic3_probe(struct platform_device *pdev) +{ + struct pasic3_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct pasic3_data *asic; + struct resource *r; + int ret; + int irq = 0; + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r) { + ds1wm_resources[1].flags = IORESOURCE_IRQ | (r->flags & + (IORESOURCE_IRQ_HIGHEDGE | IORESOURCE_IRQ_LOWEDGE)); + irq = r->start; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -ENXIO; + + if (!request_mem_region(r->start, resource_size(r), "pasic3")) + return -EBUSY; + + asic = devm_kzalloc(dev, sizeof(struct pasic3_data), GFP_KERNEL); + if (!asic) + return -ENOMEM; + + platform_set_drvdata(pdev, asic); + + asic->mapping = ioremap(r->start, resource_size(r)); + if (!asic->mapping) { + dev_err(dev, "couldn't ioremap PASIC3\n"); + return -ENOMEM; + } + + /* calculate bus shift from mem resource */ + asic->bus_shift = (resource_size(r) - 5) >> 3; + + if (pdata && pdata->clock_rate) { + ds1wm_pdata.clock_rate = pdata->clock_rate; + /* the first 5 PASIC3 registers control the DS1WM */ + ds1wm_resources[0].end = (5 << asic->bus_shift) - 1; + ret = mfd_add_devices(&pdev->dev, pdev->id, + &ds1wm_cell, 1, r, irq, NULL); + if (ret < 0) + dev_warn(dev, "failed to register DS1WM\n"); + } + + if (pdata && pdata->led_pdata) { + led_cell.platform_data = pdata->led_pdata; + led_cell.pdata_size = sizeof(struct pasic3_leds_machinfo); + ret = mfd_add_devices(&pdev->dev, pdev->id, &led_cell, 1, r, + 0, NULL); + if (ret < 0) + dev_warn(dev, "failed to register LED device\n"); + } + + return 0; +} + +static int pasic3_remove(struct platform_device *pdev) +{ + struct pasic3_data *asic = platform_get_drvdata(pdev); + struct resource *r; + + mfd_remove_devices(&pdev->dev); + + iounmap(asic->mapping); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + return 0; +} + +MODULE_ALIAS("platform:pasic3"); + +static struct platform_driver pasic3_driver = { + .driver = { + .name = "pasic3", + }, + .remove = pasic3_remove, +}; + +module_platform_driver_probe(pasic3_driver, pasic3_probe); + +MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); +MODULE_DESCRIPTION("Core driver for HTC PASIC3"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/intel_msic.c b/kernel/drivers/mfd/intel_msic.c new file mode 100644 index 000000000..25d486c54 --- /dev/null +++ b/kernel/drivers/mfd/intel_msic.c @@ -0,0 +1,457 @@ +/* + * Driver for Intel MSIC + * + * Copyright (C) 2011, Intel Corporation + * Author: Mika Westerberg <mika.westerberg@linux.intel.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. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/mfd/intel_msic.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <asm/intel_scu_ipc.h> + +#define MSIC_VENDOR(id) ((id >> 6) & 3) +#define MSIC_VERSION(id) (id & 0x3f) +#define MSIC_MAJOR(id) ('A' + ((id >> 3) & 7)) +#define MSIC_MINOR(id) (id & 7) + +/* + * MSIC interrupt tree is readable from SRAM at INTEL_MSIC_IRQ_PHYS_BASE. + * Since IRQ block starts from address 0x002 we need to subtract that from + * the actual IRQ status register address. + */ +#define MSIC_IRQ_STATUS(x) (INTEL_MSIC_IRQ_PHYS_BASE + ((x) - 2)) +#define MSIC_IRQ_STATUS_ACCDET MSIC_IRQ_STATUS(INTEL_MSIC_ACCDET) + +/* + * The SCU hardware has limitation of 16 bytes per read/write buffer on + * Medfield. + */ +#define SCU_IPC_RWBUF_LIMIT 16 + +/** + * struct intel_msic - an MSIC MFD instance + * @pdev: pointer to the platform device + * @vendor: vendor ID + * @version: chip version + * @irq_base: base address of the mapped MSIC SRAM interrupt tree + */ +struct intel_msic { + struct platform_device *pdev; + unsigned vendor; + unsigned version; + void __iomem *irq_base; +}; + +static struct resource msic_touch_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_adc_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_battery_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_gpio_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_audio_resources[] = { + { + .name = "IRQ", + .flags = IORESOURCE_IRQ, + }, + /* + * We will pass IRQ_BASE to the driver now but this can be removed + * when/if the driver starts to use intel_msic_irq_read(). + */ + { + .name = "IRQ_BASE", + .flags = IORESOURCE_MEM, + .start = MSIC_IRQ_STATUS_ACCDET, + .end = MSIC_IRQ_STATUS_ACCDET, + }, +}; + +static struct resource msic_hdmi_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_thermal_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_power_btn_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msic_ocd_resources[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +/* + * Devices that are part of the MSIC and are available via firmware + * populated SFI DEVS table. + */ +static struct mfd_cell msic_devs[] = { + [INTEL_MSIC_BLOCK_TOUCH] = { + .name = "msic_touch", + .num_resources = ARRAY_SIZE(msic_touch_resources), + .resources = msic_touch_resources, + }, + [INTEL_MSIC_BLOCK_ADC] = { + .name = "msic_adc", + .num_resources = ARRAY_SIZE(msic_adc_resources), + .resources = msic_adc_resources, + }, + [INTEL_MSIC_BLOCK_BATTERY] = { + .name = "msic_battery", + .num_resources = ARRAY_SIZE(msic_battery_resources), + .resources = msic_battery_resources, + }, + [INTEL_MSIC_BLOCK_GPIO] = { + .name = "msic_gpio", + .num_resources = ARRAY_SIZE(msic_gpio_resources), + .resources = msic_gpio_resources, + }, + [INTEL_MSIC_BLOCK_AUDIO] = { + .name = "msic_audio", + .num_resources = ARRAY_SIZE(msic_audio_resources), + .resources = msic_audio_resources, + }, + [INTEL_MSIC_BLOCK_HDMI] = { + .name = "msic_hdmi", + .num_resources = ARRAY_SIZE(msic_hdmi_resources), + .resources = msic_hdmi_resources, + }, + [INTEL_MSIC_BLOCK_THERMAL] = { + .name = "msic_thermal", + .num_resources = ARRAY_SIZE(msic_thermal_resources), + .resources = msic_thermal_resources, + }, + [INTEL_MSIC_BLOCK_POWER_BTN] = { + .name = "msic_power_btn", + .num_resources = ARRAY_SIZE(msic_power_btn_resources), + .resources = msic_power_btn_resources, + }, + [INTEL_MSIC_BLOCK_OCD] = { + .name = "msic_ocd", + .num_resources = ARRAY_SIZE(msic_ocd_resources), + .resources = msic_ocd_resources, + }, +}; + +/* + * Other MSIC related devices which are not directly available via SFI DEVS + * table. These can be pseudo devices, regulators etc. which are needed for + * different purposes. + * + * These devices appear only after the MSIC driver itself is initialized so + * we can guarantee that the SCU IPC interface is ready. + */ +static const struct mfd_cell msic_other_devs[] = { + /* Audio codec in the MSIC */ + { + .id = -1, + .name = "sn95031", + }, +}; + +/** + * intel_msic_reg_read - read a single MSIC register + * @reg: register to read + * @val: register value is placed here + * + * Read a single register from MSIC. Returns %0 on success and negative + * errno in case of failure. + * + * Function may sleep. + */ +int intel_msic_reg_read(unsigned short reg, u8 *val) +{ + return intel_scu_ipc_ioread8(reg, val); +} +EXPORT_SYMBOL_GPL(intel_msic_reg_read); + +/** + * intel_msic_reg_write - write a single MSIC register + * @reg: register to write + * @val: value to write to that register + * + * Write a single MSIC register. Returns 0 on success and negative + * errno in case of failure. + * + * Function may sleep. + */ +int intel_msic_reg_write(unsigned short reg, u8 val) +{ + return intel_scu_ipc_iowrite8(reg, val); +} +EXPORT_SYMBOL_GPL(intel_msic_reg_write); + +/** + * intel_msic_reg_update - update a single MSIC register + * @reg: register to update + * @val: value to write to the register + * @mask: specifies which of the bits are updated (%0 = don't update, + * %1 = update) + * + * Perform an update to a register @reg. @mask is used to specify which + * bits are updated. Returns %0 in case of success and negative errno in + * case of failure. + * + * Function may sleep. + */ +int intel_msic_reg_update(unsigned short reg, u8 val, u8 mask) +{ + return intel_scu_ipc_update_register(reg, val, mask); +} +EXPORT_SYMBOL_GPL(intel_msic_reg_update); + +/** + * intel_msic_bulk_read - read an array of registers + * @reg: array of register addresses to read + * @buf: array where the read values are placed + * @count: number of registers to read + * + * Function reads @count registers from the MSIC using addresses passed in + * @reg. Read values are placed in @buf. Reads are performed atomically + * wrt. MSIC. + * + * Returns %0 in case of success and negative errno in case of failure. + * + * Function may sleep. + */ +int intel_msic_bulk_read(unsigned short *reg, u8 *buf, size_t count) +{ + if (WARN_ON(count > SCU_IPC_RWBUF_LIMIT)) + return -EINVAL; + + return intel_scu_ipc_readv(reg, buf, count); +} +EXPORT_SYMBOL_GPL(intel_msic_bulk_read); + +/** + * intel_msic_bulk_write - write an array of values to the MSIC registers + * @reg: array of registers to write + * @buf: values to write to each register + * @count: number of registers to write + * + * Function writes @count registers in @buf to MSIC. Writes are performed + * atomically wrt MSIC. Returns %0 in case of success and negative errno in + * case of failure. + * + * Function may sleep. + */ +int intel_msic_bulk_write(unsigned short *reg, u8 *buf, size_t count) +{ + if (WARN_ON(count > SCU_IPC_RWBUF_LIMIT)) + return -EINVAL; + + return intel_scu_ipc_writev(reg, buf, count); +} +EXPORT_SYMBOL_GPL(intel_msic_bulk_write); + +/** + * intel_msic_irq_read - read a register from an MSIC interrupt tree + * @msic: MSIC instance + * @reg: interrupt register (between %INTEL_MSIC_IRQLVL1 and + * %INTEL_MSIC_RESETIRQ2) + * @val: value of the register is placed here + * + * This function can be used by an MSIC subdevice interrupt handler to read + * a register value from the MSIC interrupt tree. In this way subdevice + * drivers don't have to map in the interrupt tree themselves but can just + * call this function instead. + * + * Function doesn't sleep and is callable from interrupt context. + * + * Returns %-EINVAL if @reg is outside of the allowed register region. + */ +int intel_msic_irq_read(struct intel_msic *msic, unsigned short reg, u8 *val) +{ + if (WARN_ON(reg < INTEL_MSIC_IRQLVL1 || reg > INTEL_MSIC_RESETIRQ2)) + return -EINVAL; + + *val = readb(msic->irq_base + (reg - INTEL_MSIC_IRQLVL1)); + return 0; +} +EXPORT_SYMBOL_GPL(intel_msic_irq_read); + +static int intel_msic_init_devices(struct intel_msic *msic) +{ + struct platform_device *pdev = msic->pdev; + struct intel_msic_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret, i; + + if (pdata->gpio) { + struct mfd_cell *cell = &msic_devs[INTEL_MSIC_BLOCK_GPIO]; + + cell->platform_data = pdata->gpio; + cell->pdata_size = sizeof(*pdata->gpio); + } + + if (pdata->ocd) { + unsigned gpio = pdata->ocd->gpio; + + ret = devm_gpio_request_one(&pdev->dev, gpio, + GPIOF_IN, "ocd_gpio"); + if (ret) { + dev_err(&pdev->dev, "failed to register OCD GPIO\n"); + return ret; + } + + ret = gpio_to_irq(gpio); + if (ret < 0) { + dev_err(&pdev->dev, "no IRQ number for OCD GPIO\n"); + return ret; + } + + /* Update the IRQ number for the OCD */ + pdata->irq[INTEL_MSIC_BLOCK_OCD] = ret; + } + + for (i = 0; i < ARRAY_SIZE(msic_devs); i++) { + if (!pdata->irq[i]) + continue; + + ret = mfd_add_devices(&pdev->dev, -1, &msic_devs[i], 1, NULL, + pdata->irq[i], NULL); + if (ret) + goto fail; + } + + ret = mfd_add_devices(&pdev->dev, 0, msic_other_devs, + ARRAY_SIZE(msic_other_devs), NULL, 0, NULL); + if (ret) + goto fail; + + return 0; + +fail: + mfd_remove_devices(&pdev->dev); + + return ret; +} + +static void intel_msic_remove_devices(struct intel_msic *msic) +{ + struct platform_device *pdev = msic->pdev; + + mfd_remove_devices(&pdev->dev); +} + +static int intel_msic_probe(struct platform_device *pdev) +{ + struct intel_msic_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct intel_msic *msic; + struct resource *res; + u8 id0, id1; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data passed\n"); + return -EINVAL; + } + + /* First validate that we have an MSIC in place */ + ret = intel_scu_ipc_ioread8(INTEL_MSIC_ID0, &id0); + if (ret) { + dev_err(&pdev->dev, "failed to identify the MSIC chip (ID0)\n"); + return -ENXIO; + } + + ret = intel_scu_ipc_ioread8(INTEL_MSIC_ID1, &id1); + if (ret) { + dev_err(&pdev->dev, "failed to identify the MSIC chip (ID1)\n"); + return -ENXIO; + } + + if (MSIC_VENDOR(id0) != MSIC_VENDOR(id1)) { + dev_err(&pdev->dev, "invalid vendor ID: %x, %x\n", id0, id1); + return -ENXIO; + } + + msic = devm_kzalloc(&pdev->dev, sizeof(*msic), GFP_KERNEL); + if (!msic) + return -ENOMEM; + + msic->vendor = MSIC_VENDOR(id0); + msic->version = MSIC_VERSION(id0); + msic->pdev = pdev; + + /* + * Map in the MSIC interrupt tree area in SRAM. This is exposed to + * the clients via intel_msic_irq_read(). + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + msic->irq_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(msic->irq_base)) + return PTR_ERR(msic->irq_base); + + platform_set_drvdata(pdev, msic); + + ret = intel_msic_init_devices(msic); + if (ret) { + dev_err(&pdev->dev, "failed to initialize MSIC devices\n"); + return ret; + } + + dev_info(&pdev->dev, "Intel MSIC version %c%d (vendor %#x)\n", + MSIC_MAJOR(msic->version), MSIC_MINOR(msic->version), + msic->vendor); + + return 0; +} + +static int intel_msic_remove(struct platform_device *pdev) +{ + struct intel_msic *msic = platform_get_drvdata(pdev); + + intel_msic_remove_devices(msic); + + return 0; +} + +static struct platform_driver intel_msic_driver = { + .probe = intel_msic_probe, + .remove = intel_msic_remove, + .driver = { + .name = "intel_msic", + }, +}; + +module_platform_driver(intel_msic_driver); + +MODULE_DESCRIPTION("Driver for Intel MSIC"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/intel_quark_i2c_gpio.c b/kernel/drivers/mfd/intel_quark_i2c_gpio.c new file mode 100644 index 000000000..1ce16037d --- /dev/null +++ b/kernel/drivers/mfd/intel_quark_i2c_gpio.c @@ -0,0 +1,282 @@ +/* + * Intel Quark MFD PCI driver for I2C & GPIO + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * Intel Quark PCI device for I2C and GPIO controller sharing the same + * PCI function. This PCI driver will split the 2 devices into their + * respective drivers. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/mfd/core.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/dmi.h> +#include <linux/platform_data/gpio-dwapb.h> +#include <linux/platform_data/i2c-designware.h> + +/* PCI BAR for register base address */ +#define MFD_I2C_BAR 0 +#define MFD_GPIO_BAR 1 + +/* The base GPIO number under GPIOLIB framework */ +#define INTEL_QUARK_MFD_GPIO_BASE 8 + +/* The default number of South-Cluster GPIO on Quark. */ +#define INTEL_QUARK_MFD_NGPIO 8 + +/* The DesignWare GPIO ports on Quark. */ +#define INTEL_QUARK_GPIO_NPORTS 1 + +#define INTEL_QUARK_IORES_MEM 0 +#define INTEL_QUARK_IORES_IRQ 1 + +#define INTEL_QUARK_I2C_CONTROLLER_CLK "i2c_designware.0" + +/* The Quark I2C controller source clock */ +#define INTEL_QUARK_I2C_CLK_HZ 33000000 + +#define INTEL_QUARK_I2C_NCLK 1 + +struct intel_quark_mfd { + struct pci_dev *pdev; + struct clk *i2c_clk; + struct clk_lookup *i2c_clk_lookup; +}; + +struct i2c_mode_info { + const char *name; + unsigned int i2c_scl_freq; +}; + +static const struct i2c_mode_info platform_i2c_mode_info[] = { + { + .name = "Galileo", + .i2c_scl_freq = 100000, + }, + { + .name = "GalileoGen2", + .i2c_scl_freq = 400000, + }, + {} +}; + +static struct resource intel_quark_i2c_res[] = { + [INTEL_QUARK_IORES_MEM] = { + .flags = IORESOURCE_MEM, + }, + [INTEL_QUARK_IORES_IRQ] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource intel_quark_gpio_res[] = { + [INTEL_QUARK_IORES_MEM] = { + .flags = IORESOURCE_MEM, + }, +}; + +static struct mfd_cell intel_quark_mfd_cells[] = { + { + .id = MFD_I2C_BAR, + .name = "i2c_designware", + .num_resources = ARRAY_SIZE(intel_quark_i2c_res), + .resources = intel_quark_i2c_res, + .ignore_resource_conflicts = true, + }, + { + .id = MFD_GPIO_BAR, + .name = "gpio-dwapb", + .num_resources = ARRAY_SIZE(intel_quark_gpio_res), + .resources = intel_quark_gpio_res, + .ignore_resource_conflicts = true, + }, +}; + +static const struct pci_device_id intel_quark_mfd_ids[] = { + { PCI_VDEVICE(INTEL, 0x0934), }, + {}, +}; +MODULE_DEVICE_TABLE(pci, intel_quark_mfd_ids); + +static int intel_quark_register_i2c_clk(struct intel_quark_mfd *quark_mfd) +{ + struct pci_dev *pdev = quark_mfd->pdev; + struct clk_lookup *i2c_clk_lookup; + struct clk *i2c_clk; + int ret; + + i2c_clk_lookup = devm_kcalloc(&pdev->dev, INTEL_QUARK_I2C_NCLK, + sizeof(*i2c_clk_lookup), GFP_KERNEL); + if (!i2c_clk_lookup) + return -ENOMEM; + + i2c_clk_lookup[0].dev_id = INTEL_QUARK_I2C_CONTROLLER_CLK; + + i2c_clk = clk_register_fixed_rate(&pdev->dev, + INTEL_QUARK_I2C_CONTROLLER_CLK, NULL, + CLK_IS_ROOT, INTEL_QUARK_I2C_CLK_HZ); + + quark_mfd->i2c_clk_lookup = i2c_clk_lookup; + quark_mfd->i2c_clk = i2c_clk; + + ret = clk_register_clkdevs(i2c_clk, i2c_clk_lookup, + INTEL_QUARK_I2C_NCLK); + if (ret) + dev_err(&pdev->dev, "Fixed clk register failed: %d\n", ret); + + return ret; +} + +static void intel_quark_unregister_i2c_clk(struct pci_dev *pdev) +{ + struct intel_quark_mfd *quark_mfd = dev_get_drvdata(&pdev->dev); + + if (!quark_mfd->i2c_clk || !quark_mfd->i2c_clk_lookup) + return; + + clkdev_drop(quark_mfd->i2c_clk_lookup); + clk_unregister(quark_mfd->i2c_clk); +} + +static int intel_quark_i2c_setup(struct pci_dev *pdev, struct mfd_cell *cell) +{ + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + const struct i2c_mode_info *info; + struct dw_i2c_platform_data *pdata; + struct resource *res = (struct resource *)cell->resources; + struct device *dev = &pdev->dev; + + res[INTEL_QUARK_IORES_MEM].start = + pci_resource_start(pdev, MFD_I2C_BAR); + res[INTEL_QUARK_IORES_MEM].end = + pci_resource_end(pdev, MFD_I2C_BAR); + + res[INTEL_QUARK_IORES_IRQ].start = pdev->irq; + res[INTEL_QUARK_IORES_IRQ].end = pdev->irq; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + /* Normal mode by default */ + pdata->i2c_scl_freq = 100000; + + if (board_name) { + for (info = platform_i2c_mode_info; info->name; info++) { + if (!strcmp(board_name, info->name)) { + pdata->i2c_scl_freq = info->i2c_scl_freq; + break; + } + } + } + + cell->platform_data = pdata; + cell->pdata_size = sizeof(*pdata); + + return 0; +} + +static int intel_quark_gpio_setup(struct pci_dev *pdev, struct mfd_cell *cell) +{ + struct dwapb_platform_data *pdata; + struct resource *res = (struct resource *)cell->resources; + struct device *dev = &pdev->dev; + + res[INTEL_QUARK_IORES_MEM].start = + pci_resource_start(pdev, MFD_GPIO_BAR); + res[INTEL_QUARK_IORES_MEM].end = + pci_resource_end(pdev, MFD_GPIO_BAR); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + /* For intel quark x1000, it has only one port: portA */ + pdata->nports = INTEL_QUARK_GPIO_NPORTS; + pdata->properties = devm_kcalloc(dev, pdata->nports, + sizeof(*pdata->properties), + GFP_KERNEL); + if (!pdata->properties) + return -ENOMEM; + + /* Set the properties for portA */ + pdata->properties->node = NULL; + pdata->properties->name = "intel-quark-x1000-gpio-portA"; + pdata->properties->idx = 0; + pdata->properties->ngpio = INTEL_QUARK_MFD_NGPIO; + pdata->properties->gpio_base = INTEL_QUARK_MFD_GPIO_BASE; + pdata->properties->irq = pdev->irq; + pdata->properties->irq_shared = true; + + cell->platform_data = pdata; + cell->pdata_size = sizeof(*pdata); + + return 0; +} + +static int intel_quark_mfd_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct intel_quark_mfd *quark_mfd; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + quark_mfd = devm_kzalloc(&pdev->dev, sizeof(*quark_mfd), GFP_KERNEL); + if (!quark_mfd) + return -ENOMEM; + quark_mfd->pdev = pdev; + + ret = intel_quark_register_i2c_clk(quark_mfd); + if (ret) + return ret; + + dev_set_drvdata(&pdev->dev, quark_mfd); + + ret = intel_quark_i2c_setup(pdev, &intel_quark_mfd_cells[MFD_I2C_BAR]); + if (ret) + return ret; + + ret = intel_quark_gpio_setup(pdev, + &intel_quark_mfd_cells[MFD_GPIO_BAR]); + if (ret) + return ret; + + return mfd_add_devices(&pdev->dev, 0, intel_quark_mfd_cells, + ARRAY_SIZE(intel_quark_mfd_cells), NULL, 0, + NULL); +} + +static void intel_quark_mfd_remove(struct pci_dev *pdev) +{ + intel_quark_unregister_i2c_clk(pdev); + mfd_remove_devices(&pdev->dev); +} + +static struct pci_driver intel_quark_mfd_driver = { + .name = "intel_quark_mfd_i2c_gpio", + .id_table = intel_quark_mfd_ids, + .probe = intel_quark_mfd_probe, + .remove = intel_quark_mfd_remove, +}; + +module_pci_driver(intel_quark_mfd_driver); + +MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>"); +MODULE_DESCRIPTION("Intel Quark MFD PCI driver for I2C & GPIO"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/intel_soc_pmic_core.c b/kernel/drivers/mfd/intel_soc_pmic_core.c new file mode 100644 index 000000000..7b50b6b20 --- /dev/null +++ b/kernel/drivers/mfd/intel_soc_pmic_core.c @@ -0,0 +1,175 @@ +/* + * intel_soc_pmic_core.c - Intel SoC PMIC MFD Driver + * + * Copyright (C) 2013, 2014 Intel Corporation. All rights reserved. + * + * 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. + * + * Author: Yang, Bin <bin.yang@intel.com> + * Author: Zhu, Lejun <lejun.zhu@linux.intel.com> + */ + +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/gpio/consumer.h> +#include <linux/acpi.h> +#include <linux/regmap.h> +#include <linux/mfd/intel_soc_pmic.h> +#include "intel_soc_pmic_core.h" + +static int intel_soc_pmic_find_gpio_irq(struct device *dev) +{ + struct gpio_desc *desc; + int irq; + + desc = devm_gpiod_get_index(dev, "intel_soc_pmic", 0, GPIOD_IN); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + irq = gpiod_to_irq(desc); + if (irq < 0) + dev_warn(dev, "Can't get irq: %d\n", irq); + + return irq; +} + +static int intel_soc_pmic_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct device *dev = &i2c->dev; + const struct acpi_device_id *id; + struct intel_soc_pmic_config *config; + struct intel_soc_pmic *pmic; + int ret; + int irq; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id || !id->driver_data) + return -ENODEV; + + config = (struct intel_soc_pmic_config *)id->driver_data; + + pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + dev_set_drvdata(dev, pmic); + + pmic->regmap = devm_regmap_init_i2c(i2c, config->regmap_config); + + /* + * On some boards the PMIC interrupt may come from a GPIO line. Try to + * lookup the ACPI table for a such connection and setup a GPIO + * interrupt if it exists. Otherwise use the IRQ provided by I2C + */ + irq = intel_soc_pmic_find_gpio_irq(dev); + pmic->irq = (irq < 0) ? i2c->irq : irq; + + ret = regmap_add_irq_chip(pmic->regmap, pmic->irq, + config->irq_flags | IRQF_ONESHOT, + 0, config->irq_chip, + &pmic->irq_chip_data); + if (ret) + return ret; + + ret = enable_irq_wake(pmic->irq); + if (ret) + dev_warn(dev, "Can't enable IRQ as wake source: %d\n", ret); + + ret = mfd_add_devices(dev, -1, config->cell_dev, + config->n_cell_devs, NULL, 0, + regmap_irq_get_domain(pmic->irq_chip_data)); + if (ret) + goto err_del_irq_chip; + + return 0; + +err_del_irq_chip: + regmap_del_irq_chip(pmic->irq, pmic->irq_chip_data); + return ret; +} + +static int intel_soc_pmic_i2c_remove(struct i2c_client *i2c) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(&i2c->dev); + + regmap_del_irq_chip(pmic->irq, pmic->irq_chip_data); + + mfd_remove_devices(&i2c->dev); + + return 0; +} + +static void intel_soc_pmic_shutdown(struct i2c_client *i2c) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(&i2c->dev); + + disable_irq(pmic->irq); + + return; +} + +#if defined(CONFIG_PM_SLEEP) +static int intel_soc_pmic_suspend(struct device *dev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(dev); + + disable_irq(pmic->irq); + + return 0; +} + +static int intel_soc_pmic_resume(struct device *dev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(dev); + + enable_irq(pmic->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(intel_soc_pmic_pm_ops, intel_soc_pmic_suspend, + intel_soc_pmic_resume); + +static const struct i2c_device_id intel_soc_pmic_i2c_id[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, intel_soc_pmic_i2c_id); + +#if defined(CONFIG_ACPI) +static struct acpi_device_id intel_soc_pmic_acpi_match[] = { + {"INT33FD", (kernel_ulong_t)&intel_soc_pmic_config_crc}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, intel_soc_pmic_acpi_match); +#endif + +static struct i2c_driver intel_soc_pmic_i2c_driver = { + .driver = { + .name = "intel_soc_pmic_i2c", + .owner = THIS_MODULE, + .pm = &intel_soc_pmic_pm_ops, + .acpi_match_table = ACPI_PTR(intel_soc_pmic_acpi_match), + }, + .probe = intel_soc_pmic_i2c_probe, + .remove = intel_soc_pmic_i2c_remove, + .id_table = intel_soc_pmic_i2c_id, + .shutdown = intel_soc_pmic_shutdown, +}; + +module_i2c_driver(intel_soc_pmic_i2c_driver); + +MODULE_DESCRIPTION("I2C driver for Intel SoC PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yang, Bin <bin.yang@intel.com>"); +MODULE_AUTHOR("Zhu, Lejun <lejun.zhu@linux.intel.com>"); diff --git a/kernel/drivers/mfd/intel_soc_pmic_core.h b/kernel/drivers/mfd/intel_soc_pmic_core.h new file mode 100644 index 000000000..9498d6719 --- /dev/null +++ b/kernel/drivers/mfd/intel_soc_pmic_core.h @@ -0,0 +1,32 @@ +/* + * intel_soc_pmic_core.h - Intel SoC PMIC MFD Driver + * + * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. + * + * 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. + * + * Author: Yang, Bin <bin.yang@intel.com> + * Author: Zhu, Lejun <lejun.zhu@linux.intel.com> + */ + +#ifndef __INTEL_SOC_PMIC_CORE_H__ +#define __INTEL_SOC_PMIC_CORE_H__ + +struct intel_soc_pmic_config { + unsigned long irq_flags; + struct mfd_cell *cell_dev; + int n_cell_devs; + const struct regmap_config *regmap_config; + struct regmap_irq_chip *irq_chip; +}; + +extern struct intel_soc_pmic_config intel_soc_pmic_config_crc; + +#endif /* __INTEL_SOC_PMIC_CORE_H__ */ diff --git a/kernel/drivers/mfd/intel_soc_pmic_crc.c b/kernel/drivers/mfd/intel_soc_pmic_crc.c new file mode 100644 index 000000000..4cc1b324e --- /dev/null +++ b/kernel/drivers/mfd/intel_soc_pmic_crc.c @@ -0,0 +1,161 @@ +/* + * intel_soc_pmic_crc.c - Device access for Crystal Cove PMIC + * + * Copyright (C) 2013, 2014 Intel Corporation. All rights reserved. + * + * 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. + * + * Author: Yang, Bin <bin.yang@intel.com> + * Author: Zhu, Lejun <lejun.zhu@linux.intel.com> + */ + +#include <linux/mfd/core.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/mfd/intel_soc_pmic.h> +#include "intel_soc_pmic_core.h" + +#define CRYSTAL_COVE_MAX_REGISTER 0xC6 + +#define CRYSTAL_COVE_REG_IRQLVL1 0x02 +#define CRYSTAL_COVE_REG_MIRQLVL1 0x0E + +#define CRYSTAL_COVE_IRQ_PWRSRC 0 +#define CRYSTAL_COVE_IRQ_THRM 1 +#define CRYSTAL_COVE_IRQ_BCU 2 +#define CRYSTAL_COVE_IRQ_ADC 3 +#define CRYSTAL_COVE_IRQ_CHGR 4 +#define CRYSTAL_COVE_IRQ_GPIO 5 +#define CRYSTAL_COVE_IRQ_VHDMIOCP 6 + +static struct resource gpio_resources[] = { + { + .name = "GPIO", + .start = CRYSTAL_COVE_IRQ_GPIO, + .end = CRYSTAL_COVE_IRQ_GPIO, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource pwrsrc_resources[] = { + { + .name = "PWRSRC", + .start = CRYSTAL_COVE_IRQ_PWRSRC, + .end = CRYSTAL_COVE_IRQ_PWRSRC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource adc_resources[] = { + { + .name = "ADC", + .start = CRYSTAL_COVE_IRQ_ADC, + .end = CRYSTAL_COVE_IRQ_ADC, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource thermal_resources[] = { + { + .name = "THERMAL", + .start = CRYSTAL_COVE_IRQ_THRM, + .end = CRYSTAL_COVE_IRQ_THRM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource bcu_resources[] = { + { + .name = "BCU", + .start = CRYSTAL_COVE_IRQ_BCU, + .end = CRYSTAL_COVE_IRQ_BCU, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell crystal_cove_dev[] = { + { + .name = "crystal_cove_pwrsrc", + .num_resources = ARRAY_SIZE(pwrsrc_resources), + .resources = pwrsrc_resources, + }, + { + .name = "crystal_cove_adc", + .num_resources = ARRAY_SIZE(adc_resources), + .resources = adc_resources, + }, + { + .name = "crystal_cove_thermal", + .num_resources = ARRAY_SIZE(thermal_resources), + .resources = thermal_resources, + }, + { + .name = "crystal_cove_bcu", + .num_resources = ARRAY_SIZE(bcu_resources), + .resources = bcu_resources, + }, + { + .name = "crystal_cove_gpio", + .num_resources = ARRAY_SIZE(gpio_resources), + .resources = gpio_resources, + }, + { + .name = "crystal_cove_pmic", + }, +}; + +static const struct regmap_config crystal_cove_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CRYSTAL_COVE_MAX_REGISTER, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_irq crystal_cove_irqs[] = { + [CRYSTAL_COVE_IRQ_PWRSRC] = { + .mask = BIT(CRYSTAL_COVE_IRQ_PWRSRC), + }, + [CRYSTAL_COVE_IRQ_THRM] = { + .mask = BIT(CRYSTAL_COVE_IRQ_THRM), + }, + [CRYSTAL_COVE_IRQ_BCU] = { + .mask = BIT(CRYSTAL_COVE_IRQ_BCU), + }, + [CRYSTAL_COVE_IRQ_ADC] = { + .mask = BIT(CRYSTAL_COVE_IRQ_ADC), + }, + [CRYSTAL_COVE_IRQ_CHGR] = { + .mask = BIT(CRYSTAL_COVE_IRQ_CHGR), + }, + [CRYSTAL_COVE_IRQ_GPIO] = { + .mask = BIT(CRYSTAL_COVE_IRQ_GPIO), + }, + [CRYSTAL_COVE_IRQ_VHDMIOCP] = { + .mask = BIT(CRYSTAL_COVE_IRQ_VHDMIOCP), + }, +}; + +static struct regmap_irq_chip crystal_cove_irq_chip = { + .name = "Crystal Cove", + .irqs = crystal_cove_irqs, + .num_irqs = ARRAY_SIZE(crystal_cove_irqs), + .num_regs = 1, + .status_base = CRYSTAL_COVE_REG_IRQLVL1, + .mask_base = CRYSTAL_COVE_REG_MIRQLVL1, +}; + +struct intel_soc_pmic_config intel_soc_pmic_config_crc = { + .irq_flags = IRQF_TRIGGER_RISING, + .cell_dev = crystal_cove_dev, + .n_cell_devs = ARRAY_SIZE(crystal_cove_dev), + .regmap_config = &crystal_cove_regmap_config, + .irq_chip = &crystal_cove_irq_chip, +}; diff --git a/kernel/drivers/mfd/ipaq-micro.c b/kernel/drivers/mfd/ipaq-micro.c new file mode 100644 index 000000000..8df326606 --- /dev/null +++ b/kernel/drivers/mfd/ipaq-micro.c @@ -0,0 +1,482 @@ +/* + * Compaq iPAQ h3xxx Atmel microcontroller companion support + * + * This is an Atmel AT90LS8535 with a special flashed-in firmware that + * implements the special protocol used by this driver. + * + * based on previous kernel 2.4 version by Andrew Christian + * Author : Alessandro Gardich <gremlin@gremlin.it> + * Author : Dmitry Artamonow <mad_soft@inbox.ru> + * Author : Linus Walleij <linus.walleij@linaro.org> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/mfd/core.h> +#include <linux/mfd/ipaq-micro.h> +#include <linux/string.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/list.h> + +#include <mach/hardware.h> + +static void ipaq_micro_trigger_tx(struct ipaq_micro *micro) +{ + struct ipaq_micro_txdev *tx = µ->tx; + struct ipaq_micro_msg *msg = micro->msg; + int i, bp; + u8 checksum; + u32 val; + + bp = 0; + tx->buf[bp++] = CHAR_SOF; + + checksum = ((msg->id & 0x0f) << 4) | (msg->tx_len & 0x0f); + tx->buf[bp++] = checksum; + + for (i = 0; i < msg->tx_len; i++) { + tx->buf[bp++] = msg->tx_data[i]; + checksum += msg->tx_data[i]; + } + + tx->buf[bp++] = checksum; + tx->len = bp; + tx->index = 0; + print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_OFFSET, 16, 1, + tx->buf, tx->len, true); + + /* Enable interrupt */ + val = readl(micro->base + UTCR3); + val |= UTCR3_TIE; + writel(val, micro->base + UTCR3); +} + +int ipaq_micro_tx_msg(struct ipaq_micro *micro, struct ipaq_micro_msg *msg) +{ + unsigned long flags; + + dev_dbg(micro->dev, "TX msg: %02x, %d bytes\n", msg->id, msg->tx_len); + + spin_lock_irqsave(µ->lock, flags); + if (micro->msg) { + list_add_tail(&msg->node, µ->queue); + spin_unlock_irqrestore(µ->lock, flags); + return 0; + } + micro->msg = msg; + ipaq_micro_trigger_tx(micro); + spin_unlock_irqrestore(µ->lock, flags); + return 0; +} +EXPORT_SYMBOL(ipaq_micro_tx_msg); + +static void micro_rx_msg(struct ipaq_micro *micro, u8 id, int len, u8 *data) +{ + int i; + + dev_dbg(micro->dev, "RX msg: %02x, %d bytes\n", id, len); + + spin_lock(µ->lock); + switch (id) { + case MSG_VERSION: + case MSG_EEPROM_READ: + case MSG_EEPROM_WRITE: + case MSG_BACKLIGHT: + case MSG_NOTIFY_LED: + case MSG_THERMAL_SENSOR: + case MSG_BATTERY: + /* Handle synchronous messages */ + if (micro->msg && micro->msg->id == id) { + struct ipaq_micro_msg *msg = micro->msg; + + memcpy(msg->rx_data, data, len); + msg->rx_len = len; + complete(µ->msg->ack); + if (!list_empty(µ->queue)) { + micro->msg = list_entry(micro->queue.next, + struct ipaq_micro_msg, + node); + list_del_init(µ->msg->node); + ipaq_micro_trigger_tx(micro); + } else + micro->msg = NULL; + dev_dbg(micro->dev, "OK RX message 0x%02x\n", id); + } else { + dev_err(micro->dev, + "out of band RX message 0x%02x\n", id); + if (!micro->msg) + dev_info(micro->dev, "no message queued\n"); + else + dev_info(micro->dev, "expected message %02x\n", + micro->msg->id); + } + break; + case MSG_KEYBOARD: + if (micro->key) + micro->key(micro->key_data, len, data); + else + dev_dbg(micro->dev, "key message ignored, no handle\n"); + break; + case MSG_TOUCHSCREEN: + if (micro->ts) + micro->ts(micro->ts_data, len, data); + else + dev_dbg(micro->dev, "touchscreen message ignored, no handle\n"); + break; + default: + dev_err(micro->dev, + "unknown msg %d [%d] ", id, len); + for (i = 0; i < len; ++i) + pr_cont("0x%02x ", data[i]); + pr_cont("\n"); + } + spin_unlock(µ->lock); +} + +static void micro_process_char(struct ipaq_micro *micro, u8 ch) +{ + struct ipaq_micro_rxdev *rx = µ->rx; + + switch (rx->state) { + case STATE_SOF: /* Looking for SOF */ + if (ch == CHAR_SOF) + rx->state = STATE_ID; /* Next byte is the id and len */ + break; + case STATE_ID: /* Looking for id and len byte */ + rx->id = (ch & 0xf0) >> 4; + rx->len = (ch & 0x0f); + rx->index = 0; + rx->chksum = ch; + rx->state = (rx->len > 0) ? STATE_DATA : STATE_CHKSUM; + break; + case STATE_DATA: /* Looking for 'len' data bytes */ + rx->chksum += ch; + rx->buf[rx->index] = ch; + if (++rx->index == rx->len) + rx->state = STATE_CHKSUM; + break; + case STATE_CHKSUM: /* Looking for the checksum */ + if (ch == rx->chksum) + micro_rx_msg(micro, rx->id, rx->len, rx->buf); + rx->state = STATE_SOF; + break; + } +} + +static void micro_rx_chars(struct ipaq_micro *micro) +{ + u32 status, ch; + + while ((status = readl(micro->base + UTSR1)) & UTSR1_RNE) { + ch = readl(micro->base + UTDR); + if (status & UTSR1_PRE) + dev_err(micro->dev, "rx: parity error\n"); + else if (status & UTSR1_FRE) + dev_err(micro->dev, "rx: framing error\n"); + else if (status & UTSR1_ROR) + dev_err(micro->dev, "rx: overrun error\n"); + micro_process_char(micro, ch); + } +} + +static void ipaq_micro_get_version(struct ipaq_micro *micro) +{ + struct ipaq_micro_msg msg = { + .id = MSG_VERSION, + }; + + ipaq_micro_tx_msg_sync(micro, &msg); + if (msg.rx_len == 4) { + memcpy(micro->version, msg.rx_data, 4); + micro->version[4] = '\0'; + } else if (msg.rx_len == 9) { + memcpy(micro->version, msg.rx_data, 4); + micro->version[4] = '\0'; + /* Bytes 4-7 are "pack", byte 8 is "boot type" */ + } else { + dev_err(micro->dev, + "illegal version message %d bytes\n", msg.rx_len); + } +} + +static void ipaq_micro_eeprom_read(struct ipaq_micro *micro, + u8 address, u8 len, u8 *data) +{ + struct ipaq_micro_msg msg = { + .id = MSG_EEPROM_READ, + }; + u8 i; + + for (i = 0; i < len; i++) { + msg.tx_data[0] = address + i; + msg.tx_data[1] = 1; + msg.tx_len = 2; + ipaq_micro_tx_msg_sync(micro, &msg); + memcpy(data + (i * 2), msg.rx_data, 2); + } +} + +static char *ipaq_micro_str(u8 *wchar, u8 len) +{ + char retstr[256]; + u8 i; + + for (i = 0; i < len / 2; i++) + retstr[i] = wchar[i * 2]; + return kstrdup(retstr, GFP_KERNEL); +} + +static u16 ipaq_micro_to_u16(u8 *data) +{ + return data[1] << 8 | data[0]; +} + +static void ipaq_micro_eeprom_dump(struct ipaq_micro *micro) +{ + u8 dump[256]; + char *str; + + ipaq_micro_eeprom_read(micro, 0, 128, dump); + str = ipaq_micro_str(dump, 10); + if (str) { + dev_info(micro->dev, "HM version %s\n", str); + kfree(str); + } + str = ipaq_micro_str(dump+10, 40); + if (str) { + dev_info(micro->dev, "serial number: %s\n", str); + /* Feed the random pool with this */ + add_device_randomness(str, strlen(str)); + kfree(str); + } + str = ipaq_micro_str(dump+50, 20); + if (str) { + dev_info(micro->dev, "module ID: %s\n", str); + kfree(str); + } + str = ipaq_micro_str(dump+70, 10); + if (str) { + dev_info(micro->dev, "product revision: %s\n", str); + kfree(str); + } + dev_info(micro->dev, "product ID: %u\n", ipaq_micro_to_u16(dump+80)); + dev_info(micro->dev, "frame rate: %u fps\n", + ipaq_micro_to_u16(dump+82)); + dev_info(micro->dev, "page mode: %u\n", ipaq_micro_to_u16(dump+84)); + dev_info(micro->dev, "country ID: %u\n", ipaq_micro_to_u16(dump+86)); + dev_info(micro->dev, "color display: %s\n", + ipaq_micro_to_u16(dump+88) ? "yes" : "no"); + dev_info(micro->dev, "ROM size: %u MiB\n", ipaq_micro_to_u16(dump+90)); + dev_info(micro->dev, "RAM size: %u KiB\n", ipaq_micro_to_u16(dump+92)); + dev_info(micro->dev, "screen: %u x %u\n", + ipaq_micro_to_u16(dump+94), ipaq_micro_to_u16(dump+96)); + print_hex_dump(KERN_DEBUG, "eeprom: ", DUMP_PREFIX_OFFSET, 16, 1, + dump, 256, true); + +} + +static void micro_tx_chars(struct ipaq_micro *micro) +{ + struct ipaq_micro_txdev *tx = µ->tx; + u32 val; + + while ((tx->index < tx->len) && + (readl(micro->base + UTSR1) & UTSR1_TNF)) { + writel(tx->buf[tx->index], micro->base + UTDR); + tx->index++; + } + + /* Stop interrupts */ + val = readl(micro->base + UTCR3); + val &= ~UTCR3_TIE; + writel(val, micro->base + UTCR3); +} + +static void micro_reset_comm(struct ipaq_micro *micro) +{ + struct ipaq_micro_rxdev *rx = µ->rx; + u32 val; + + if (micro->msg) + complete(µ->msg->ack); + + /* Initialize Serial channel protocol frame */ + rx->state = STATE_SOF; /* Reset the state machine */ + + /* Set up interrupts */ + writel(0x01, micro->sdlc + 0x0); /* Select UART mode */ + + /* Clean up CR3 */ + writel(0x0, micro->base + UTCR3); + + /* Format: 8N1 */ + writel(UTCR0_8BitData | UTCR0_1StpBit, micro->base + UTCR0); + + /* Baud rate: 115200 */ + writel(0x0, micro->base + UTCR1); + writel(0x1, micro->base + UTCR2); + + /* Clear SR0 */ + writel(0xff, micro->base + UTSR0); + + /* Enable RX int, disable TX int */ + writel(UTCR3_TXE | UTCR3_RXE | UTCR3_RIE, micro->base + UTCR3); + val = readl(micro->base + UTCR3); + val &= ~UTCR3_TIE; + writel(val, micro->base + UTCR3); +} + +static irqreturn_t micro_serial_isr(int irq, void *dev_id) +{ + struct ipaq_micro *micro = dev_id; + struct ipaq_micro_txdev *tx = µ->tx; + u32 status; + + status = readl(micro->base + UTSR0); + do { + if (status & (UTSR0_RID | UTSR0_RFS)) { + if (status & UTSR0_RID) + /* Clear the Receiver IDLE bit */ + writel(UTSR0_RID, micro->base + UTSR0); + micro_rx_chars(micro); + } + + /* Clear break bits */ + if (status & (UTSR0_RBB | UTSR0_REB)) + writel(status & (UTSR0_RBB | UTSR0_REB), + micro->base + UTSR0); + + if (status & UTSR0_TFS) + micro_tx_chars(micro); + + status = readl(micro->base + UTSR0); + + } while (((tx->index < tx->len) && (status & UTSR0_TFS)) || + (status & (UTSR0_RFS | UTSR0_RID))); + + return IRQ_HANDLED; +} + +static const struct mfd_cell micro_cells[] = { + { .name = "ipaq-micro-backlight", }, + { .name = "ipaq-micro-battery", }, + { .name = "ipaq-micro-keys", }, + { .name = "ipaq-micro-ts", }, + { .name = "ipaq-micro-leds", }, +}; + +static int micro_resume(struct device *dev) +{ + struct ipaq_micro *micro = dev_get_drvdata(dev); + + micro_reset_comm(micro); + mdelay(10); + + return 0; +} + +static int micro_probe(struct platform_device *pdev) +{ + struct ipaq_micro *micro; + struct resource *res; + int ret; + int irq; + + micro = devm_kzalloc(&pdev->dev, sizeof(*micro), GFP_KERNEL); + if (!micro) + return -ENOMEM; + + micro->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + micro->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(micro->base)) + return PTR_ERR(micro->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -EINVAL; + + micro->sdlc = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(micro->sdlc)) + return PTR_ERR(micro->sdlc); + + micro_reset_comm(micro); + + irq = platform_get_irq(pdev, 0); + if (!irq) + return -EINVAL; + ret = devm_request_irq(&pdev->dev, irq, micro_serial_isr, + IRQF_SHARED, "ipaq-micro", + micro); + if (ret) { + dev_err(&pdev->dev, "unable to grab serial port IRQ\n"); + return ret; + } else + dev_info(&pdev->dev, "grabbed serial port IRQ\n"); + + spin_lock_init(µ->lock); + INIT_LIST_HEAD(µ->queue); + platform_set_drvdata(pdev, micro); + + ret = mfd_add_devices(&pdev->dev, pdev->id, micro_cells, + ARRAY_SIZE(micro_cells), NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "error adding MFD cells"); + return ret; + } + + /* Check version */ + ipaq_micro_get_version(micro); + dev_info(&pdev->dev, "Atmel micro ASIC version %s\n", micro->version); + ipaq_micro_eeprom_dump(micro); + + return 0; +} + +static int micro_remove(struct platform_device *pdev) +{ + struct ipaq_micro *micro = platform_get_drvdata(pdev); + u32 val; + + mfd_remove_devices(&pdev->dev); + + val = readl(micro->base + UTCR3); + val &= ~(UTCR3_RXE | UTCR3_RIE); /* disable receive interrupt */ + val &= ~(UTCR3_TXE | UTCR3_TIE); /* disable transmit interrupt */ + writel(val, micro->base + UTCR3); + + return 0; +} + +static const struct dev_pm_ops micro_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(NULL, micro_resume) +}; + +static struct platform_driver micro_device_driver = { + .driver = { + .name = "ipaq-h3xxx-micro", + .pm = µ_dev_pm_ops, + }, + .probe = micro_probe, + .remove = micro_remove, + /* .shutdown = micro_suspend, // FIXME */ +}; +module_platform_driver(micro_device_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("driver for iPAQ Atmel micro core and backlight"); diff --git a/kernel/drivers/mfd/janz-cmodio.c b/kernel/drivers/mfd/janz-cmodio.c new file mode 100644 index 000000000..433f82303 --- /dev/null +++ b/kernel/drivers/mfd/janz-cmodio.c @@ -0,0 +1,285 @@ +/* + * Janz CMOD-IO MODULbus Carrier Board PCI Driver + * + * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu> + * + * Lots of inspiration and code was copied from drivers/mfd/sm501.c + * + * 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/pci.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mfd/core.h> + +#include <linux/mfd/janz.h> + +#define DRV_NAME "janz-cmodio" + +/* Size of each MODULbus module in PCI BAR4 */ +#define CMODIO_MODULBUS_SIZE 0x200 + +/* Maximum number of MODULbus modules on a CMOD-IO carrier board */ +#define CMODIO_MAX_MODULES 4 + +/* Module Parameters */ +static unsigned int num_modules = CMODIO_MAX_MODULES; +static char *modules[CMODIO_MAX_MODULES] = { + "empty", "empty", "empty", "empty", +}; + +module_param_array(modules, charp, &num_modules, S_IRUGO); +MODULE_PARM_DESC(modules, "MODULbus modules attached to the carrier board"); + +/* Unique Device Id */ +static unsigned int cmodio_id; + +struct cmodio_device { + /* Parent PCI device */ + struct pci_dev *pdev; + + /* PLX control registers */ + struct janz_cmodio_onboard_regs __iomem *ctrl; + + /* hex switch position */ + u8 hex; + + /* mfd-core API */ + struct mfd_cell cells[CMODIO_MAX_MODULES]; + struct resource resources[3 * CMODIO_MAX_MODULES]; + struct janz_platform_data pdata[CMODIO_MAX_MODULES]; +}; + +/* + * Subdevices using the mfd-core API + */ + +static int cmodio_setup_subdevice(struct cmodio_device *priv, + char *name, unsigned int devno, + unsigned int modno) +{ + struct janz_platform_data *pdata; + struct mfd_cell *cell; + struct resource *res; + struct pci_dev *pci; + + pci = priv->pdev; + cell = &priv->cells[devno]; + res = &priv->resources[devno * 3]; + pdata = &priv->pdata[devno]; + + cell->name = name; + cell->resources = res; + cell->num_resources = 3; + + /* Setup the subdevice ID -- must be unique */ + cell->id = cmodio_id++; + + /* Add platform data */ + pdata->modno = modno; + cell->platform_data = pdata; + cell->pdata_size = sizeof(*pdata); + + /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */ + res->flags = IORESOURCE_MEM; + res->parent = &pci->resource[3]; + res->start = pci->resource[3].start + (CMODIO_MODULBUS_SIZE * modno); + res->end = res->start + CMODIO_MODULBUS_SIZE - 1; + res++; + + /* PLX Control Registers -- PCI BAR4 is interrupt and other registers */ + res->flags = IORESOURCE_MEM; + res->parent = &pci->resource[4]; + res->start = pci->resource[4].start; + res->end = pci->resource[4].end; + res++; + + /* + * IRQ + * + * The start and end fields are used as an offset to the irq_base + * parameter passed into the mfd_add_devices() function call. All + * devices share the same IRQ. + */ + res->flags = IORESOURCE_IRQ; + res->parent = NULL; + res->start = 0; + res->end = 0; + res++; + + return 0; +} + +/* Probe each submodule using kernel parameters */ +static int cmodio_probe_submodules(struct cmodio_device *priv) +{ + struct pci_dev *pdev = priv->pdev; + unsigned int num_probed = 0; + char *name; + int i; + + for (i = 0; i < num_modules; i++) { + name = modules[i]; + if (!strcmp(name, "") || !strcmp(name, "empty")) + continue; + + dev_dbg(&priv->pdev->dev, "MODULbus %d: name %s\n", i, name); + cmodio_setup_subdevice(priv, name, num_probed, i); + num_probed++; + } + + /* print an error message if no modules were probed */ + if (num_probed == 0) { + dev_err(&priv->pdev->dev, "no MODULbus modules specified, " + "please set the ``modules'' kernel " + "parameter according to your " + "hardware configuration\n"); + return -ENODEV; + } + + return mfd_add_devices(&pdev->dev, 0, priv->cells, + num_probed, NULL, pdev->irq, NULL); +} + +/* + * SYSFS Attributes + */ + +static ssize_t mbus_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cmodio_device *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%x\n", priv->hex); +} + +static DEVICE_ATTR(modulbus_number, S_IRUGO, mbus_show, NULL); + +static struct attribute *cmodio_sysfs_attrs[] = { + &dev_attr_modulbus_number.attr, + NULL, +}; + +static const struct attribute_group cmodio_sysfs_attr_group = { + .attrs = cmodio_sysfs_attrs, +}; + +/* + * PCI Driver + */ + +static int cmodio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct cmodio_device *priv; + int ret; + + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&dev->dev, "unable to allocate private data\n"); + return -ENOMEM; + } + + pci_set_drvdata(dev, priv); + priv->pdev = dev; + + /* Hardware Initialization */ + ret = pci_enable_device(dev); + if (ret) { + dev_err(&dev->dev, "unable to enable device\n"); + return ret; + } + + pci_set_master(dev); + ret = pci_request_regions(dev, DRV_NAME); + if (ret) { + dev_err(&dev->dev, "unable to request regions\n"); + goto out_pci_disable_device; + } + + /* Onboard configuration registers */ + priv->ctrl = pci_ioremap_bar(dev, 4); + if (!priv->ctrl) { + dev_err(&dev->dev, "unable to remap onboard regs\n"); + ret = -ENOMEM; + goto out_pci_release_regions; + } + + /* Read the hex switch on the carrier board */ + priv->hex = ioread8(&priv->ctrl->int_enable); + + /* Add the MODULbus number (hex switch value) to the device's sysfs */ + ret = sysfs_create_group(&dev->dev.kobj, &cmodio_sysfs_attr_group); + if (ret) { + dev_err(&dev->dev, "unable to create sysfs attributes\n"); + goto out_unmap_ctrl; + } + + /* + * Disable all interrupt lines, each submodule will enable its + * own interrupt line if needed + */ + iowrite8(0xf, &priv->ctrl->int_disable); + + /* Register drivers for all submodules */ + ret = cmodio_probe_submodules(priv); + if (ret) { + dev_err(&dev->dev, "unable to probe submodules\n"); + goto out_sysfs_remove_group; + } + + return 0; + +out_sysfs_remove_group: + sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group); +out_unmap_ctrl: + iounmap(priv->ctrl); +out_pci_release_regions: + pci_release_regions(dev); +out_pci_disable_device: + pci_disable_device(dev); + + return ret; +} + +static void cmodio_pci_remove(struct pci_dev *dev) +{ + struct cmodio_device *priv = pci_get_drvdata(dev); + + mfd_remove_devices(&dev->dev); + sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group); + iounmap(priv->ctrl); + pci_release_regions(dev); + pci_disable_device(dev); +} + +#define PCI_VENDOR_ID_JANZ 0x13c3 + +/* The list of devices that this module will support */ +static const struct pci_device_id cmodio_pci_ids[] = { + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9030, PCI_VENDOR_ID_JANZ, 0x0101 }, + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_JANZ, 0x0100 }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, cmodio_pci_ids); + +static struct pci_driver cmodio_pci_driver = { + .name = DRV_NAME, + .id_table = cmodio_pci_ids, + .probe = cmodio_pci_probe, + .remove = cmodio_pci_remove, +}; + +module_pci_driver(cmodio_pci_driver); + +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>"); +MODULE_DESCRIPTION("Janz CMOD-IO PCI MODULbus Carrier Board Driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/jz4740-adc.c b/kernel/drivers/mfd/jz4740-adc.c new file mode 100644 index 000000000..b31c54e4e --- /dev/null +++ b/kernel/drivers/mfd/jz4740-adc.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SoC ADC driver + * + * 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This driver synchronizes access to the JZ4740 ADC core between the + * JZ4740 battery and hwmon drivers. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <linux/clk.h> +#include <linux/mfd/core.h> + +#include <linux/jz4740-adc.h> + + +#define JZ_REG_ADC_ENABLE 0x00 +#define JZ_REG_ADC_CFG 0x04 +#define JZ_REG_ADC_CTRL 0x08 +#define JZ_REG_ADC_STATUS 0x0c + +#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10 +#define JZ_REG_ADC_BATTERY_BASE 0x1c +#define JZ_REG_ADC_HWMON_BASE 0x20 + +#define JZ_ADC_ENABLE_TOUCH BIT(2) +#define JZ_ADC_ENABLE_BATTERY BIT(1) +#define JZ_ADC_ENABLE_ADCIN BIT(0) + +enum { + JZ_ADC_IRQ_ADCIN = 0, + JZ_ADC_IRQ_BATTERY, + JZ_ADC_IRQ_TOUCH, + JZ_ADC_IRQ_PENUP, + JZ_ADC_IRQ_PENDOWN, +}; + +struct jz4740_adc { + struct resource *mem; + void __iomem *base; + + int irq; + struct irq_chip_generic *gc; + + struct clk *clk; + atomic_t clk_ref; + + spinlock_t lock; +}; + +static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc) +{ + struct irq_chip_generic *gc = irq_desc_get_handler_data(desc); + uint8_t status; + unsigned int i; + + status = readb(gc->reg_base + JZ_REG_ADC_STATUS); + + for (i = 0; i < 5; ++i) { + if (status & BIT(i)) + generic_handle_irq(gc->irq_base + i); + } +} + + +/* Refcounting for the ADC clock is done in here instead of in the clock + * framework, because it is the only clock which is shared between multiple + * devices and thus is the only clock which needs refcounting */ +static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc) +{ + if (atomic_inc_return(&adc->clk_ref) == 1) + clk_prepare_enable(adc->clk); +} + +static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc) +{ + if (atomic_dec_return(&adc->clk_ref) == 0) + clk_disable_unprepare(adc->clk); +} + +static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine, + bool enabled) +{ + unsigned long flags; + uint8_t val; + + spin_lock_irqsave(&adc->lock, flags); + + val = readb(adc->base + JZ_REG_ADC_ENABLE); + if (enabled) + val |= BIT(engine); + else + val &= ~BIT(engine); + writeb(val, adc->base + JZ_REG_ADC_ENABLE); + + spin_unlock_irqrestore(&adc->lock, flags); +} + +static int jz4740_adc_cell_enable(struct platform_device *pdev) +{ + struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent); + + jz4740_adc_clk_enable(adc); + jz4740_adc_set_enabled(adc, pdev->id, true); + + return 0; +} + +static int jz4740_adc_cell_disable(struct platform_device *pdev) +{ + struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent); + + jz4740_adc_set_enabled(adc, pdev->id, false); + jz4740_adc_clk_disable(adc); + + return 0; +} + +int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val) +{ + struct jz4740_adc *adc = dev_get_drvdata(dev); + unsigned long flags; + uint32_t cfg; + + if (!adc) + return -ENODEV; + + spin_lock_irqsave(&adc->lock, flags); + + cfg = readl(adc->base + JZ_REG_ADC_CFG); + + cfg &= ~mask; + cfg |= val; + + writel(cfg, adc->base + JZ_REG_ADC_CFG); + + spin_unlock_irqrestore(&adc->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(jz4740_adc_set_config); + +static struct resource jz4740_hwmon_resources[] = { + { + .start = JZ_ADC_IRQ_ADCIN, + .flags = IORESOURCE_IRQ, + }, + { + .start = JZ_REG_ADC_HWMON_BASE, + .end = JZ_REG_ADC_HWMON_BASE + 3, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource jz4740_battery_resources[] = { + { + .start = JZ_ADC_IRQ_BATTERY, + .flags = IORESOURCE_IRQ, + }, + { + .start = JZ_REG_ADC_BATTERY_BASE, + .end = JZ_REG_ADC_BATTERY_BASE + 3, + .flags = IORESOURCE_MEM, + }, +}; + +static const struct mfd_cell jz4740_adc_cells[] = { + { + .id = 0, + .name = "jz4740-hwmon", + .num_resources = ARRAY_SIZE(jz4740_hwmon_resources), + .resources = jz4740_hwmon_resources, + + .enable = jz4740_adc_cell_enable, + .disable = jz4740_adc_cell_disable, + }, + { + .id = 1, + .name = "jz4740-battery", + .num_resources = ARRAY_SIZE(jz4740_battery_resources), + .resources = jz4740_battery_resources, + + .enable = jz4740_adc_cell_enable, + .disable = jz4740_adc_cell_disable, + }, +}; + +static int jz4740_adc_probe(struct platform_device *pdev) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + struct jz4740_adc *adc; + struct resource *mem_base; + int ret; + int irq_base; + + adc = devm_kzalloc(&pdev->dev, sizeof(*adc), GFP_KERNEL); + if (!adc) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } + + adc->irq = platform_get_irq(pdev, 0); + if (adc->irq < 0) { + ret = adc->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + return ret; + } + + irq_base = platform_get_irq(pdev, 1); + if (irq_base < 0) { + dev_err(&pdev->dev, "Failed to get irq base: %d\n", irq_base); + return irq_base; + } + + mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_base) { + dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); + return -ENOENT; + } + + /* Only request the shared registers for the MFD driver */ + adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS, + pdev->name); + if (!adc->mem) { + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + return -EBUSY; + } + + adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem)); + if (!adc->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + goto err_release_mem_region; + } + + adc->clk = clk_get(&pdev->dev, "adc"); + if (IS_ERR(adc->clk)) { + ret = PTR_ERR(adc->clk); + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); + goto err_iounmap; + } + + spin_lock_init(&adc->lock); + atomic_set(&adc->clk_ref, 0); + + platform_set_drvdata(pdev, adc); + + 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_set_bit; + + irq_setup_generic_chip(gc, IRQ_MSK(5), 0, 0, IRQ_NOPROBE | IRQ_LEVEL); + + adc->gc = gc; + + 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, + irq_base, NULL); + if (ret < 0) + goto err_clk_put; + + return 0; + +err_clk_put: + clk_put(adc->clk); +err_iounmap: + iounmap(adc->base); +err_release_mem_region: + release_mem_region(adc->mem->start, resource_size(adc->mem)); + return ret; +} + +static int jz4740_adc_remove(struct platform_device *pdev) +{ + struct jz4740_adc *adc = platform_get_drvdata(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); + + iounmap(adc->base); + release_mem_region(adc->mem->start, resource_size(adc->mem)); + + clk_put(adc->clk); + + return 0; +} + +static struct platform_driver jz4740_adc_driver = { + .probe = jz4740_adc_probe, + .remove = jz4740_adc_remove, + .driver = { + .name = "jz4740-adc", + }, +}; + +module_platform_driver(jz4740_adc_driver); + +MODULE_DESCRIPTION("JZ4740 SoC ADC driver"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-adc"); diff --git a/kernel/drivers/mfd/kempld-core.c b/kernel/drivers/mfd/kempld-core.c new file mode 100644 index 000000000..8057849d5 --- /dev/null +++ b/kernel/drivers/mfd/kempld-core.c @@ -0,0 +1,779 @@ +/* + * Kontron PLD MFD core driver + * + * Copyright (c) 2010-2013 Kontron Europe GmbH + * Author: Michael Brunner <michael.brunner@kontron.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + */ + +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/kempld.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/io.h> +#include <linux/delay.h> + +#define MAX_ID_LEN 4 +static char force_device_id[MAX_ID_LEN + 1] = ""; +module_param_string(force_device_id, force_device_id, + sizeof(force_device_id), 0); +MODULE_PARM_DESC(force_device_id, "Override detected product"); + +/* + * Get hardware mutex to block firmware from accessing the pld. + * It is possible for the firmware may hold the mutex for an extended length of + * time. This function will block until access has been granted. + */ +static void kempld_get_hardware_mutex(struct kempld_device_data *pld) +{ + /* The mutex bit will read 1 until access has been granted */ + while (ioread8(pld->io_index) & KEMPLD_MUTEX_KEY) + usleep_range(1000, 3000); +} + +static void kempld_release_hardware_mutex(struct kempld_device_data *pld) +{ + /* The harware mutex is released when 1 is written to the mutex bit. */ + iowrite8(KEMPLD_MUTEX_KEY, pld->io_index); +} + +static int kempld_get_info_generic(struct kempld_device_data *pld) +{ + u16 version; + u8 spec; + + kempld_get_mutex(pld); + + version = kempld_read16(pld, KEMPLD_VERSION); + spec = kempld_read8(pld, KEMPLD_SPEC); + pld->info.buildnr = kempld_read16(pld, KEMPLD_BUILDNR); + + pld->info.minor = KEMPLD_VERSION_GET_MINOR(version); + pld->info.major = KEMPLD_VERSION_GET_MAJOR(version); + pld->info.number = KEMPLD_VERSION_GET_NUMBER(version); + pld->info.type = KEMPLD_VERSION_GET_TYPE(version); + + if (spec == 0xff) { + pld->info.spec_minor = 0; + pld->info.spec_major = 1; + } else { + pld->info.spec_minor = KEMPLD_SPEC_GET_MINOR(spec); + pld->info.spec_major = KEMPLD_SPEC_GET_MAJOR(spec); + } + + if (pld->info.spec_major > 0) + pld->feature_mask = kempld_read16(pld, KEMPLD_FEATURE); + else + pld->feature_mask = 0; + + kempld_release_mutex(pld); + + return 0; +} + +enum kempld_cells { + KEMPLD_I2C = 0, + KEMPLD_WDT, + KEMPLD_GPIO, + KEMPLD_UART, +}; + +static const struct mfd_cell kempld_devs[] = { + [KEMPLD_I2C] = { + .name = "kempld-i2c", + }, + [KEMPLD_WDT] = { + .name = "kempld-wdt", + }, + [KEMPLD_GPIO] = { + .name = "kempld-gpio", + }, + [KEMPLD_UART] = { + .name = "kempld-uart", + }, +}; + +#define KEMPLD_MAX_DEVS ARRAY_SIZE(kempld_devs) + +static int kempld_register_cells_generic(struct kempld_device_data *pld) +{ + struct mfd_cell devs[KEMPLD_MAX_DEVS]; + int i = 0; + + if (pld->feature_mask & KEMPLD_FEATURE_BIT_I2C) + devs[i++] = kempld_devs[KEMPLD_I2C]; + + if (pld->feature_mask & KEMPLD_FEATURE_BIT_WATCHDOG) + devs[i++] = kempld_devs[KEMPLD_WDT]; + + if (pld->feature_mask & KEMPLD_FEATURE_BIT_GPIO) + devs[i++] = kempld_devs[KEMPLD_GPIO]; + + if (pld->feature_mask & KEMPLD_FEATURE_MASK_UART) + devs[i++] = kempld_devs[KEMPLD_UART]; + + return mfd_add_devices(pld->dev, -1, devs, i, NULL, 0, NULL); +} + +static struct resource kempld_ioresource = { + .start = KEMPLD_IOINDEX, + .end = KEMPLD_IODATA, + .flags = IORESOURCE_IO, +}; + +static const struct kempld_platform_data kempld_platform_data_generic = { + .pld_clock = KEMPLD_CLK, + .ioresource = &kempld_ioresource, + .get_hardware_mutex = kempld_get_hardware_mutex, + .release_hardware_mutex = kempld_release_hardware_mutex, + .get_info = kempld_get_info_generic, + .register_cells = kempld_register_cells_generic, +}; + +static struct platform_device *kempld_pdev; + +static int kempld_create_platform_device(const struct dmi_system_id *id) +{ + struct kempld_platform_data *pdata = id->driver_data; + int ret; + + kempld_pdev = platform_device_alloc("kempld", -1); + if (!kempld_pdev) + return -ENOMEM; + + ret = platform_device_add_data(kempld_pdev, pdata, sizeof(*pdata)); + if (ret) + goto err; + + ret = platform_device_add_resources(kempld_pdev, pdata->ioresource, 1); + if (ret) + goto err; + + ret = platform_device_add(kempld_pdev); + if (ret) + goto err; + + return 0; +err: + platform_device_put(kempld_pdev); + return ret; +} + +/** + * kempld_read8 - read 8 bit register + * @pld: kempld_device_data structure describing the PLD + * @index: register index on the chip + * + * kempld_get_mutex must be called prior to calling this function. + */ +u8 kempld_read8(struct kempld_device_data *pld, u8 index) +{ + iowrite8(index, pld->io_index); + return ioread8(pld->io_data); +} +EXPORT_SYMBOL_GPL(kempld_read8); + +/** + * kempld_write8 - write 8 bit register + * @pld: kempld_device_data structure describing the PLD + * @index: register index on the chip + * @data: new register value + * + * kempld_get_mutex must be called prior to calling this function. + */ +void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data) +{ + iowrite8(index, pld->io_index); + iowrite8(data, pld->io_data); +} +EXPORT_SYMBOL_GPL(kempld_write8); + +/** + * kempld_read16 - read 16 bit register + * @pld: kempld_device_data structure describing the PLD + * @index: register index on the chip + * + * kempld_get_mutex must be called prior to calling this function. + */ +u16 kempld_read16(struct kempld_device_data *pld, u8 index) +{ + return kempld_read8(pld, index) | kempld_read8(pld, index + 1) << 8; +} +EXPORT_SYMBOL_GPL(kempld_read16); + +/** + * kempld_write16 - write 16 bit register + * @pld: kempld_device_data structure describing the PLD + * @index: register index on the chip + * @data: new register value + * + * kempld_get_mutex must be called prior to calling this function. + */ +void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data) +{ + kempld_write8(pld, index, (u8)data); + kempld_write8(pld, index + 1, (u8)(data >> 8)); +} +EXPORT_SYMBOL_GPL(kempld_write16); + +/** + * kempld_read32 - read 32 bit register + * @pld: kempld_device_data structure describing the PLD + * @index: register index on the chip + * + * kempld_get_mutex must be called prior to calling this function. + */ +u32 kempld_read32(struct kempld_device_data *pld, u8 index) +{ + return kempld_read16(pld, index) | kempld_read16(pld, index + 2) << 16; +} +EXPORT_SYMBOL_GPL(kempld_read32); + +/** + * kempld_write32 - write 32 bit register + * @pld: kempld_device_data structure describing the PLD + * @index: register index on the chip + * @data: new register value + * + * kempld_get_mutex must be called prior to calling this function. + */ +void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data) +{ + kempld_write16(pld, index, (u16)data); + kempld_write16(pld, index + 2, (u16)(data >> 16)); +} +EXPORT_SYMBOL_GPL(kempld_write32); + +/** + * kempld_get_mutex - acquire PLD mutex + * @pld: kempld_device_data structure describing the PLD + */ +void kempld_get_mutex(struct kempld_device_data *pld) +{ + struct kempld_platform_data *pdata = dev_get_platdata(pld->dev); + + mutex_lock(&pld->lock); + pdata->get_hardware_mutex(pld); +} +EXPORT_SYMBOL_GPL(kempld_get_mutex); + +/** + * kempld_release_mutex - release PLD mutex + * @pld: kempld_device_data structure describing the PLD + */ +void kempld_release_mutex(struct kempld_device_data *pld) +{ + struct kempld_platform_data *pdata = dev_get_platdata(pld->dev); + + pdata->release_hardware_mutex(pld); + mutex_unlock(&pld->lock); +} +EXPORT_SYMBOL_GPL(kempld_release_mutex); + +/** + * kempld_get_info - update device specific information + * @pld: kempld_device_data structure describing the PLD + * + * This function calls the configured board specific kempld_get_info_XXXX + * function which is responsible for gathering information about the specific + * hardware. The information is then stored within the pld structure. + */ +static int kempld_get_info(struct kempld_device_data *pld) +{ + int ret; + struct kempld_platform_data *pdata = dev_get_platdata(pld->dev); + char major, minor; + + ret = pdata->get_info(pld); + if (ret) + return ret; + + /* The Kontron PLD firmware version string has the following format: + * Pwxy.zzzz + * P: Fixed + * w: PLD number - 1 hex digit + * x: Major version - 1 alphanumerical digit (0-9A-V) + * y: Minor version - 1 alphanumerical digit (0-9A-V) + * zzzz: Build number - 4 zero padded hex digits */ + + if (pld->info.major < 10) + major = pld->info.major + '0'; + else + major = (pld->info.major - 10) + 'A'; + if (pld->info.minor < 10) + minor = pld->info.minor + '0'; + else + minor = (pld->info.minor - 10) + 'A'; + + ret = scnprintf(pld->info.version, sizeof(pld->info.version), + "P%X%c%c.%04X", pld->info.number, major, minor, + pld->info.buildnr); + if (ret < 0) + return ret; + + return 0; +} + +/* + * kempld_register_cells - register cell drivers + * + * This function registers cell drivers for the detected hardware by calling + * the configured kempld_register_cells_XXXX function which is responsible + * to detect and register the needed cell drivers. + */ +static int kempld_register_cells(struct kempld_device_data *pld) +{ + struct kempld_platform_data *pdata = dev_get_platdata(pld->dev); + + return pdata->register_cells(pld); +} + +static const char *kempld_get_type_string(struct kempld_device_data *pld) +{ + const char *version_type; + + switch (pld->info.type) { + case 0: + version_type = "release"; + break; + case 1: + version_type = "debug"; + break; + case 2: + version_type = "custom"; + break; + default: + version_type = "unspecified"; + break; + } + + return version_type; +} + +static ssize_t kempld_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kempld_device_data *pld = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", pld->info.version); +} + +static ssize_t kempld_specification_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kempld_device_data *pld = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d.%d\n", pld->info.spec_major, + pld->info.spec_minor); +} + +static ssize_t kempld_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kempld_device_data *pld = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", kempld_get_type_string(pld)); +} + +static DEVICE_ATTR(pld_version, S_IRUGO, kempld_version_show, NULL); +static DEVICE_ATTR(pld_specification, S_IRUGO, kempld_specification_show, + NULL); +static DEVICE_ATTR(pld_type, S_IRUGO, kempld_type_show, NULL); + +static struct attribute *pld_attributes[] = { + &dev_attr_pld_version.attr, + &dev_attr_pld_specification.attr, + &dev_attr_pld_type.attr, + NULL +}; + +static const struct attribute_group pld_attr_group = { + .attrs = pld_attributes, +}; + +static int kempld_detect_device(struct kempld_device_data *pld) +{ + u8 index_reg; + int ret; + + mutex_lock(&pld->lock); + + /* Check for empty IO space */ + index_reg = ioread8(pld->io_index); + if (index_reg == 0xff && ioread8(pld->io_data) == 0xff) { + mutex_unlock(&pld->lock); + return -ENODEV; + } + + /* Release hardware mutex if acquired */ + if (!(index_reg & KEMPLD_MUTEX_KEY)) { + iowrite8(KEMPLD_MUTEX_KEY, pld->io_index); + /* PXT and COMe-cPC2 boards may require a second release */ + iowrite8(KEMPLD_MUTEX_KEY, pld->io_index); + } + + mutex_unlock(&pld->lock); + + ret = kempld_get_info(pld); + if (ret) + return ret; + + dev_info(pld->dev, "Found Kontron PLD - %s (%s), spec %d.%d\n", + pld->info.version, kempld_get_type_string(pld), + pld->info.spec_major, pld->info.spec_minor); + + ret = sysfs_create_group(&pld->dev->kobj, &pld_attr_group); + if (ret) + return ret; + + ret = kempld_register_cells(pld); + if (ret) + sysfs_remove_group(&pld->dev->kobj, &pld_attr_group); + + return ret; +} + +static int kempld_probe(struct platform_device *pdev) +{ + struct kempld_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct kempld_device_data *pld; + struct resource *ioport; + int ret; + + pld = devm_kzalloc(dev, sizeof(*pld), GFP_KERNEL); + if (!pld) + return -ENOMEM; + + ioport = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!ioport) + return -EINVAL; + + pld->io_base = devm_ioport_map(dev, ioport->start, + ioport->end - ioport->start); + if (!pld->io_base) + return -ENOMEM; + + pld->io_index = pld->io_base; + pld->io_data = pld->io_base + 1; + pld->pld_clock = pdata->pld_clock; + pld->dev = dev; + + mutex_init(&pld->lock); + platform_set_drvdata(pdev, pld); + + ret = kempld_detect_device(pld); + if (ret) + return ret; + + return 0; +} + +static int kempld_remove(struct platform_device *pdev) +{ + struct kempld_device_data *pld = platform_get_drvdata(pdev); + struct kempld_platform_data *pdata = dev_get_platdata(pld->dev); + + sysfs_remove_group(&pld->dev->kobj, &pld_attr_group); + + mfd_remove_devices(&pdev->dev); + pdata->release_hardware_mutex(pld); + + return 0; +} + +static struct platform_driver kempld_driver = { + .driver = { + .name = "kempld", + }, + .probe = kempld_probe, + .remove = kempld_remove, +}; + +static struct dmi_system_id kempld_dmi_table[] __initdata = { + { + .ident = "BHL6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-bHL6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CBL6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cBL6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CCR2", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CCR6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHL6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cHL6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHR2", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHR2", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHR2", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHR6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHR6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CHR6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CNTG", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-PC"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CNTG", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-bPC2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CNTX", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "PXT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "CVV6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cBT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "FRI2", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BIOS_VERSION, "FRI2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "FRI2", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Fish River Island II"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "MBR1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "ETX-OH"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "MVV1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-mBT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "NTC1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "nanoETXexpress-TT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "NTC1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "nETXe-TT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "NTC1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-mTT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "NUP1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-mCT"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "UNP1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-DC"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "UNP1", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cDC2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "UNTG", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-PC"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "UNTG", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cPC2"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, { + .ident = "UUP6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cCT6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, + { + .ident = "UTH6", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"), + DMI_MATCH(DMI_BOARD_NAME, "COMe-cTH6"), + }, + .driver_data = (void *)&kempld_platform_data_generic, + .callback = kempld_create_platform_device, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, kempld_dmi_table); + +static int __init kempld_init(void) +{ + const struct dmi_system_id *id; + int ret; + + if (force_device_id[0]) { + for (id = kempld_dmi_table; + id->matches[0].slot != DMI_NONE; id++) + if (strstr(id->ident, force_device_id)) + if (id->callback && !id->callback(id)) + break; + if (id->matches[0].slot == DMI_NONE) + return -ENODEV; + } else { + if (!dmi_check_system(kempld_dmi_table)) + return -ENODEV; + } + + ret = platform_driver_register(&kempld_driver); + if (ret) + return ret; + + return 0; +} + +static void __exit kempld_exit(void) +{ + if (kempld_pdev) + platform_device_unregister(kempld_pdev); + + platform_driver_unregister(&kempld_driver); +} + +module_init(kempld_init); +module_exit(kempld_exit); + +MODULE_DESCRIPTION("KEM PLD Core Driver"); +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:kempld-core"); diff --git a/kernel/drivers/mfd/lm3533-core.c b/kernel/drivers/mfd/lm3533-core.c new file mode 100644 index 000000000..d42fbb667 --- /dev/null +++ b/kernel/drivers/mfd/lm3533-core.c @@ -0,0 +1,664 @@ +/* + * lm3533-core.c -- LM3533 Core + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.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/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/regmap.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_BOOST_OVP_MASK 0x06 +#define LM3533_BOOST_OVP_SHIFT 1 + +#define LM3533_BOOST_FREQ_MASK 0x01 +#define LM3533_BOOST_FREQ_SHIFT 0 + +#define LM3533_BL_ID_MASK 1 +#define LM3533_LED_ID_MASK 3 +#define LM3533_BL_ID_MAX 1 +#define LM3533_LED_ID_MAX 3 + +#define LM3533_HVLED_ID_MAX 2 +#define LM3533_LVLED_ID_MAX 5 + +#define LM3533_REG_OUTPUT_CONF1 0x10 +#define LM3533_REG_OUTPUT_CONF2 0x11 +#define LM3533_REG_BOOST_PWM 0x2c + +#define LM3533_REG_MAX 0xb2 + + +static struct mfd_cell lm3533_als_devs[] = { + { + .name = "lm3533-als", + .id = -1, + }, +}; + +static struct mfd_cell lm3533_bl_devs[] = { + { + .name = "lm3533-backlight", + .id = 0, + }, + { + .name = "lm3533-backlight", + .id = 1, + }, +}; + +static struct mfd_cell lm3533_led_devs[] = { + { + .name = "lm3533-leds", + .id = 0, + }, + { + .name = "lm3533-leds", + .id = 1, + }, + { + .name = "lm3533-leds", + .id = 2, + }, + { + .name = "lm3533-leds", + .id = 3, + }, +}; + +int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val) +{ + int tmp; + int ret; + + ret = regmap_read(lm3533->regmap, reg, &tmp); + if (ret < 0) { + dev_err(lm3533->dev, "failed to read register %02x: %d\n", + reg, ret); + return ret; + } + + *val = tmp; + + dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val); + + return ret; +} +EXPORT_SYMBOL_GPL(lm3533_read); + +int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val) +{ + int ret; + + dev_dbg(lm3533->dev, "write [%02x]: %02x\n", reg, val); + + ret = regmap_write(lm3533->regmap, reg, val); + if (ret < 0) { + dev_err(lm3533->dev, "failed to write register %02x: %d\n", + reg, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(lm3533_write); + +int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask) +{ + int ret; + + dev_dbg(lm3533->dev, "update [%02x]: %02x/%02x\n", reg, val, mask); + + ret = regmap_update_bits(lm3533->regmap, reg, mask, val); + if (ret < 0) { + dev_err(lm3533->dev, "failed to update register %02x: %d\n", + reg, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(lm3533_update); + +static int lm3533_set_boost_freq(struct lm3533 *lm3533, + enum lm3533_boost_freq freq) +{ + int ret; + + ret = lm3533_update(lm3533, LM3533_REG_BOOST_PWM, + freq << LM3533_BOOST_FREQ_SHIFT, + LM3533_BOOST_FREQ_MASK); + if (ret) + dev_err(lm3533->dev, "failed to set boost frequency\n"); + + return ret; +} + + +static int lm3533_set_boost_ovp(struct lm3533 *lm3533, + enum lm3533_boost_ovp ovp) +{ + int ret; + + ret = lm3533_update(lm3533, LM3533_REG_BOOST_PWM, + ovp << LM3533_BOOST_OVP_SHIFT, + LM3533_BOOST_OVP_MASK); + if (ret) + dev_err(lm3533->dev, "failed to set boost ovp\n"); + + return ret; +} + +/* + * HVLED output config -- output hvled controlled by backlight bl + */ +static int lm3533_set_hvled_config(struct lm3533 *lm3533, u8 hvled, u8 bl) +{ + u8 val; + u8 mask; + int shift; + int ret; + + if (hvled == 0 || hvled > LM3533_HVLED_ID_MAX) + return -EINVAL; + + if (bl > LM3533_BL_ID_MAX) + return -EINVAL; + + shift = hvled - 1; + mask = LM3533_BL_ID_MASK << shift; + val = bl << shift; + + ret = lm3533_update(lm3533, LM3533_REG_OUTPUT_CONF1, val, mask); + if (ret) + dev_err(lm3533->dev, "failed to set hvled config\n"); + + return ret; +} + +/* + * LVLED output config -- output lvled controlled by LED led + */ +static int lm3533_set_lvled_config(struct lm3533 *lm3533, u8 lvled, u8 led) +{ + u8 reg; + u8 val; + u8 mask; + int shift; + int ret; + + if (lvled == 0 || lvled > LM3533_LVLED_ID_MAX) + return -EINVAL; + + if (led > LM3533_LED_ID_MAX) + return -EINVAL; + + if (lvled < 4) { + reg = LM3533_REG_OUTPUT_CONF1; + shift = 2 * lvled; + } else { + reg = LM3533_REG_OUTPUT_CONF2; + shift = 2 * (lvled - 4); + } + + mask = LM3533_LED_ID_MASK << shift; + val = led << shift; + + ret = lm3533_update(lm3533, reg, val, mask); + if (ret) + dev_err(lm3533->dev, "failed to set lvled config\n"); + + return ret; +} + +static void lm3533_enable(struct lm3533 *lm3533) +{ + if (gpio_is_valid(lm3533->gpio_hwen)) + gpio_set_value(lm3533->gpio_hwen, 1); +} + +static void lm3533_disable(struct lm3533 *lm3533) +{ + if (gpio_is_valid(lm3533->gpio_hwen)) + gpio_set_value(lm3533->gpio_hwen, 0); +} + +enum lm3533_attribute_type { + LM3533_ATTR_TYPE_BACKLIGHT, + LM3533_ATTR_TYPE_LED, +}; + +struct lm3533_device_attribute { + struct device_attribute dev_attr; + enum lm3533_attribute_type type; + union { + struct { + u8 id; + } output; + } u; +}; + +#define to_lm3533_dev_attr(_attr) \ + container_of(_attr, struct lm3533_device_attribute, dev_attr) + +static ssize_t show_output(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm3533 *lm3533 = dev_get_drvdata(dev); + struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr); + int id = lattr->u.output.id; + u8 reg; + u8 val; + u8 mask; + int shift; + int ret; + + if (lattr->type == LM3533_ATTR_TYPE_BACKLIGHT) { + reg = LM3533_REG_OUTPUT_CONF1; + shift = id - 1; + mask = LM3533_BL_ID_MASK << shift; + } else { + if (id < 4) { + reg = LM3533_REG_OUTPUT_CONF1; + shift = 2 * id; + } else { + reg = LM3533_REG_OUTPUT_CONF2; + shift = 2 * (id - 4); + } + mask = LM3533_LED_ID_MASK << shift; + } + + ret = lm3533_read(lm3533, reg, &val); + if (ret) + return ret; + + val = (val & mask) >> shift; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_output(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lm3533 *lm3533 = dev_get_drvdata(dev); + struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr); + int id = lattr->u.output.id; + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + if (lattr->type == LM3533_ATTR_TYPE_BACKLIGHT) + ret = lm3533_set_hvled_config(lm3533, id, val); + else + ret = lm3533_set_lvled_config(lm3533, id, val); + + if (ret) + return ret; + + return len; +} + +#define LM3533_OUTPUT_ATTR(_name, _mode, _show, _store, _type, _id) \ + struct lm3533_device_attribute lm3533_dev_attr_##_name = \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .type = _type, \ + .u.output = { .id = _id }, } + +#define LM3533_OUTPUT_ATTR_RW(_name, _type, _id) \ + LM3533_OUTPUT_ATTR(output_##_name, S_IRUGO | S_IWUSR, \ + show_output, store_output, _type, _id) + +#define LM3533_OUTPUT_HVLED_ATTR_RW(_nr) \ + LM3533_OUTPUT_ATTR_RW(hvled##_nr, LM3533_ATTR_TYPE_BACKLIGHT, _nr) +#define LM3533_OUTPUT_LVLED_ATTR_RW(_nr) \ + LM3533_OUTPUT_ATTR_RW(lvled##_nr, LM3533_ATTR_TYPE_LED, _nr) +/* + * Output config: + * + * output_hvled<nr> 0-1 + * output_lvled<nr> 0-3 + */ +static LM3533_OUTPUT_HVLED_ATTR_RW(1); +static LM3533_OUTPUT_HVLED_ATTR_RW(2); +static LM3533_OUTPUT_LVLED_ATTR_RW(1); +static LM3533_OUTPUT_LVLED_ATTR_RW(2); +static LM3533_OUTPUT_LVLED_ATTR_RW(3); +static LM3533_OUTPUT_LVLED_ATTR_RW(4); +static LM3533_OUTPUT_LVLED_ATTR_RW(5); + +static struct attribute *lm3533_attributes[] = { + &lm3533_dev_attr_output_hvled1.dev_attr.attr, + &lm3533_dev_attr_output_hvled2.dev_attr.attr, + &lm3533_dev_attr_output_lvled1.dev_attr.attr, + &lm3533_dev_attr_output_lvled2.dev_attr.attr, + &lm3533_dev_attr_output_lvled3.dev_attr.attr, + &lm3533_dev_attr_output_lvled4.dev_attr.attr, + &lm3533_dev_attr_output_lvled5.dev_attr.attr, + NULL, +}; + +#define to_dev_attr(_attr) \ + container_of(_attr, struct device_attribute, attr) + +static umode_t lm3533_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct lm3533 *lm3533 = dev_get_drvdata(dev); + struct device_attribute *dattr = to_dev_attr(attr); + struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(dattr); + enum lm3533_attribute_type type = lattr->type; + umode_t mode = attr->mode; + + if (!lm3533->have_backlights && type == LM3533_ATTR_TYPE_BACKLIGHT) + mode = 0; + else if (!lm3533->have_leds && type == LM3533_ATTR_TYPE_LED) + mode = 0; + + return mode; +}; + +static struct attribute_group lm3533_attribute_group = { + .is_visible = lm3533_attr_is_visible, + .attrs = lm3533_attributes +}; + +static int lm3533_device_als_init(struct lm3533 *lm3533) +{ + struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev); + int ret; + + if (!pdata->als) + return 0; + + lm3533_als_devs[0].platform_data = pdata->als; + lm3533_als_devs[0].pdata_size = sizeof(*pdata->als); + + ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL, + 0, NULL); + if (ret) { + dev_err(lm3533->dev, "failed to add ALS device\n"); + return ret; + } + + lm3533->have_als = 1; + + return 0; +} + +static int lm3533_device_bl_init(struct lm3533 *lm3533) +{ + struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev); + int i; + int ret; + + if (!pdata->backlights || pdata->num_backlights == 0) + return 0; + + if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs)) + pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs); + + for (i = 0; i < pdata->num_backlights; ++i) { + lm3533_bl_devs[i].platform_data = &pdata->backlights[i]; + lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]); + } + + ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs, + pdata->num_backlights, NULL, 0, NULL); + if (ret) { + dev_err(lm3533->dev, "failed to add backlight devices\n"); + return ret; + } + + lm3533->have_backlights = 1; + + return 0; +} + +static int lm3533_device_led_init(struct lm3533 *lm3533) +{ + struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev); + int i; + int ret; + + if (!pdata->leds || pdata->num_leds == 0) + return 0; + + if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs)) + pdata->num_leds = ARRAY_SIZE(lm3533_led_devs); + + for (i = 0; i < pdata->num_leds; ++i) { + lm3533_led_devs[i].platform_data = &pdata->leds[i]; + lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]); + } + + ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs, + pdata->num_leds, NULL, 0, NULL); + if (ret) { + dev_err(lm3533->dev, "failed to add LED devices\n"); + return ret; + } + + lm3533->have_leds = 1; + + return 0; +} + +static int lm3533_device_setup(struct lm3533 *lm3533, + struct lm3533_platform_data *pdata) +{ + int ret; + + ret = lm3533_set_boost_freq(lm3533, pdata->boost_freq); + if (ret) + return ret; + + ret = lm3533_set_boost_ovp(lm3533, pdata->boost_ovp); + if (ret) + return ret; + + return 0; +} + +static int lm3533_device_init(struct lm3533 *lm3533) +{ + struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev); + int ret; + + dev_dbg(lm3533->dev, "%s\n", __func__); + + if (!pdata) { + dev_err(lm3533->dev, "no platform data\n"); + return -EINVAL; + } + + lm3533->gpio_hwen = pdata->gpio_hwen; + + dev_set_drvdata(lm3533->dev, lm3533); + + if (gpio_is_valid(lm3533->gpio_hwen)) { + ret = devm_gpio_request_one(lm3533->dev, lm3533->gpio_hwen, + GPIOF_OUT_INIT_LOW, "lm3533-hwen"); + if (ret < 0) { + dev_err(lm3533->dev, + "failed to request HWEN GPIO %d\n", + lm3533->gpio_hwen); + return ret; + } + } + + lm3533_enable(lm3533); + + ret = lm3533_device_setup(lm3533, pdata); + if (ret) + goto err_disable; + + lm3533_device_als_init(lm3533); + lm3533_device_bl_init(lm3533); + lm3533_device_led_init(lm3533); + + ret = sysfs_create_group(&lm3533->dev->kobj, &lm3533_attribute_group); + if (ret < 0) { + dev_err(lm3533->dev, "failed to create sysfs attributes\n"); + goto err_unregister; + } + + return 0; + +err_unregister: + mfd_remove_devices(lm3533->dev); +err_disable: + lm3533_disable(lm3533); + + return ret; +} + +static void lm3533_device_exit(struct lm3533 *lm3533) +{ + dev_dbg(lm3533->dev, "%s\n", __func__); + + sysfs_remove_group(&lm3533->dev->kobj, &lm3533_attribute_group); + + mfd_remove_devices(lm3533->dev); + lm3533_disable(lm3533); +} + +static bool lm3533_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x10 ... 0x2c: + case 0x30 ... 0x38: + case 0x40 ... 0x45: + case 0x50 ... 0x57: + case 0x60 ... 0x6e: + case 0x70 ... 0x75: + case 0x80 ... 0x85: + case 0x90 ... 0x95: + case 0xa0 ... 0xa5: + case 0xb0 ... 0xb2: + return true; + default: + return false; + } +} + +static bool lm3533_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x34 ... 0x36: /* zone */ + case 0x37 ... 0x38: /* adc */ + case 0xb0 ... 0xb1: /* fault */ + return true; + default: + return false; + } +} + +static bool lm3533_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x34: /* zone */ + return true; + default: + return false; + } +} + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LM3533_REG_MAX, + .readable_reg = lm3533_readable_register, + .volatile_reg = lm3533_volatile_register, + .precious_reg = lm3533_precious_register, +}; + +static int lm3533_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lm3533 *lm3533; + int ret; + + dev_dbg(&i2c->dev, "%s\n", __func__); + + lm3533 = devm_kzalloc(&i2c->dev, sizeof(*lm3533), GFP_KERNEL); + if (!lm3533) + return -ENOMEM; + + i2c_set_clientdata(i2c, lm3533); + + lm3533->regmap = devm_regmap_init_i2c(i2c, ®map_config); + if (IS_ERR(lm3533->regmap)) + return PTR_ERR(lm3533->regmap); + + lm3533->dev = &i2c->dev; + lm3533->irq = i2c->irq; + + ret = lm3533_device_init(lm3533); + if (ret) + return ret; + + return 0; +} + +static int lm3533_i2c_remove(struct i2c_client *i2c) +{ + struct lm3533 *lm3533 = i2c_get_clientdata(i2c); + + dev_dbg(&i2c->dev, "%s\n", __func__); + + lm3533_device_exit(lm3533); + + return 0; +} + +static const struct i2c_device_id lm3533_i2c_ids[] = { + { "lm3533", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, lm3533_i2c_ids); + +static struct i2c_driver lm3533_i2c_driver = { + .driver = { + .name = "lm3533", + .owner = THIS_MODULE, + }, + .id_table = lm3533_i2c_ids, + .probe = lm3533_i2c_probe, + .remove = lm3533_i2c_remove, +}; + +static int __init lm3533_i2c_init(void) +{ + return i2c_add_driver(&lm3533_i2c_driver); +} +subsys_initcall(lm3533_i2c_init); + +static void __exit lm3533_i2c_exit(void) +{ + i2c_del_driver(&lm3533_i2c_driver); +} +module_exit(lm3533_i2c_exit); + +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>"); +MODULE_DESCRIPTION("LM3533 Core"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/lm3533-ctrlbank.c b/kernel/drivers/mfd/lm3533-ctrlbank.c new file mode 100644 index 000000000..a4cb7a522 --- /dev/null +++ b/kernel/drivers/mfd/lm3533-ctrlbank.c @@ -0,0 +1,148 @@ +/* + * lm3533-ctrlbank.c -- LM3533 Generic Control Bank interface + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.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/device.h> +#include <linux/module.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_MAX_CURRENT_MIN 5000 +#define LM3533_MAX_CURRENT_MAX 29800 +#define LM3533_MAX_CURRENT_STEP 800 + +#define LM3533_BRIGHTNESS_MAX 255 +#define LM3533_PWM_MAX 0x3f + +#define LM3533_REG_PWM_BASE 0x14 +#define LM3533_REG_MAX_CURRENT_BASE 0x1f +#define LM3533_REG_CTRLBANK_ENABLE 0x27 +#define LM3533_REG_BRIGHTNESS_BASE 0x40 + + +static inline u8 lm3533_ctrlbank_get_reg(struct lm3533_ctrlbank *cb, u8 base) +{ + return base + cb->id; +} + +int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb) +{ + u8 mask; + int ret; + + dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id); + + mask = 1 << cb->id; + ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, + mask, mask); + if (ret) + dev_err(cb->dev, "failed to enable ctrlbank %d\n", cb->id); + + return ret; +} +EXPORT_SYMBOL_GPL(lm3533_ctrlbank_enable); + +int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb) +{ + u8 mask; + int ret; + + dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id); + + mask = 1 << cb->id; + ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, 0, mask); + if (ret) + dev_err(cb->dev, "failed to disable ctrlbank %d\n", cb->id); + + return ret; +} +EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable); + +/* + * Full-scale current. + * + * imax 5000 - 29800 uA (800 uA step) + */ +int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u16 imax) +{ + u8 reg; + u8 val; + int ret; + + if (imax < LM3533_MAX_CURRENT_MIN || imax > LM3533_MAX_CURRENT_MAX) + return -EINVAL; + + val = (imax - LM3533_MAX_CURRENT_MIN) / LM3533_MAX_CURRENT_STEP; + + reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_MAX_CURRENT_BASE); + ret = lm3533_write(cb->lm3533, reg, val); + if (ret) + dev_err(cb->dev, "failed to set max current\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_max_current); + +#define lm3533_ctrlbank_set(_name, _NAME) \ +int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val) \ +{ \ + u8 reg; \ + int ret; \ + \ + if (val > LM3533_##_NAME##_MAX) \ + return -EINVAL; \ + \ + reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE); \ + ret = lm3533_write(cb->lm3533, reg, val); \ + if (ret) \ + dev_err(cb->dev, "failed to set " #_name "\n"); \ + \ + return ret; \ +} \ +EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_##_name); + +#define lm3533_ctrlbank_get(_name, _NAME) \ +int lm3533_ctrlbank_get_##_name(struct lm3533_ctrlbank *cb, u8 *val) \ +{ \ + u8 reg; \ + int ret; \ + \ + reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE); \ + ret = lm3533_read(cb->lm3533, reg, val); \ + if (ret) \ + dev_err(cb->dev, "failed to get " #_name "\n"); \ + \ + return ret; \ +} \ +EXPORT_SYMBOL_GPL(lm3533_ctrlbank_get_##_name); + +lm3533_ctrlbank_set(brightness, BRIGHTNESS); +lm3533_ctrlbank_get(brightness, BRIGHTNESS); + +/* + * PWM-input control mask: + * + * bit 5 - PWM-input enabled in Zone 4 + * bit 4 - PWM-input enabled in Zone 3 + * bit 3 - PWM-input enabled in Zone 2 + * bit 2 - PWM-input enabled in Zone 1 + * bit 1 - PWM-input enabled in Zone 0 + * bit 0 - PWM-input enabled + */ +lm3533_ctrlbank_set(pwm, PWM); +lm3533_ctrlbank_get(pwm, PWM); + + +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>"); +MODULE_DESCRIPTION("LM3533 Control Bank interface"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/lp3943.c b/kernel/drivers/mfd/lp3943.c new file mode 100644 index 000000000..335b93011 --- /dev/null +++ b/kernel/drivers/mfd/lp3943.c @@ -0,0 +1,167 @@ +/* + * TI/National Semiconductor LP3943 MFD Core Driver + * + * Copyright 2013 Texas Instruments + * + * Author: Milo Kim <milo.kim@ti.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. + * + * Driver structure: + * LP3943 is an integrated device capable of driving 16 output channels. + * It can be used for a GPIO expander and PWM generators. + * + * LED control General usage for a device + * ___________ ____________________________ + * + * LP3943 MFD ---- GPIO expander leds-gpio eg) HW enable pin + * | + * --- PWM generator leds-pwm eg) PWM input + * + * Internal two PWM channels are used for LED dimming effect. + * And each output pin can be used as a GPIO as well. + * The LED functionality can work with GPIOs or PWMs. + * LEDs can be controlled with legacy leds-gpio(static brightness) or + * leds-pwm drivers(dynamic brightness control). + * Alternatively, it can be used for generic GPIO and PWM controller. + * For example, a GPIO is HW enable pin of a device. + * A PWM is input pin of a backlight device. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/lp3943.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> + +#define LP3943_MAX_REGISTERS 0x09 + +/* Register configuration for pin MUX */ +static const struct lp3943_reg_cfg lp3943_mux_cfg[] = { + /* address, mask, shift */ + { LP3943_REG_MUX0, 0x03, 0 }, + { LP3943_REG_MUX0, 0x0C, 2 }, + { LP3943_REG_MUX0, 0x30, 4 }, + { LP3943_REG_MUX0, 0xC0, 6 }, + { LP3943_REG_MUX1, 0x03, 0 }, + { LP3943_REG_MUX1, 0x0C, 2 }, + { LP3943_REG_MUX1, 0x30, 4 }, + { LP3943_REG_MUX1, 0xC0, 6 }, + { LP3943_REG_MUX2, 0x03, 0 }, + { LP3943_REG_MUX2, 0x0C, 2 }, + { LP3943_REG_MUX2, 0x30, 4 }, + { LP3943_REG_MUX2, 0xC0, 6 }, + { LP3943_REG_MUX3, 0x03, 0 }, + { LP3943_REG_MUX3, 0x0C, 2 }, + { LP3943_REG_MUX3, 0x30, 4 }, + { LP3943_REG_MUX3, 0xC0, 6 }, +}; + +static const struct mfd_cell lp3943_devs[] = { + { + .name = "lp3943-pwm", + .of_compatible = "ti,lp3943-pwm", + }, + { + .name = "lp3943-gpio", + .of_compatible = "ti,lp3943-gpio", + }, +}; + +int lp3943_read_byte(struct lp3943 *lp3943, u8 reg, u8 *read) +{ + int ret; + unsigned int val; + + ret = regmap_read(lp3943->regmap, reg, &val); + if (ret < 0) + return ret; + + *read = (u8)val; + return 0; +} +EXPORT_SYMBOL_GPL(lp3943_read_byte); + +int lp3943_write_byte(struct lp3943 *lp3943, u8 reg, u8 data) +{ + return regmap_write(lp3943->regmap, reg, data); +} +EXPORT_SYMBOL_GPL(lp3943_write_byte); + +int lp3943_update_bits(struct lp3943 *lp3943, u8 reg, u8 mask, u8 data) +{ + return regmap_update_bits(lp3943->regmap, reg, mask, data); +} +EXPORT_SYMBOL_GPL(lp3943_update_bits); + +static const struct regmap_config lp3943_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LP3943_MAX_REGISTERS, +}; + +static int lp3943_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp3943 *lp3943; + struct device *dev = &cl->dev; + + lp3943 = devm_kzalloc(dev, sizeof(*lp3943), GFP_KERNEL); + if (!lp3943) + return -ENOMEM; + + lp3943->regmap = devm_regmap_init_i2c(cl, &lp3943_regmap_config); + if (IS_ERR(lp3943->regmap)) + return PTR_ERR(lp3943->regmap); + + lp3943->pdata = dev_get_platdata(dev); + lp3943->dev = dev; + lp3943->mux_cfg = lp3943_mux_cfg; + i2c_set_clientdata(cl, lp3943); + + return mfd_add_devices(dev, -1, lp3943_devs, ARRAY_SIZE(lp3943_devs), + NULL, 0, NULL); +} + +static int lp3943_remove(struct i2c_client *cl) +{ + struct lp3943 *lp3943 = i2c_get_clientdata(cl); + + mfd_remove_devices(lp3943->dev); + return 0; +} + +static const struct i2c_device_id lp3943_ids[] = { + { "lp3943", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp3943_ids); + +#ifdef CONFIG_OF +static const struct of_device_id lp3943_of_match[] = { + { .compatible = "ti,lp3943", }, + { } +}; +MODULE_DEVICE_TABLE(of, lp3943_of_match); +#endif + +static struct i2c_driver lp3943_driver = { + .probe = lp3943_probe, + .remove = lp3943_remove, + .driver = { + .name = "lp3943", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(lp3943_of_match), + }, + .id_table = lp3943_ids, +}; + +module_i2c_driver(lp3943_driver); + +MODULE_DESCRIPTION("LP3943 MFD Core Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/lp8788-irq.c b/kernel/drivers/mfd/lp8788-irq.c new file mode 100644 index 000000000..23982dbf0 --- /dev/null +++ b/kernel/drivers/mfd/lp8788-irq.c @@ -0,0 +1,200 @@ +/* + * TI LP8788 MFD - interrupt handler + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.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. + * + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/device.h> +#include <linux/mfd/lp8788.h> +#include <linux/module.h> +#include <linux/slab.h> + +/* register address */ +#define LP8788_INT_1 0x00 +#define LP8788_INTEN_1 0x03 + +#define BASE_INTEN_ADDR LP8788_INTEN_1 +#define SIZE_REG 8 +#define NUM_REGS 3 + +/* + * struct lp8788_irq_data + * @lp : used for accessing to lp8788 registers + * @irq_lock : mutex for enabling/disabling the interrupt + * @domain : IRQ domain for handling nested interrupt + * @enabled : status of enabled interrupt + */ +struct lp8788_irq_data { + struct lp8788 *lp; + struct mutex irq_lock; + struct irq_domain *domain; + int enabled[LP8788_INT_MAX]; +}; + +static inline u8 _irq_to_addr(enum lp8788_int_id id) +{ + return id / SIZE_REG; +} + +static inline u8 _irq_to_enable_addr(enum lp8788_int_id id) +{ + return _irq_to_addr(id) + BASE_INTEN_ADDR; +} + +static inline u8 _irq_to_mask(enum lp8788_int_id id) +{ + return 1 << (id % SIZE_REG); +} + +static inline u8 _irq_to_val(enum lp8788_int_id id, int enable) +{ + return enable << (id % SIZE_REG); +} + +static void lp8788_irq_enable(struct irq_data *data) +{ + struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data); + + irqd->enabled[data->hwirq] = 1; +} + +static void lp8788_irq_disable(struct irq_data *data) +{ + struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data); + + irqd->enabled[data->hwirq] = 0; +} + +static void lp8788_irq_bus_lock(struct irq_data *data) +{ + struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data); + + mutex_lock(&irqd->irq_lock); +} + +static void lp8788_irq_bus_sync_unlock(struct irq_data *data) +{ + struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data); + enum lp8788_int_id irq = data->hwirq; + u8 addr, mask, val; + + addr = _irq_to_enable_addr(irq); + mask = _irq_to_mask(irq); + val = _irq_to_val(irq, irqd->enabled[irq]); + + lp8788_update_bits(irqd->lp, addr, mask, val); + + mutex_unlock(&irqd->irq_lock); +} + +static struct irq_chip lp8788_irq_chip = { + .name = "lp8788", + .irq_enable = lp8788_irq_enable, + .irq_disable = lp8788_irq_disable, + .irq_bus_lock = lp8788_irq_bus_lock, + .irq_bus_sync_unlock = lp8788_irq_bus_sync_unlock, +}; + +static irqreturn_t lp8788_irq_handler(int irq, void *ptr) +{ + struct lp8788_irq_data *irqd = ptr; + struct lp8788 *lp = irqd->lp; + u8 status[NUM_REGS], addr, mask; + bool handled; + int i; + + if (lp8788_read_multi_bytes(lp, LP8788_INT_1, status, NUM_REGS)) + return IRQ_NONE; + + for (i = 0 ; i < LP8788_INT_MAX ; i++) { + addr = _irq_to_addr(i); + mask = _irq_to_mask(i); + + /* reporting only if the irq is enabled */ + if (status[addr] & mask) { + handle_nested_irq(irq_find_mapping(irqd->domain, i)); + handled = true; + } + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int lp8788_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct lp8788_irq_data *irqd = d->host_data; + struct irq_chip *chip = &lp8788_irq_chip; + + irq_set_chip_data(virq, irqd); + irq_set_chip_and_handler(virq, chip, handle_edge_irq); + irq_set_nested_thread(virq, 1); + +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static struct irq_domain_ops lp8788_domain_ops = { + .map = lp8788_irq_map, +}; + +int lp8788_irq_init(struct lp8788 *lp, int irq) +{ + struct lp8788_irq_data *irqd; + int ret; + + if (irq <= 0) { + dev_warn(lp->dev, "invalid irq number: %d\n", irq); + return 0; + } + + irqd = devm_kzalloc(lp->dev, sizeof(*irqd), GFP_KERNEL); + if (!irqd) + return -ENOMEM; + + irqd->lp = lp; + irqd->domain = irq_domain_add_linear(lp->dev->of_node, LP8788_INT_MAX, + &lp8788_domain_ops, irqd); + if (!irqd->domain) { + dev_err(lp->dev, "failed to add irq domain err\n"); + return -EINVAL; + } + + lp->irqdm = irqd->domain; + mutex_init(&irqd->irq_lock); + + ret = request_threaded_irq(irq, NULL, lp8788_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "lp8788-irq", irqd); + if (ret) { + dev_err(lp->dev, "failed to create a thread for IRQ_N\n"); + return ret; + } + + lp->irq = irq; + + return 0; +} + +void lp8788_irq_exit(struct lp8788 *lp) +{ + if (lp->irq) + free_irq(lp->irq, lp->irqdm); +} diff --git a/kernel/drivers/mfd/lp8788.c b/kernel/drivers/mfd/lp8788.c new file mode 100644 index 000000000..a30bc15fe --- /dev/null +++ b/kernel/drivers/mfd/lp8788.c @@ -0,0 +1,245 @@ +/* + * TI LP8788 MFD - core interface + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.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. + * + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/lp8788.h> +#include <linux/module.h> +#include <linux/slab.h> + +#define MAX_LP8788_REGISTERS 0xA2 + +#define MFD_DEV_SIMPLE(_name) \ +{ \ + .name = LP8788_DEV_##_name, \ +} + +#define MFD_DEV_WITH_ID(_name, _id) \ +{ \ + .name = LP8788_DEV_##_name, \ + .id = _id, \ +} + +#define MFD_DEV_WITH_RESOURCE(_name, _resource, num_resource) \ +{ \ + .name = LP8788_DEV_##_name, \ + .resources = _resource, \ + .num_resources = num_resource, \ +} + +static struct resource chg_irqs[] = { + /* Charger Interrupts */ + { + .start = LP8788_INT_CHG_INPUT_STATE, + .end = LP8788_INT_PRECHG_TIMEOUT, + .name = LP8788_CHG_IRQ, + .flags = IORESOURCE_IRQ, + }, + /* Power Routing Switch Interrupts */ + { + .start = LP8788_INT_ENTER_SYS_SUPPORT, + .end = LP8788_INT_EXIT_SYS_SUPPORT, + .name = LP8788_PRSW_IRQ, + .flags = IORESOURCE_IRQ, + }, + /* Battery Interrupts */ + { + .start = LP8788_INT_BATT_LOW, + .end = LP8788_INT_NO_BATT, + .name = LP8788_BATT_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource rtc_irqs[] = { + { + .start = LP8788_INT_RTC_ALARM1, + .end = LP8788_INT_RTC_ALARM2, + .name = LP8788_ALM_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell lp8788_devs[] = { + /* 4 bucks */ + MFD_DEV_WITH_ID(BUCK, 1), + MFD_DEV_WITH_ID(BUCK, 2), + MFD_DEV_WITH_ID(BUCK, 3), + MFD_DEV_WITH_ID(BUCK, 4), + + /* 12 digital ldos */ + MFD_DEV_WITH_ID(DLDO, 1), + MFD_DEV_WITH_ID(DLDO, 2), + MFD_DEV_WITH_ID(DLDO, 3), + MFD_DEV_WITH_ID(DLDO, 4), + MFD_DEV_WITH_ID(DLDO, 5), + MFD_DEV_WITH_ID(DLDO, 6), + MFD_DEV_WITH_ID(DLDO, 7), + MFD_DEV_WITH_ID(DLDO, 8), + MFD_DEV_WITH_ID(DLDO, 9), + MFD_DEV_WITH_ID(DLDO, 10), + MFD_DEV_WITH_ID(DLDO, 11), + MFD_DEV_WITH_ID(DLDO, 12), + + /* 10 analog ldos */ + MFD_DEV_WITH_ID(ALDO, 1), + MFD_DEV_WITH_ID(ALDO, 2), + MFD_DEV_WITH_ID(ALDO, 3), + MFD_DEV_WITH_ID(ALDO, 4), + MFD_DEV_WITH_ID(ALDO, 5), + MFD_DEV_WITH_ID(ALDO, 6), + MFD_DEV_WITH_ID(ALDO, 7), + MFD_DEV_WITH_ID(ALDO, 8), + MFD_DEV_WITH_ID(ALDO, 9), + MFD_DEV_WITH_ID(ALDO, 10), + + /* ADC */ + MFD_DEV_SIMPLE(ADC), + + /* battery charger */ + MFD_DEV_WITH_RESOURCE(CHARGER, chg_irqs, ARRAY_SIZE(chg_irqs)), + + /* rtc */ + MFD_DEV_WITH_RESOURCE(RTC, rtc_irqs, ARRAY_SIZE(rtc_irqs)), + + /* backlight */ + MFD_DEV_SIMPLE(BACKLIGHT), + + /* current sink for vibrator */ + MFD_DEV_SIMPLE(VIBRATOR), + + /* current sink for keypad LED */ + MFD_DEV_SIMPLE(KEYLED), +}; + +int lp8788_read_byte(struct lp8788 *lp, u8 reg, u8 *data) +{ + int ret; + unsigned int val; + + ret = regmap_read(lp->regmap, reg, &val); + if (ret < 0) { + dev_err(lp->dev, "failed to read 0x%.2x\n", reg); + return ret; + } + + *data = (u8)val; + return 0; +} +EXPORT_SYMBOL_GPL(lp8788_read_byte); + +int lp8788_read_multi_bytes(struct lp8788 *lp, u8 reg, u8 *data, size_t count) +{ + return regmap_bulk_read(lp->regmap, reg, data, count); +} +EXPORT_SYMBOL_GPL(lp8788_read_multi_bytes); + +int lp8788_write_byte(struct lp8788 *lp, u8 reg, u8 data) +{ + return regmap_write(lp->regmap, reg, data); +} +EXPORT_SYMBOL_GPL(lp8788_write_byte); + +int lp8788_update_bits(struct lp8788 *lp, u8 reg, u8 mask, u8 data) +{ + return regmap_update_bits(lp->regmap, reg, mask, data); +} +EXPORT_SYMBOL_GPL(lp8788_update_bits); + +static int lp8788_platform_init(struct lp8788 *lp) +{ + struct lp8788_platform_data *pdata = lp->pdata; + + return (pdata && pdata->init_func) ? pdata->init_func(lp) : 0; +} + +static const struct regmap_config lp8788_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX_LP8788_REGISTERS, +}; + +static int lp8788_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp8788 *lp; + struct lp8788_platform_data *pdata = dev_get_platdata(&cl->dev); + int ret; + + lp = devm_kzalloc(&cl->dev, sizeof(struct lp8788), GFP_KERNEL); + if (!lp) + return -ENOMEM; + + lp->regmap = devm_regmap_init_i2c(cl, &lp8788_regmap_config); + if (IS_ERR(lp->regmap)) { + ret = PTR_ERR(lp->regmap); + dev_err(&cl->dev, "regmap init i2c err: %d\n", ret); + return ret; + } + + lp->pdata = pdata; + lp->dev = &cl->dev; + i2c_set_clientdata(cl, lp); + + ret = lp8788_platform_init(lp); + if (ret) + return ret; + + ret = lp8788_irq_init(lp, cl->irq); + if (ret) + return ret; + + return mfd_add_devices(lp->dev, -1, lp8788_devs, + ARRAY_SIZE(lp8788_devs), NULL, 0, NULL); +} + +static int lp8788_remove(struct i2c_client *cl) +{ + struct lp8788 *lp = i2c_get_clientdata(cl); + + mfd_remove_devices(lp->dev); + lp8788_irq_exit(lp); + return 0; +} + +static const struct i2c_device_id lp8788_ids[] = { + {"lp8788", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8788_ids); + +static struct i2c_driver lp8788_driver = { + .driver = { + .name = "lp8788", + .owner = THIS_MODULE, + }, + .probe = lp8788_probe, + .remove = lp8788_remove, + .id_table = lp8788_ids, +}; + +static int __init lp8788_init(void) +{ + return i2c_add_driver(&lp8788_driver); +} +subsys_initcall(lp8788_init); + +static void __exit lp8788_exit(void) +{ + i2c_del_driver(&lp8788_driver); +} +module_exit(lp8788_exit); + +MODULE_DESCRIPTION("TI LP8788 MFD Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/lpc_ich.c b/kernel/drivers/mfd/lpc_ich.c new file mode 100644 index 000000000..12d960a60 --- /dev/null +++ b/kernel/drivers/mfd/lpc_ich.c @@ -0,0 +1,1091 @@ +/* + * lpc_ich.c - LPC interface for Intel ICH + * + * LPC bridge function of the Intel ICH contains many other + * functional units, such as Interrupt controllers, Timers, + * Power Management, System Management, GPIO, RTC, and LPC + * Configuration Registers. + * + * This driver is derived from lpc_sch. + + * Copyright (c) 2011 Extreme Engineering Solution, Inc. + * Author: Aaron Sierra <asierra@xes-inc.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This driver supports the following I/O Controller hubs: + * (See the intel documentation on http://developer.intel.com.) + * document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO) + * document number 290687-002, 298242-027: 82801BA (ICH2) + * document number 290733-003, 290739-013: 82801CA (ICH3-S) + * document number 290716-001, 290718-007: 82801CAM (ICH3-M) + * document number 290744-001, 290745-025: 82801DB (ICH4) + * document number 252337-001, 252663-008: 82801DBM (ICH4-M) + * document number 273599-001, 273645-002: 82801E (C-ICH) + * document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R) + * document number 300641-004, 300884-013: 6300ESB + * document number 301473-002, 301474-026: 82801F (ICH6) + * document number 313082-001, 313075-006: 631xESB, 632xESB + * document number 307013-003, 307014-024: 82801G (ICH7) + * document number 322896-001, 322897-001: NM10 + * document number 313056-003, 313057-017: 82801H (ICH8) + * document number 316972-004, 316973-012: 82801I (ICH9) + * document number 319973-002, 319974-002: 82801J (ICH10) + * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH) + * document number 320066-003, 320257-008: EP80597 (IICH) + * document number 324645-001, 324646-001: Cougar Point (CPT) + * document number TBD : Patsburg (PBG) + * document number TBD : DH89xxCC + * document number TBD : Panther Point + * document number TBD : Lynx Point + * document number TBD : Lynx Point-LP + * document number TBD : Wellsburg + * document number TBD : Avoton SoC + * document number TBD : Coleto Creek + * document number TBD : Wildcat Point-LP + * document number TBD : 9 Series + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <linux/mfd/core.h> +#include <linux/mfd/lpc_ich.h> + +#define ACPIBASE 0x40 +#define ACPIBASE_GPE_OFF 0x28 +#define ACPIBASE_GPE_END 0x2f +#define ACPIBASE_SMI_OFF 0x30 +#define ACPIBASE_SMI_END 0x33 +#define ACPIBASE_PMC_OFF 0x08 +#define ACPIBASE_PMC_END 0x0c +#define ACPIBASE_TCO_OFF 0x60 +#define ACPIBASE_TCO_END 0x7f +#define ACPICTRL_PMCBASE 0x44 + +#define ACPIBASE_GCS_OFF 0x3410 +#define ACPIBASE_GCS_END 0x3414 + +#define GPIOBASE_ICH0 0x58 +#define GPIOCTRL_ICH0 0x5C +#define GPIOBASE_ICH6 0x48 +#define GPIOCTRL_ICH6 0x4C + +#define RCBABASE 0xf0 + +#define wdt_io_res(i) wdt_res(0, i) +#define wdt_mem_res(i) wdt_res(ICH_RES_MEM_OFF, i) +#define wdt_res(b, i) (&wdt_ich_res[(b) + (i)]) + +struct lpc_ich_priv { + int chipset; + + int abase; /* ACPI base */ + int actrl_pbase; /* ACPI control or PMC base */ + int gbase; /* GPIO base */ + int gctrl; /* GPIO control */ + + int abase_save; /* Cached ACPI base value */ + int actrl_pbase_save; /* Cached ACPI control or PMC base value */ + int gctrl_save; /* Cached GPIO control value */ +}; + +static struct resource wdt_ich_res[] = { + /* ACPI - TCO */ + { + .flags = IORESOURCE_IO, + }, + /* ACPI - SMI */ + { + .flags = IORESOURCE_IO, + }, + /* GCS or PMC */ + { + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource gpio_ich_res[] = { + /* GPIO */ + { + .flags = IORESOURCE_IO, + }, + /* ACPI - GPE0 */ + { + .flags = IORESOURCE_IO, + }, +}; + +enum lpc_cells { + LPC_WDT = 0, + LPC_GPIO, +}; + +static struct mfd_cell lpc_ich_cells[] = { + [LPC_WDT] = { + .name = "iTCO_wdt", + .num_resources = ARRAY_SIZE(wdt_ich_res), + .resources = wdt_ich_res, + .ignore_resource_conflicts = true, + }, + [LPC_GPIO] = { + .name = "gpio_ich", + .num_resources = ARRAY_SIZE(gpio_ich_res), + .resources = gpio_ich_res, + .ignore_resource_conflicts = true, + }, +}; + +/* chipset related info */ +enum lpc_chipsets { + LPC_ICH = 0, /* ICH */ + LPC_ICH0, /* ICH0 */ + LPC_ICH2, /* ICH2 */ + LPC_ICH2M, /* ICH2-M */ + LPC_ICH3, /* ICH3-S */ + LPC_ICH3M, /* ICH3-M */ + LPC_ICH4, /* ICH4 */ + LPC_ICH4M, /* ICH4-M */ + LPC_CICH, /* C-ICH */ + LPC_ICH5, /* ICH5 & ICH5R */ + LPC_6300ESB, /* 6300ESB */ + LPC_ICH6, /* ICH6 & ICH6R */ + LPC_ICH6M, /* ICH6-M */ + LPC_ICH6W, /* ICH6W & ICH6RW */ + LPC_631XESB, /* 631xESB/632xESB */ + LPC_ICH7, /* ICH7 & ICH7R */ + LPC_ICH7DH, /* ICH7DH */ + LPC_ICH7M, /* ICH7-M & ICH7-U */ + LPC_ICH7MDH, /* ICH7-M DH */ + LPC_NM10, /* NM10 */ + LPC_ICH8, /* ICH8 & ICH8R */ + LPC_ICH8DH, /* ICH8DH */ + LPC_ICH8DO, /* ICH8DO */ + LPC_ICH8M, /* ICH8M */ + LPC_ICH8ME, /* ICH8M-E */ + LPC_ICH9, /* ICH9 */ + LPC_ICH9R, /* ICH9R */ + LPC_ICH9DH, /* ICH9DH */ + LPC_ICH9DO, /* ICH9DO */ + LPC_ICH9M, /* ICH9M */ + LPC_ICH9ME, /* ICH9M-E */ + LPC_ICH10, /* ICH10 */ + LPC_ICH10R, /* ICH10R */ + LPC_ICH10D, /* ICH10D */ + LPC_ICH10DO, /* ICH10DO */ + LPC_PCH, /* PCH Desktop Full Featured */ + LPC_PCHM, /* PCH Mobile Full Featured */ + LPC_P55, /* P55 */ + LPC_PM55, /* PM55 */ + LPC_H55, /* H55 */ + LPC_QM57, /* QM57 */ + LPC_H57, /* H57 */ + LPC_HM55, /* HM55 */ + LPC_Q57, /* Q57 */ + LPC_HM57, /* HM57 */ + LPC_PCHMSFF, /* PCH Mobile SFF Full Featured */ + LPC_QS57, /* QS57 */ + LPC_3400, /* 3400 */ + LPC_3420, /* 3420 */ + LPC_3450, /* 3450 */ + LPC_EP80579, /* EP80579 */ + LPC_CPT, /* Cougar Point */ + LPC_CPTD, /* Cougar Point Desktop */ + LPC_CPTM, /* Cougar Point Mobile */ + LPC_PBG, /* Patsburg */ + LPC_DH89XXCC, /* DH89xxCC */ + LPC_PPT, /* Panther Point */ + LPC_LPT, /* Lynx Point */ + LPC_LPT_LP, /* Lynx Point-LP */ + LPC_WBG, /* Wellsburg */ + LPC_AVN, /* Avoton SoC */ + LPC_BAYTRAIL, /* Bay Trail SoC */ + LPC_COLETO, /* Coleto Creek */ + LPC_WPT_LP, /* Wildcat Point-LP */ + LPC_BRASWELL, /* Braswell SoC */ + LPC_9S, /* 9 Series */ +}; + +static struct lpc_ich_info lpc_chipset_info[] = { + [LPC_ICH] = { + .name = "ICH", + .iTCO_version = 1, + }, + [LPC_ICH0] = { + .name = "ICH0", + .iTCO_version = 1, + }, + [LPC_ICH2] = { + .name = "ICH2", + .iTCO_version = 1, + }, + [LPC_ICH2M] = { + .name = "ICH2-M", + .iTCO_version = 1, + }, + [LPC_ICH3] = { + .name = "ICH3-S", + .iTCO_version = 1, + }, + [LPC_ICH3M] = { + .name = "ICH3-M", + .iTCO_version = 1, + }, + [LPC_ICH4] = { + .name = "ICH4", + .iTCO_version = 1, + }, + [LPC_ICH4M] = { + .name = "ICH4-M", + .iTCO_version = 1, + }, + [LPC_CICH] = { + .name = "C-ICH", + .iTCO_version = 1, + }, + [LPC_ICH5] = { + .name = "ICH5 or ICH5R", + .iTCO_version = 1, + }, + [LPC_6300ESB] = { + .name = "6300ESB", + .iTCO_version = 1, + }, + [LPC_ICH6] = { + .name = "ICH6 or ICH6R", + .iTCO_version = 2, + .gpio_version = ICH_V6_GPIO, + }, + [LPC_ICH6M] = { + .name = "ICH6-M", + .iTCO_version = 2, + .gpio_version = ICH_V6_GPIO, + }, + [LPC_ICH6W] = { + .name = "ICH6W or ICH6RW", + .iTCO_version = 2, + .gpio_version = ICH_V6_GPIO, + }, + [LPC_631XESB] = { + .name = "631xESB/632xESB", + .iTCO_version = 2, + .gpio_version = ICH_V6_GPIO, + }, + [LPC_ICH7] = { + .name = "ICH7 or ICH7R", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH7DH] = { + .name = "ICH7DH", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH7M] = { + .name = "ICH7-M or ICH7-U", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH7MDH] = { + .name = "ICH7-M DH", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_NM10] = { + .name = "NM10", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH8] = { + .name = "ICH8 or ICH8R", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH8DH] = { + .name = "ICH8DH", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH8DO] = { + .name = "ICH8DO", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH8M] = { + .name = "ICH8M", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH8ME] = { + .name = "ICH8M-E", + .iTCO_version = 2, + .gpio_version = ICH_V7_GPIO, + }, + [LPC_ICH9] = { + .name = "ICH9", + .iTCO_version = 2, + .gpio_version = ICH_V9_GPIO, + }, + [LPC_ICH9R] = { + .name = "ICH9R", + .iTCO_version = 2, + .gpio_version = ICH_V9_GPIO, + }, + [LPC_ICH9DH] = { + .name = "ICH9DH", + .iTCO_version = 2, + .gpio_version = ICH_V9_GPIO, + }, + [LPC_ICH9DO] = { + .name = "ICH9DO", + .iTCO_version = 2, + .gpio_version = ICH_V9_GPIO, + }, + [LPC_ICH9M] = { + .name = "ICH9M", + .iTCO_version = 2, + .gpio_version = ICH_V9_GPIO, + }, + [LPC_ICH9ME] = { + .name = "ICH9M-E", + .iTCO_version = 2, + .gpio_version = ICH_V9_GPIO, + }, + [LPC_ICH10] = { + .name = "ICH10", + .iTCO_version = 2, + .gpio_version = ICH_V10CONS_GPIO, + }, + [LPC_ICH10R] = { + .name = "ICH10R", + .iTCO_version = 2, + .gpio_version = ICH_V10CONS_GPIO, + }, + [LPC_ICH10D] = { + .name = "ICH10D", + .iTCO_version = 2, + .gpio_version = ICH_V10CORP_GPIO, + }, + [LPC_ICH10DO] = { + .name = "ICH10DO", + .iTCO_version = 2, + .gpio_version = ICH_V10CORP_GPIO, + }, + [LPC_PCH] = { + .name = "PCH Desktop Full Featured", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_PCHM] = { + .name = "PCH Mobile Full Featured", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_P55] = { + .name = "P55", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_PM55] = { + .name = "PM55", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_H55] = { + .name = "H55", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_QM57] = { + .name = "QM57", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_H57] = { + .name = "H57", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_HM55] = { + .name = "HM55", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_Q57] = { + .name = "Q57", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_HM57] = { + .name = "HM57", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_PCHMSFF] = { + .name = "PCH Mobile SFF Full Featured", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_QS57] = { + .name = "QS57", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_3400] = { + .name = "3400", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_3420] = { + .name = "3420", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_3450] = { + .name = "3450", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_EP80579] = { + .name = "EP80579", + .iTCO_version = 2, + }, + [LPC_CPT] = { + .name = "Cougar Point", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_CPTD] = { + .name = "Cougar Point Desktop", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_CPTM] = { + .name = "Cougar Point Mobile", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_PBG] = { + .name = "Patsburg", + .iTCO_version = 2, + }, + [LPC_DH89XXCC] = { + .name = "DH89xxCC", + .iTCO_version = 2, + }, + [LPC_PPT] = { + .name = "Panther Point", + .iTCO_version = 2, + .gpio_version = ICH_V5_GPIO, + }, + [LPC_LPT] = { + .name = "Lynx Point", + .iTCO_version = 2, + }, + [LPC_LPT_LP] = { + .name = "Lynx Point_LP", + .iTCO_version = 2, + }, + [LPC_WBG] = { + .name = "Wellsburg", + .iTCO_version = 2, + }, + [LPC_AVN] = { + .name = "Avoton SoC", + .iTCO_version = 3, + .gpio_version = AVOTON_GPIO, + }, + [LPC_BAYTRAIL] = { + .name = "Bay Trail SoC", + .iTCO_version = 3, + }, + [LPC_COLETO] = { + .name = "Coleto Creek", + .iTCO_version = 2, + }, + [LPC_WPT_LP] = { + .name = "Wildcat Point_LP", + .iTCO_version = 2, + }, + [LPC_BRASWELL] = { + .name = "Braswell SoC", + .iTCO_version = 3, + }, + [LPC_9S] = { + .name = "9 Series", + .iTCO_version = 2, + }, +}; + +/* + * This data only exists for exporting the supported PCI ids + * via MODULE_DEVICE_TABLE. We do not actually register a + * pci_driver, because the I/O Controller Hub has also other + * functions that probably will be registered by other drivers. + */ +static const struct pci_device_id lpc_ich_ids[] = { + { PCI_VDEVICE(INTEL, 0x0f1c), LPC_BAYTRAIL}, + { PCI_VDEVICE(INTEL, 0x1c41), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c42), LPC_CPTD}, + { PCI_VDEVICE(INTEL, 0x1c43), LPC_CPTM}, + { PCI_VDEVICE(INTEL, 0x1c44), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c45), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c46), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c47), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c48), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c49), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4a), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4b), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4c), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4d), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4e), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4f), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c50), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c51), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c52), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c53), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c54), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c55), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c56), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c57), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c58), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c59), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5a), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5b), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5c), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5d), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5e), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5f), LPC_CPT}, + { PCI_VDEVICE(INTEL, 0x1d40), LPC_PBG}, + { PCI_VDEVICE(INTEL, 0x1d41), LPC_PBG}, + { PCI_VDEVICE(INTEL, 0x1e40), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e41), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e42), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e43), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e44), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e45), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e46), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e47), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e48), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e49), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4a), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4b), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4c), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4d), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4e), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4f), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e50), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e51), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e52), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e53), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e54), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e55), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e56), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e57), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e58), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e59), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5a), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5b), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5c), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5d), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5e), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5f), LPC_PPT}, + { PCI_VDEVICE(INTEL, 0x1f38), LPC_AVN}, + { PCI_VDEVICE(INTEL, 0x1f39), LPC_AVN}, + { PCI_VDEVICE(INTEL, 0x1f3a), LPC_AVN}, + { PCI_VDEVICE(INTEL, 0x1f3b), LPC_AVN}, + { PCI_VDEVICE(INTEL, 0x229c), LPC_BRASWELL}, + { PCI_VDEVICE(INTEL, 0x2310), LPC_DH89XXCC}, + { PCI_VDEVICE(INTEL, 0x2390), LPC_COLETO}, + { PCI_VDEVICE(INTEL, 0x2410), LPC_ICH}, + { PCI_VDEVICE(INTEL, 0x2420), LPC_ICH0}, + { PCI_VDEVICE(INTEL, 0x2440), LPC_ICH2}, + { PCI_VDEVICE(INTEL, 0x244c), LPC_ICH2M}, + { PCI_VDEVICE(INTEL, 0x2450), LPC_CICH}, + { PCI_VDEVICE(INTEL, 0x2480), LPC_ICH3}, + { PCI_VDEVICE(INTEL, 0x248c), LPC_ICH3M}, + { PCI_VDEVICE(INTEL, 0x24c0), LPC_ICH4}, + { PCI_VDEVICE(INTEL, 0x24cc), LPC_ICH4M}, + { PCI_VDEVICE(INTEL, 0x24d0), LPC_ICH5}, + { PCI_VDEVICE(INTEL, 0x25a1), LPC_6300ESB}, + { PCI_VDEVICE(INTEL, 0x2640), LPC_ICH6}, + { PCI_VDEVICE(INTEL, 0x2641), LPC_ICH6M}, + { PCI_VDEVICE(INTEL, 0x2642), LPC_ICH6W}, + { PCI_VDEVICE(INTEL, 0x2670), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2671), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2672), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2673), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2674), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2675), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2676), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2677), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2678), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x2679), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x267a), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x267b), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x267c), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x267d), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x267e), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x267f), LPC_631XESB}, + { PCI_VDEVICE(INTEL, 0x27b0), LPC_ICH7DH}, + { PCI_VDEVICE(INTEL, 0x27b8), LPC_ICH7}, + { PCI_VDEVICE(INTEL, 0x27b9), LPC_ICH7M}, + { PCI_VDEVICE(INTEL, 0x27bc), LPC_NM10}, + { PCI_VDEVICE(INTEL, 0x27bd), LPC_ICH7MDH}, + { PCI_VDEVICE(INTEL, 0x2810), LPC_ICH8}, + { PCI_VDEVICE(INTEL, 0x2811), LPC_ICH8ME}, + { PCI_VDEVICE(INTEL, 0x2812), LPC_ICH8DH}, + { PCI_VDEVICE(INTEL, 0x2814), LPC_ICH8DO}, + { PCI_VDEVICE(INTEL, 0x2815), LPC_ICH8M}, + { PCI_VDEVICE(INTEL, 0x2912), LPC_ICH9DH}, + { PCI_VDEVICE(INTEL, 0x2914), LPC_ICH9DO}, + { PCI_VDEVICE(INTEL, 0x2916), LPC_ICH9R}, + { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME}, + { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9}, + { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M}, + { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO}, + { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R}, + { PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10}, + { PCI_VDEVICE(INTEL, 0x3a1a), LPC_ICH10D}, + { PCI_VDEVICE(INTEL, 0x3b00), LPC_PCH}, + { PCI_VDEVICE(INTEL, 0x3b01), LPC_PCHM}, + { PCI_VDEVICE(INTEL, 0x3b02), LPC_P55}, + { PCI_VDEVICE(INTEL, 0x3b03), LPC_PM55}, + { PCI_VDEVICE(INTEL, 0x3b06), LPC_H55}, + { PCI_VDEVICE(INTEL, 0x3b07), LPC_QM57}, + { PCI_VDEVICE(INTEL, 0x3b08), LPC_H57}, + { PCI_VDEVICE(INTEL, 0x3b09), LPC_HM55}, + { PCI_VDEVICE(INTEL, 0x3b0a), LPC_Q57}, + { PCI_VDEVICE(INTEL, 0x3b0b), LPC_HM57}, + { PCI_VDEVICE(INTEL, 0x3b0d), LPC_PCHMSFF}, + { PCI_VDEVICE(INTEL, 0x3b0f), LPC_QS57}, + { PCI_VDEVICE(INTEL, 0x3b12), LPC_3400}, + { PCI_VDEVICE(INTEL, 0x3b14), LPC_3420}, + { PCI_VDEVICE(INTEL, 0x3b16), LPC_3450}, + { PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579}, + { PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c43), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c44), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c45), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c46), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c47), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c48), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c49), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4a), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4b), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4c), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4d), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4e), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4f), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c50), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c51), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c52), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c53), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c54), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c55), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c56), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c57), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c58), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c59), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5a), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5b), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5c), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5d), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5e), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5f), LPC_LPT}, + { PCI_VDEVICE(INTEL, 0x8cc1), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc2), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc3), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc4), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc6), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8d40), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d41), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d42), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d43), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d44), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d45), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d46), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d47), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d48), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d49), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d4a), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d4b), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d4c), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d4d), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d4e), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d4f), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d50), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d51), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d52), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d53), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d54), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d55), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d56), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d57), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d58), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d59), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d5a), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d5b), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d5c), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d5d), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d5e), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x8d5f), LPC_WBG}, + { PCI_VDEVICE(INTEL, 0x9c40), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c41), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c42), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c43), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c44), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c45), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c46), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9c47), LPC_LPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc1), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc2), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc3), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc5), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc6), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc7), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x9cc9), LPC_WPT_LP}, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, lpc_ich_ids); + +static void lpc_ich_restore_config_space(struct pci_dev *dev) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + + if (priv->abase_save >= 0) { + pci_write_config_byte(dev, priv->abase, priv->abase_save); + priv->abase_save = -1; + } + + if (priv->actrl_pbase_save >= 0) { + pci_write_config_byte(dev, priv->actrl_pbase, + priv->actrl_pbase_save); + priv->actrl_pbase_save = -1; + } + + if (priv->gctrl_save >= 0) { + pci_write_config_byte(dev, priv->gctrl, priv->gctrl_save); + priv->gctrl_save = -1; + } +} + +static void lpc_ich_enable_acpi_space(struct pci_dev *dev) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + u8 reg_save; + + switch (lpc_chipset_info[priv->chipset].iTCO_version) { + case 3: + /* + * Some chipsets (eg Avoton) enable the ACPI space in the + * ACPI BASE register. + */ + pci_read_config_byte(dev, priv->abase, ®_save); + pci_write_config_byte(dev, priv->abase, reg_save | 0x2); + priv->abase_save = reg_save; + break; + default: + /* + * Most chipsets enable the ACPI space in the ACPI control + * register. + */ + pci_read_config_byte(dev, priv->actrl_pbase, ®_save); + pci_write_config_byte(dev, priv->actrl_pbase, reg_save | 0x80); + priv->actrl_pbase_save = reg_save; + break; + } +} + +static void lpc_ich_enable_gpio_space(struct pci_dev *dev) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + u8 reg_save; + + pci_read_config_byte(dev, priv->gctrl, ®_save); + pci_write_config_byte(dev, priv->gctrl, reg_save | 0x10); + priv->gctrl_save = reg_save; +} + +static void lpc_ich_enable_pmc_space(struct pci_dev *dev) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + u8 reg_save; + + pci_read_config_byte(dev, priv->actrl_pbase, ®_save); + pci_write_config_byte(dev, priv->actrl_pbase, reg_save | 0x2); + + priv->actrl_pbase_save = reg_save; +} + +static void lpc_ich_finalize_cell(struct pci_dev *dev, struct mfd_cell *cell) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + + cell->platform_data = &lpc_chipset_info[priv->chipset]; + cell->pdata_size = sizeof(struct lpc_ich_info); +} + +/* + * We don't check for resource conflict globally. There are 2 or 3 independent + * GPIO groups and it's enough to have access to one of these to instantiate + * the device. + */ +static int lpc_ich_check_conflict_gpio(struct resource *res) +{ + int ret; + u8 use_gpio = 0; + + if (resource_size(res) >= 0x50 && + !acpi_check_region(res->start + 0x40, 0x10, "LPC ICH GPIO3")) + use_gpio |= 1 << 2; + + if (!acpi_check_region(res->start + 0x30, 0x10, "LPC ICH GPIO2")) + use_gpio |= 1 << 1; + + ret = acpi_check_region(res->start + 0x00, 0x30, "LPC ICH GPIO1"); + if (!ret) + use_gpio |= 1 << 0; + + return use_gpio ? use_gpio : ret; +} + +static int lpc_ich_init_gpio(struct pci_dev *dev) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + u32 base_addr_cfg; + u32 base_addr; + int ret; + bool acpi_conflict = false; + struct resource *res; + + /* Setup power management base register */ + pci_read_config_dword(dev, priv->abase, &base_addr_cfg); + base_addr = base_addr_cfg & 0x0000ff80; + if (!base_addr) { + dev_notice(&dev->dev, "I/O space for ACPI uninitialized\n"); + lpc_ich_cells[LPC_GPIO].num_resources--; + goto gpe0_done; + } + + res = &gpio_ich_res[ICH_RES_GPE0]; + res->start = base_addr + ACPIBASE_GPE_OFF; + res->end = base_addr + ACPIBASE_GPE_END; + ret = acpi_check_resource_conflict(res); + if (ret) { + /* + * This isn't fatal for the GPIO, but we have to make sure that + * the platform_device subsystem doesn't see this resource + * or it will register an invalid region. + */ + lpc_ich_cells[LPC_GPIO].num_resources--; + acpi_conflict = true; + } else { + lpc_ich_enable_acpi_space(dev); + } + +gpe0_done: + /* Setup GPIO base register */ + pci_read_config_dword(dev, priv->gbase, &base_addr_cfg); + base_addr = base_addr_cfg & 0x0000ff80; + if (!base_addr) { + dev_notice(&dev->dev, "I/O space for GPIO uninitialized\n"); + ret = -ENODEV; + goto gpio_done; + } + + /* Older devices provide fewer GPIO and have a smaller resource size. */ + res = &gpio_ich_res[ICH_RES_GPIO]; + res->start = base_addr; + switch (lpc_chipset_info[priv->chipset].gpio_version) { + case ICH_V5_GPIO: + case ICH_V10CORP_GPIO: + res->end = res->start + 128 - 1; + break; + default: + res->end = res->start + 64 - 1; + break; + } + + ret = lpc_ich_check_conflict_gpio(res); + if (ret < 0) { + /* this isn't necessarily fatal for the GPIO */ + acpi_conflict = true; + goto gpio_done; + } + lpc_chipset_info[priv->chipset].use_gpio = ret; + lpc_ich_enable_gpio_space(dev); + + lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_GPIO]); + ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_GPIO], + 1, NULL, 0, NULL); + +gpio_done: + if (acpi_conflict) + pr_warn("Resource conflict(s) found affecting %s\n", + lpc_ich_cells[LPC_GPIO].name); + return ret; +} + +static int lpc_ich_init_wdt(struct pci_dev *dev) +{ + struct lpc_ich_priv *priv = pci_get_drvdata(dev); + u32 base_addr_cfg; + u32 base_addr; + int ret; + struct resource *res; + + /* Setup power management base register */ + pci_read_config_dword(dev, priv->abase, &base_addr_cfg); + base_addr = base_addr_cfg & 0x0000ff80; + if (!base_addr) { + dev_notice(&dev->dev, "I/O space for ACPI uninitialized\n"); + ret = -ENODEV; + goto wdt_done; + } + + res = wdt_io_res(ICH_RES_IO_TCO); + res->start = base_addr + ACPIBASE_TCO_OFF; + res->end = base_addr + ACPIBASE_TCO_END; + + res = wdt_io_res(ICH_RES_IO_SMI); + res->start = base_addr + ACPIBASE_SMI_OFF; + res->end = base_addr + ACPIBASE_SMI_END; + + lpc_ich_enable_acpi_space(dev); + + /* + * iTCO v2: + * Get the Memory-Mapped GCS register. To get access to it + * we have to read RCBA from PCI Config space 0xf0 and use + * it as base. GCS = RCBA + ICH6_GCS(0x3410). + * + * iTCO v3: + * Get the Power Management Configuration register. To get access + * to it we have to read the PMC BASE from config space and address + * the register at offset 0x8. + */ + if (lpc_chipset_info[priv->chipset].iTCO_version == 1) { + /* Don't register iomem for TCO ver 1 */ + lpc_ich_cells[LPC_WDT].num_resources--; + } else if (lpc_chipset_info[priv->chipset].iTCO_version == 2) { + pci_read_config_dword(dev, RCBABASE, &base_addr_cfg); + base_addr = base_addr_cfg & 0xffffc000; + if (!(base_addr_cfg & 1)) { + dev_notice(&dev->dev, "RCBA is disabled by " + "hardware/BIOS, device disabled\n"); + ret = -ENODEV; + goto wdt_done; + } + res = wdt_mem_res(ICH_RES_MEM_GCS_PMC); + res->start = base_addr + ACPIBASE_GCS_OFF; + res->end = base_addr + ACPIBASE_GCS_END; + } else if (lpc_chipset_info[priv->chipset].iTCO_version == 3) { + lpc_ich_enable_pmc_space(dev); + pci_read_config_dword(dev, ACPICTRL_PMCBASE, &base_addr_cfg); + base_addr = base_addr_cfg & 0xfffffe00; + + res = wdt_mem_res(ICH_RES_MEM_GCS_PMC); + res->start = base_addr + ACPIBASE_PMC_OFF; + res->end = base_addr + ACPIBASE_PMC_END; + } + + lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_WDT]); + ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_WDT], + 1, NULL, 0, NULL); + +wdt_done: + return ret; +} + +static int lpc_ich_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct lpc_ich_priv *priv; + int ret; + bool cell_added = false; + + priv = devm_kzalloc(&dev->dev, + sizeof(struct lpc_ich_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->chipset = id->driver_data; + + priv->actrl_pbase_save = -1; + priv->abase_save = -1; + + priv->abase = ACPIBASE; + priv->actrl_pbase = ACPICTRL_PMCBASE; + + priv->gctrl_save = -1; + if (priv->chipset <= LPC_ICH5) { + priv->gbase = GPIOBASE_ICH0; + priv->gctrl = GPIOCTRL_ICH0; + } else { + priv->gbase = GPIOBASE_ICH6; + priv->gctrl = GPIOCTRL_ICH6; + } + + pci_set_drvdata(dev, priv); + + if (lpc_chipset_info[priv->chipset].iTCO_version) { + ret = lpc_ich_init_wdt(dev); + if (!ret) + cell_added = true; + } + + if (lpc_chipset_info[priv->chipset].gpio_version) { + ret = lpc_ich_init_gpio(dev); + if (!ret) + cell_added = true; + } + + /* + * We only care if at least one or none of the cells registered + * successfully. + */ + if (!cell_added) { + dev_warn(&dev->dev, "No MFD cells added\n"); + lpc_ich_restore_config_space(dev); + return -ENODEV; + } + + return 0; +} + +static void lpc_ich_remove(struct pci_dev *dev) +{ + mfd_remove_devices(&dev->dev); + lpc_ich_restore_config_space(dev); +} + +static struct pci_driver lpc_ich_driver = { + .name = "lpc_ich", + .id_table = lpc_ich_ids, + .probe = lpc_ich_probe, + .remove = lpc_ich_remove, +}; + +module_pci_driver(lpc_ich_driver); + +MODULE_AUTHOR("Aaron Sierra <asierra@xes-inc.com>"); +MODULE_DESCRIPTION("LPC interface for Intel ICH"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/lpc_sch.c b/kernel/drivers/mfd/lpc_sch.c new file mode 100644 index 000000000..a56e4ba52 --- /dev/null +++ b/kernel/drivers/mfd/lpc_sch.c @@ -0,0 +1,216 @@ +/* + * lpc_sch.c - LPC interface for Intel Poulsbo SCH + * + * LPC bridge function of the Intel SCH contains many other + * functional units, such as Interrupt controllers, Timers, + * Power Management, System Management, GPIO, RTC, and LPC + * Configuration Registers. + * + * Copyright (c) 2010 CompuLab Ltd + * Copyright (c) 2014 Intel Corp. + * Author: Denis Turischev <denis@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <linux/mfd/core.h> + +#define SMBASE 0x40 +#define SMBUS_IO_SIZE 64 + +#define GPIOBASE 0x44 +#define GPIO_IO_SIZE 64 +#define GPIO_IO_SIZE_CENTERTON 128 + +/* Intel Quark X1000 GPIO IRQ Number */ +#define GPIO_IRQ_QUARK_X1000 9 + +#define WDTBASE 0x84 +#define WDT_IO_SIZE 64 + +enum sch_chipsets { + LPC_SCH = 0, /* Intel Poulsbo SCH */ + LPC_ITC, /* Intel Tunnel Creek */ + LPC_CENTERTON, /* Intel Centerton */ + LPC_QUARK_X1000, /* Intel Quark X1000 */ +}; + +struct lpc_sch_info { + unsigned int io_size_smbus; + unsigned int io_size_gpio; + unsigned int io_size_wdt; + int irq_gpio; +}; + +static struct lpc_sch_info sch_chipset_info[] = { + [LPC_SCH] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE, + .irq_gpio = -1, + }, + [LPC_ITC] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE, + .io_size_wdt = WDT_IO_SIZE, + .irq_gpio = -1, + }, + [LPC_CENTERTON] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE_CENTERTON, + .io_size_wdt = WDT_IO_SIZE, + .irq_gpio = -1, + }, + [LPC_QUARK_X1000] = { + .io_size_gpio = GPIO_IO_SIZE, + .irq_gpio = GPIO_IRQ_QUARK_X1000, + .io_size_wdt = WDT_IO_SIZE, + }, +}; + +static const struct pci_device_id lpc_sch_ids[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC), LPC_SCH }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC), LPC_ITC }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CENTERTON_ILB), LPC_CENTERTON }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_ILB), LPC_QUARK_X1000 }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, lpc_sch_ids); + +#define LPC_NO_RESOURCE 1 +#define LPC_SKIP_RESOURCE 2 + +static int lpc_sch_get_io(struct pci_dev *pdev, int where, const char *name, + struct resource *res, int size) +{ + unsigned int base_addr_cfg; + unsigned short base_addr; + + if (size == 0) + return LPC_NO_RESOURCE; + + pci_read_config_dword(pdev, where, &base_addr_cfg); + base_addr = 0; + if (!(base_addr_cfg & (1 << 31))) + dev_warn(&pdev->dev, "Decode of the %s I/O range disabled\n", + name); + else + base_addr = (unsigned short)base_addr_cfg; + + if (base_addr == 0) { + dev_warn(&pdev->dev, "I/O space for %s uninitialized\n", name); + return LPC_SKIP_RESOURCE; + } + + res->start = base_addr; + res->end = base_addr + size - 1; + res->flags = IORESOURCE_IO; + + return 0; +} + +static int lpc_sch_populate_cell(struct pci_dev *pdev, int where, + const char *name, int size, int irq, + int id, struct mfd_cell *cell) +{ + struct resource *res; + int ret; + + res = devm_kcalloc(&pdev->dev, 2, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + ret = lpc_sch_get_io(pdev, where, name, res, size); + if (ret) + return ret; + + memset(cell, 0, sizeof(*cell)); + + cell->name = name; + cell->resources = res; + cell->num_resources = 1; + cell->ignore_resource_conflicts = true; + cell->id = id; + + /* Check if we need to add an IRQ resource */ + if (irq < 0) + return 0; + + res++; + + res->start = irq; + res->end = irq; + res->flags = IORESOURCE_IRQ; + + cell->num_resources++; + + return 0; +} + +static int lpc_sch_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct mfd_cell lpc_sch_cells[3]; + struct lpc_sch_info *info = &sch_chipset_info[id->driver_data]; + unsigned int cells = 0; + int ret; + + ret = lpc_sch_populate_cell(dev, SMBASE, "isch_smbus", + info->io_size_smbus, -1, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + ret = lpc_sch_populate_cell(dev, GPIOBASE, "sch_gpio", + info->io_size_gpio, info->irq_gpio, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + ret = lpc_sch_populate_cell(dev, WDTBASE, "ie6xx_wdt", + info->io_size_wdt, -1, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + if (cells == 0) { + dev_err(&dev->dev, "All decode registers disabled.\n"); + return -ENODEV; + } + + return mfd_add_devices(&dev->dev, 0, lpc_sch_cells, cells, NULL, 0, NULL); +} + +static void lpc_sch_remove(struct pci_dev *dev) +{ + mfd_remove_devices(&dev->dev); +} + +static struct pci_driver lpc_sch_driver = { + .name = "lpc_sch", + .id_table = lpc_sch_ids, + .probe = lpc_sch_probe, + .remove = lpc_sch_remove, +}; + +module_pci_driver(lpc_sch_driver); + +MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); +MODULE_DESCRIPTION("LPC interface for Intel Poulsbo SCH"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max14577.c b/kernel/drivers/mfd/max14577.c new file mode 100644 index 000000000..3bf8def82 --- /dev/null +++ b/kernel/drivers/mfd/max14577.c @@ -0,0 +1,575 @@ +/* + * max14577.c - mfd core driver for the Maxim 14577/77836 + * + * Copyright (C) 2014 Samsung Electronics + * Chanwoo Choi <cw00.choi@samsung.com> + * Krzysztof Kozlowski <k.kozlowski@samsung.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. + * + * 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. + * + * This driver is based on max8997.c + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/of_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max14577.h> +#include <linux/mfd/max14577-private.h> + +/* + * Table of valid charger currents for different Maxim chipsets. + * It is placed here because it is used by both charger and regulator driver. + */ +const struct maxim_charger_current maxim_charger_currents[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, + [MAXIM_DEVICE_TYPE_MAX14577] = { + .min = MAX14577_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX14577_CHARGER_CURRENT_LIMIT_MAX, + }, + [MAXIM_DEVICE_TYPE_MAX77836] = { + .min = MAX77836_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX77836_CHARGER_CURRENT_LIMIT_MAX, + }, +}; +EXPORT_SYMBOL_GPL(maxim_charger_currents); + +/* + * maxim_charger_calc_reg_current - Calculate register value for current + * @limits: constraints for charger, matching the MBCICHWRC register + * @min_ua: minimal requested current, micro Amps + * @max_ua: maximum requested current, micro Amps + * @dst: destination to store calculated register value + * + * Calculates the value of MBCICHWRC (Fast Battery Charge Current) register + * for given current and stores it under pointed 'dst'. The stored value + * combines low bit (MBCICHWRCL) and high bits (MBCICHWRCH). It is also + * properly shifted. + * + * The calculated register value matches the current which: + * - is always between <limits.min, limits.max>; + * - is always less or equal to max_ua; + * - is the highest possible value; + * - may be lower than min_ua. + * + * On success returns 0. On error returns -EINVAL (requested min/max current + * is outside of given charger limits) and 'dst' is not set. + */ +int maxim_charger_calc_reg_current(const struct maxim_charger_current *limits, + unsigned int min_ua, unsigned int max_ua, u8 *dst) +{ + unsigned int current_bits = 0xf; + + if (min_ua > max_ua) + return -EINVAL; + + if (min_ua > limits->max || max_ua < limits->min) + return -EINVAL; + + if (max_ua < limits->high_start) { + /* + * Less than high_start, so set the minimal current + * (turn Low Bit off, 0 as high bits). + */ + *dst = 0x0; + return 0; + } + + /* max_ua is in range: <high_start, infinite>, cut it to limits.max */ + max_ua = min(limits->max, max_ua); + max_ua -= limits->high_start; + /* + * There is no risk of overflow 'max_ua' here because: + * - max_ua >= limits.high_start + * - BUILD_BUG checks that 'limits' are: max >= high_start + high_step + */ + current_bits = max_ua / limits->high_step; + + /* Turn Low Bit on (use range <limits.high_start, limits.max>) ... */ + *dst = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + /* and set proper High Bits */ + *dst |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + + return 0; +} +EXPORT_SYMBOL_GPL(maxim_charger_calc_reg_current); + +static const struct mfd_cell max14577_devs[] = { + { + .name = "max14577-muic", + .of_compatible = "maxim,max14577-muic", + }, + { + .name = "max14577-regulator", + .of_compatible = "maxim,max14577-regulator", + }, + { + .name = "max14577-charger", + .of_compatible = "maxim,max14577-charger", + }, +}; + +static const struct mfd_cell max77836_devs[] = { + { + .name = "max77836-muic", + .of_compatible = "maxim,max77836-muic", + }, + { + .name = "max77836-regulator", + .of_compatible = "maxim,max77836-regulator", + }, + { + .name = "max77836-charger", + .of_compatible = "maxim,max77836-charger", + }, + { + .name = "max77836-battery", + .of_compatible = "maxim,max77836-battery", + }, +}; + +static const struct of_device_id max14577_dt_match[] = { + { + .compatible = "maxim,max14577", + .data = (void *)MAXIM_DEVICE_TYPE_MAX14577, + }, + { + .compatible = "maxim,max77836", + .data = (void *)MAXIM_DEVICE_TYPE_MAX77836, + }, + {}, +}; + +static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3: + return true; + default: + break; + } + return false; +} + +static bool max77836_muic_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Any max14577 volatile registers are also max77836 volatile. */ + if (max14577_muic_volatile_reg(dev, reg)) + return true; + + switch (reg) { + case MAX77836_FG_REG_VCELL_MSB ... MAX77836_FG_REG_SOC_LSB: + case MAX77836_FG_REG_CRATE_MSB ... MAX77836_FG_REG_CRATE_LSB: + case MAX77836_FG_REG_STATUS_H ... MAX77836_FG_REG_STATUS_L: + case MAX77836_PMIC_REG_INTSRC: + case MAX77836_PMIC_REG_TOPSYS_INT: + case MAX77836_PMIC_REG_TOPSYS_STAT: + return true; + default: + break; + } + return false; +} + +static const struct regmap_config max14577_muic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max14577_muic_volatile_reg, + .max_register = MAX14577_REG_END, +}; + +static const struct regmap_config max77836_pmic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max77836_muic_volatile_reg, + .max_register = MAX77836_PMIC_REG_END, +}; + +static const struct regmap_irq max14577_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, + /* INT2 interrupts */ + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, + /* INT3 interrupts */ + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, +}; + +static const struct regmap_irq_chip max14577_irq_chip = { + .name = "max14577", + .status_base = MAX14577_REG_INT1, + .mask_base = MAX14577_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max14577_irqs, + .num_irqs = ARRAY_SIZE(max14577_irqs), +}; + +static const struct regmap_irq max77836_muic_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX77836_INT1_ADC1K_MASK, }, + /* INT2 interrupts */ + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX77836_INT2_VIDRM_MASK, }, + /* INT3 interrupts */ + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, +}; + +static const struct regmap_irq_chip max77836_muic_irq_chip = { + .name = "max77836-muic", + .status_base = MAX14577_REG_INT1, + .mask_base = MAX14577_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77836_muic_irqs, + .num_irqs = ARRAY_SIZE(max77836_muic_irqs), +}; + +static const struct regmap_irq max77836_pmic_irqs[] = { + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T120C_MASK, }, + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T140C_MASK, }, +}; + +static const struct regmap_irq_chip max77836_pmic_irq_chip = { + .name = "max77836-pmic", + .status_base = MAX77836_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77836_PMIC_REG_TOPSYS_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77836_pmic_irqs, + .num_irqs = ARRAY_SIZE(max77836_pmic_irqs), +}; + +static void max14577_print_dev_type(struct max14577 *max14577) +{ + u8 reg_data, vendor_id, device_id; + int ret; + + ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, + ®_data); + if (ret) { + dev_err(max14577->dev, + "Failed to read DEVICEID register: %d\n", ret); + return; + } + + vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> + DEVID_VENDORID_SHIFT); + device_id = ((reg_data & DEVID_DEVICEID_MASK) >> + DEVID_DEVICEID_SHIFT); + + dev_info(max14577->dev, "Device type: %u (ID: 0x%x, vendor: 0x%x)\n", + max14577->dev_type, device_id, vendor_id); +} + +/* + * Max77836 specific initialization code for driver probe. + * Adds new I2C dummy device, regmap and regmap IRQ chip. + * Unmasks Interrupt Source register. + * + * On success returns 0. + * On failure returns errno and reverts any changes done so far (e.g. remove + * I2C dummy device), except masking the INT SRC register. + */ +static int max77836_init(struct max14577 *max14577) +{ + int ret; + u8 intsrc_mask; + + max14577->i2c_pmic = i2c_new_dummy(max14577->i2c->adapter, + I2C_ADDR_PMIC); + if (!max14577->i2c_pmic) { + dev_err(max14577->dev, "Failed to register PMIC I2C device\n"); + return -ENODEV; + } + i2c_set_clientdata(max14577->i2c_pmic, max14577); + + max14577->regmap_pmic = devm_regmap_init_i2c(max14577->i2c_pmic, + &max77836_pmic_regmap_config); + if (IS_ERR(max14577->regmap_pmic)) { + ret = PTR_ERR(max14577->regmap_pmic); + dev_err(max14577->dev, "Failed to allocate PMIC register map: %d\n", + ret); + goto err; + } + + /* Un-mask MAX77836 Interrupt Source register */ + ret = max14577_read_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, &intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to read PMIC register\n"); + goto err; + } + + intsrc_mask &= ~(MAX77836_INTSRC_MASK_TOP_INT_MASK); + intsrc_mask &= ~(MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK); + ret = max14577_write_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to write PMIC register\n"); + goto err; + } + + ret = regmap_add_irq_chip(max14577->regmap_pmic, max14577->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77836_pmic_irq_chip, + &max14577->irq_data_pmic); + if (ret != 0) { + dev_err(max14577->dev, "Failed to request PMIC IRQ %d: %d\n", + max14577->irq, ret); + goto err; + } + + return 0; + +err: + i2c_unregister_device(max14577->i2c_pmic); + + return ret; +} + +/* + * Max77836 specific de-initialization code for driver remove. + */ +static void max77836_remove(struct max14577 *max14577) +{ + regmap_del_irq_chip(max14577->irq, max14577->irq_data_pmic); + i2c_unregister_device(max14577->i2c_pmic); +} + +static int max14577_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max14577 *max14577; + struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct device_node *np = i2c->dev.of_node; + int ret = 0; + const struct regmap_irq_chip *irq_chip; + const struct mfd_cell *mfd_devs; + unsigned int mfd_devs_size; + int irq_flags; + + if (np) { + pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + i2c->dev.platform_data = pdata; + } + + if (!pdata) { + dev_err(&i2c->dev, "No platform data found.\n"); + return -EINVAL; + } + + max14577 = devm_kzalloc(&i2c->dev, sizeof(*max14577), GFP_KERNEL); + if (!max14577) + return -ENOMEM; + + i2c_set_clientdata(i2c, max14577); + max14577->dev = &i2c->dev; + max14577->i2c = i2c; + max14577->irq = i2c->irq; + + max14577->regmap = devm_regmap_init_i2c(i2c, + &max14577_muic_regmap_config); + if (IS_ERR(max14577->regmap)) { + ret = PTR_ERR(max14577->regmap); + dev_err(max14577->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(max14577_dt_match, &i2c->dev); + if (of_id) + max14577->dev_type = + (enum maxim_device_type)of_id->data; + } else { + max14577->dev_type = id->driver_data; + } + + max14577_print_dev_type(max14577); + + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_chip = &max77836_muic_irq_chip; + mfd_devs = max77836_devs; + mfd_devs_size = ARRAY_SIZE(max77836_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + irq_chip = &max14577_irq_chip; + mfd_devs = max14577_devs; + mfd_devs_size = ARRAY_SIZE(max14577_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + break; + } + + ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, + irq_flags, 0, irq_chip, + &max14577->irq_data); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", + max14577->irq, ret); + return ret; + } + + /* Max77836 specific initialization code (additional regmap) */ + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) { + ret = max77836_init(max14577); + if (ret < 0) + goto err_max77836; + } + + ret = mfd_add_devices(max14577->dev, -1, mfd_devs, + mfd_devs_size, NULL, 0, NULL); + if (ret < 0) + goto err_mfd; + + device_init_wakeup(max14577->dev, 1); + + return 0; + +err_mfd: + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); +err_max77836: + regmap_del_irq_chip(max14577->irq, max14577->irq_data); + + return ret; +} + +static int max14577_i2c_remove(struct i2c_client *i2c) +{ + struct max14577 *max14577 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max14577->dev); + regmap_del_irq_chip(max14577->irq, max14577->irq_data); + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); + + return 0; +} + +static const struct i2c_device_id max14577_i2c_id[] = { + { "max14577", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); + +#ifdef CONFIG_PM_SLEEP +static int max14577_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max14577 *max14577 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + enable_irq_wake(max14577->irq); + /* + * MUIC IRQ must be disabled during suspend because if it happens + * while suspended it will be handled before resuming I2C. + * + * When device is woken up from suspend (e.g. by ADC change), + * an interrupt occurs before resuming I2C bus controller. + * Interrupt handler tries to read registers but this read + * will fail because I2C is still suspended. + */ + disable_irq(max14577->irq); + + return 0; +} + +static int max14577_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max14577 *max14577 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(max14577->irq); + enable_irq(max14577->irq); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume); + +static struct i2c_driver max14577_i2c_driver = { + .driver = { + .name = "max14577", + .owner = THIS_MODULE, + .pm = &max14577_pm, + .of_match_table = max14577_dt_match, + }, + .probe = max14577_i2c_probe, + .remove = max14577_i2c_remove, + .id_table = max14577_i2c_id, +}; + +static int __init max14577_i2c_init(void) +{ + BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); + BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); + + /* Valid charger current values must be provided for each chipset */ + BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); + + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX14577_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX77836_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + + return i2c_add_driver(&max14577_i2c_driver); +} +subsys_initcall(max14577_i2c_init); + +static void __exit max14577_i2c_exit(void) +{ + i2c_del_driver(&max14577_i2c_driver); +} +module_exit(max14577_i2c_exit); + +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); +MODULE_DESCRIPTION("Maxim 14577/77836 multi-function core driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max77686.c b/kernel/drivers/mfd/max77686.c new file mode 100644 index 000000000..760d08d79 --- /dev/null +++ b/kernel/drivers/mfd/max77686.c @@ -0,0 +1,418 @@ +/* + * max77686.c - mfd core driver for the Maxim 77686/802 + * + * Copyright (C) 2012 Samsung Electronics + * Chiwoong Byun <woong.byun@smasung.com> + * Jonghwa Lee <jonghwa3.lee@samsung.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8997.c + */ + +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77686.h> +#include <linux/mfd/max77686-private.h> +#include <linux/err.h> +#include <linux/of.h> + +#define I2C_ADDR_RTC (0x0C >> 1) + +static const struct mfd_cell max77686_devs[] = { + { .name = "max77686-pmic", }, + { .name = "max77686-rtc", }, + { .name = "max77686-clk", }, +}; + +static const struct mfd_cell max77802_devs[] = { + { .name = "max77802-pmic", }, + { .name = "max77802-clk", }, + { .name = "max77802-rtc", }, +}; + +static bool max77802_pmic_is_accessible_reg(struct device *dev, + unsigned int reg) +{ + return reg < MAX77802_REG_PMIC_END; +} + +static bool max77802_rtc_is_accessible_reg(struct device *dev, + unsigned int reg) +{ + return (reg >= MAX77802_RTC_INT && reg < MAX77802_RTC_END); +} + +static bool max77802_is_accessible_reg(struct device *dev, unsigned int reg) +{ + return (max77802_pmic_is_accessible_reg(dev, reg) || + max77802_rtc_is_accessible_reg(dev, reg)); +} + +static bool max77802_pmic_is_precious_reg(struct device *dev, unsigned int reg) +{ + return (reg == MAX77802_REG_INTSRC || reg == MAX77802_REG_INT1 || + reg == MAX77802_REG_INT2); +} + +static bool max77802_rtc_is_precious_reg(struct device *dev, unsigned int reg) +{ + return (reg == MAX77802_RTC_INT || + reg == MAX77802_RTC_UPDATE0 || + reg == MAX77802_RTC_UPDATE1); +} + +static bool max77802_is_precious_reg(struct device *dev, unsigned int reg) +{ + return (max77802_pmic_is_precious_reg(dev, reg) || + max77802_rtc_is_precious_reg(dev, reg)); +} + +static bool max77802_pmic_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (max77802_is_precious_reg(dev, reg) || + reg == MAX77802_REG_STATUS1 || reg == MAX77802_REG_STATUS2 || + reg == MAX77802_REG_PWRON); +} + +static bool max77802_rtc_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (max77802_rtc_is_precious_reg(dev, reg) || + reg == MAX77802_RTC_SEC || + reg == MAX77802_RTC_MIN || + reg == MAX77802_RTC_HOUR || + reg == MAX77802_RTC_WEEKDAY || + reg == MAX77802_RTC_MONTH || + reg == MAX77802_RTC_YEAR || + reg == MAX77802_RTC_DATE); +} + +static bool max77802_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (max77802_pmic_is_volatile_reg(dev, reg) || + max77802_rtc_is_volatile_reg(dev, reg)); +} + +static const struct regmap_config max77686_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config max77686_rtc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config max77802_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = max77802_is_accessible_reg, + .readable_reg = max77802_is_accessible_reg, + .precious_reg = max77802_is_precious_reg, + .volatile_reg = max77802_is_volatile_reg, + .name = "max77802-pmic", + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_irq max77686_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = MAX77686_INT1_PWRONF_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_PWRONR_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_JIGONBF_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_JIGONBR_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_ACOKBF_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_ACOKBR_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_ONKEY1S_MSK, }, + { .reg_offset = 0, .mask = MAX77686_INT1_MRSTB_MSK, }, + /* INT2 interrupts */ + { .reg_offset = 1, .mask = MAX77686_INT2_140C_MSK, }, + { .reg_offset = 1, .mask = MAX77686_INT2_120C_MSK, }, +}; + +static const struct regmap_irq_chip max77686_irq_chip = { + .name = "max77686-pmic", + .status_base = MAX77686_REG_INT1, + .mask_base = MAX77686_REG_INT1MSK, + .num_regs = 2, + .irqs = max77686_irqs, + .num_irqs = ARRAY_SIZE(max77686_irqs), +}; + +static const struct regmap_irq max77686_rtc_irqs[] = { + /* RTC interrupts */ + { .reg_offset = 0, .mask = MAX77686_RTCINT_RTC60S_MSK, }, + { .reg_offset = 0, .mask = MAX77686_RTCINT_RTCA1_MSK, }, + { .reg_offset = 0, .mask = MAX77686_RTCINT_RTCA2_MSK, }, + { .reg_offset = 0, .mask = MAX77686_RTCINT_SMPL_MSK, }, + { .reg_offset = 0, .mask = MAX77686_RTCINT_RTC1S_MSK, }, + { .reg_offset = 0, .mask = MAX77686_RTCINT_WTSR_MSK, }, +}; + +static const struct regmap_irq_chip max77686_rtc_irq_chip = { + .name = "max77686-rtc", + .status_base = MAX77686_RTC_INT, + .mask_base = MAX77686_RTC_INTM, + .num_regs = 1, + .irqs = max77686_rtc_irqs, + .num_irqs = ARRAY_SIZE(max77686_rtc_irqs), +}; + +static const struct regmap_irq_chip max77802_irq_chip = { + .name = "max77802-pmic", + .status_base = MAX77802_REG_INT1, + .mask_base = MAX77802_REG_INT1MSK, + .num_regs = 2, + .irqs = max77686_irqs, /* same masks as 77686 */ + .num_irqs = ARRAY_SIZE(max77686_irqs), +}; + +static const struct regmap_irq_chip max77802_rtc_irq_chip = { + .name = "max77802-rtc", + .status_base = MAX77802_RTC_INT, + .mask_base = MAX77802_RTC_INTM, + .num_regs = 1, + .irqs = max77686_rtc_irqs, /* same masks as 77686 */ + .num_irqs = ARRAY_SIZE(max77686_rtc_irqs), +}; + +static const struct of_device_id max77686_pmic_dt_match[] = { + { + .compatible = "maxim,max77686", + .data = (void *)TYPE_MAX77686, + }, + { + .compatible = "maxim,max77802", + .data = (void *)TYPE_MAX77802, + }, + { }, +}; + +static int max77686_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max77686_dev *max77686 = NULL; + const struct of_device_id *match; + unsigned int data; + int ret = 0; + const struct regmap_config *config; + const struct regmap_irq_chip *irq_chip; + const struct regmap_irq_chip *rtc_irq_chip; + struct regmap **rtc_regmap; + const struct mfd_cell *cells; + int n_devs; + + max77686 = devm_kzalloc(&i2c->dev, + sizeof(struct max77686_dev), GFP_KERNEL); + if (!max77686) + return -ENOMEM; + + if (i2c->dev.of_node) { + match = of_match_node(max77686_pmic_dt_match, i2c->dev.of_node); + if (!match) + return -EINVAL; + + max77686->type = (unsigned long)match->data; + } else + max77686->type = id->driver_data; + + i2c_set_clientdata(i2c, max77686); + max77686->dev = &i2c->dev; + max77686->i2c = i2c; + + max77686->irq = i2c->irq; + + if (max77686->type == TYPE_MAX77686) { + config = &max77686_regmap_config; + irq_chip = &max77686_irq_chip; + rtc_irq_chip = &max77686_rtc_irq_chip; + rtc_regmap = &max77686->rtc_regmap; + cells = max77686_devs; + n_devs = ARRAY_SIZE(max77686_devs); + } else { + config = &max77802_regmap_config; + irq_chip = &max77802_irq_chip; + rtc_irq_chip = &max77802_rtc_irq_chip; + rtc_regmap = &max77686->regmap; + cells = max77802_devs; + n_devs = ARRAY_SIZE(max77802_devs); + } + + max77686->regmap = devm_regmap_init_i2c(i2c, config); + if (IS_ERR(max77686->regmap)) { + ret = PTR_ERR(max77686->regmap); + dev_err(max77686->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = regmap_read(max77686->regmap, MAX77686_REG_DEVICE_ID, &data); + if (ret < 0) { + dev_err(max77686->dev, + "device not found on this channel (this is not an error)\n"); + return -ENODEV; + } + + if (max77686->type == TYPE_MAX77686) { + max77686->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC); + if (!max77686->rtc) { + dev_err(max77686->dev, + "Failed to allocate I2C device for RTC\n"); + return -ENODEV; + } + i2c_set_clientdata(max77686->rtc, max77686); + + max77686->rtc_regmap = + devm_regmap_init_i2c(max77686->rtc, + &max77686_rtc_regmap_config); + if (IS_ERR(max77686->rtc_regmap)) { + ret = PTR_ERR(max77686->rtc_regmap); + dev_err(max77686->dev, + "failed to allocate RTC regmap: %d\n", + ret); + goto err_unregister_i2c; + } + } + + ret = regmap_add_irq_chip(max77686->regmap, max77686->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | + IRQF_SHARED, 0, irq_chip, + &max77686->irq_data); + if (ret) { + dev_err(&i2c->dev, "failed to add PMIC irq chip: %d\n", ret); + goto err_unregister_i2c; + } + + ret = regmap_add_irq_chip(*rtc_regmap, max77686->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | + IRQF_SHARED, 0, rtc_irq_chip, + &max77686->rtc_irq_data); + if (ret) { + dev_err(&i2c->dev, "failed to add RTC irq chip: %d\n", ret); + goto err_del_irqc; + } + + ret = mfd_add_devices(max77686->dev, -1, cells, n_devs, NULL, 0, NULL); + if (ret < 0) { + dev_err(&i2c->dev, "failed to add MFD devices: %d\n", ret); + goto err_del_rtc_irqc; + } + + return 0; + +err_del_rtc_irqc: + regmap_del_irq_chip(max77686->irq, max77686->rtc_irq_data); +err_del_irqc: + regmap_del_irq_chip(max77686->irq, max77686->irq_data); +err_unregister_i2c: + if (max77686->type == TYPE_MAX77686) + i2c_unregister_device(max77686->rtc); + + return ret; +} + +static int max77686_i2c_remove(struct i2c_client *i2c) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max77686->dev); + + regmap_del_irq_chip(max77686->irq, max77686->rtc_irq_data); + regmap_del_irq_chip(max77686->irq, max77686->irq_data); + + if (max77686->type == TYPE_MAX77686) + i2c_unregister_device(max77686->rtc); + + return 0; +} + +static const struct i2c_device_id max77686_i2c_id[] = { + { "max77686", TYPE_MAX77686 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77686_i2c_id); + +#ifdef CONFIG_PM_SLEEP +static int max77686_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + enable_irq_wake(max77686->irq); + + /* + * IRQ must be disabled during suspend because if it happens + * while suspended it will be handled before resuming I2C. + * + * When device is woken up from suspend (e.g. by RTC wake alarm), + * an interrupt occurs before resuming I2C bus controller. + * Interrupt handler tries to read registers but this read + * will fail because I2C is still suspended. + */ + disable_irq(max77686->irq); + + return 0; +} + +static int max77686_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(max77686->irq); + + enable_irq(max77686->irq); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max77686_pm, max77686_suspend, max77686_resume); + +static struct i2c_driver max77686_i2c_driver = { + .driver = { + .name = "max77686", + .owner = THIS_MODULE, + .pm = &max77686_pm, + .of_match_table = of_match_ptr(max77686_pmic_dt_match), + }, + .probe = max77686_i2c_probe, + .remove = max77686_i2c_remove, + .id_table = max77686_i2c_id, +}; + +static int __init max77686_i2c_init(void) +{ + return i2c_add_driver(&max77686_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77686_i2c_init); + +static void __exit max77686_i2c_exit(void) +{ + i2c_del_driver(&max77686_i2c_driver); +} +module_exit(max77686_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 77686/802 multi-function core driver"); +MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max77693.c b/kernel/drivers/mfd/max77693.c new file mode 100644 index 000000000..cb14afa97 --- /dev/null +++ b/kernel/drivers/mfd/max77693.c @@ -0,0 +1,399 @@ +/* + * max77693.c - mfd core driver for the MAX 77693 + * + * Copyright (C) 2012 Samsung Electronics + * SangYoung Son <hello.son@smasung.com> + * + * This program is not provided / owned by Maxim Integrated Products. + * + * 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8997.c + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> +#include <linux/regulator/machine.h> +#include <linux/regmap.h> + +#define I2C_ADDR_PMIC (0xCC >> 1) /* Charger, Flash LED */ +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_HAPTIC (0x90 >> 1) + +static const struct mfd_cell max77693_devs[] = { + { .name = "max77693-pmic", }, + { + .name = "max77693-charger", + .of_compatible = "maxim,max77693-charger", + }, + { .name = "max77693-muic", }, + { + .name = "max77693-haptic", + .of_compatible = "maxim,max77693-haptic", + }, + { + .name = "max77693-led", + .of_compatible = "maxim,max77693-led", + }, +}; + +static const struct regmap_config max77693_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77693_PMIC_REG_END, +}; + +static const struct regmap_irq max77693_led_irqs[] = { + { .mask = LED_IRQ_FLED2_OPEN, }, + { .mask = LED_IRQ_FLED2_SHORT, }, + { .mask = LED_IRQ_FLED1_OPEN, }, + { .mask = LED_IRQ_FLED1_SHORT, }, + { .mask = LED_IRQ_MAX_FLASH, }, +}; + +static const struct regmap_irq_chip max77693_led_irq_chip = { + .name = "max77693-led", + .status_base = MAX77693_LED_REG_FLASH_INT, + .mask_base = MAX77693_LED_REG_FLASH_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77693_led_irqs, + .num_irqs = ARRAY_SIZE(max77693_led_irqs), +}; + +static const struct regmap_irq max77693_topsys_irqs[] = { + { .mask = TOPSYS_IRQ_T120C_INT, }, + { .mask = TOPSYS_IRQ_T140C_INT, }, + { .mask = TOPSYS_IRQ_LOWSYS_INT, }, +}; + +static const struct regmap_irq_chip max77693_topsys_irq_chip = { + .name = "max77693-topsys", + .status_base = MAX77693_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77693_PMIC_REG_TOPSYS_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77693_topsys_irqs, + .num_irqs = ARRAY_SIZE(max77693_topsys_irqs), +}; + +static const struct regmap_irq max77693_charger_irqs[] = { + { .mask = CHG_IRQ_BYP_I, }, + { .mask = CHG_IRQ_THM_I, }, + { .mask = CHG_IRQ_BAT_I, }, + { .mask = CHG_IRQ_CHG_I, }, + { .mask = CHG_IRQ_CHGIN_I, }, +}; + +static const struct regmap_irq_chip max77693_charger_irq_chip = { + .name = "max77693-charger", + .status_base = MAX77693_CHG_REG_CHG_INT, + .mask_base = MAX77693_CHG_REG_CHG_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77693_charger_irqs, + .num_irqs = ARRAY_SIZE(max77693_charger_irqs), +}; + +static const struct regmap_config max77693_regmap_muic_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77693_MUIC_REG_END, +}; + +static const struct regmap_irq max77693_muic_irqs[] = { + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC, }, + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC_LOW, }, + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC_ERR, }, + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC1K, }, + + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_CHGTYP, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_CHGDETREUN, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_DCDTMR, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_DXOVP, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_VBVOLT, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_VIDRM, }, + + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_EOC, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_CGMBC, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_OVP, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_MBCCHG_ERR, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_CHG_ENABLED, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_BAT_DET, }, +}; + +static const struct regmap_irq_chip max77693_muic_irq_chip = { + .name = "max77693-muic", + .status_base = MAX77693_MUIC_REG_INT1, + .mask_base = MAX77693_MUIC_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77693_muic_irqs, + .num_irqs = ARRAY_SIZE(max77693_muic_irqs), +}; + +static const struct regmap_config max77693_regmap_haptic_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77693_HAPTIC_REG_END, +}; + +static int max77693_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max77693_dev *max77693; + unsigned int reg_data; + int ret = 0; + + max77693 = devm_kzalloc(&i2c->dev, + sizeof(struct max77693_dev), GFP_KERNEL); + if (max77693 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max77693); + max77693->dev = &i2c->dev; + max77693->i2c = i2c; + max77693->irq = i2c->irq; + max77693->type = id->driver_data; + + max77693->regmap = devm_regmap_init_i2c(i2c, &max77693_regmap_config); + if (IS_ERR(max77693->regmap)) { + ret = PTR_ERR(max77693->regmap); + dev_err(max77693->dev, "failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = regmap_read(max77693->regmap, MAX77693_PMIC_REG_PMIC_ID2, + ®_data); + if (ret < 0) { + dev_err(max77693->dev, "device not found on this channel\n"); + return ret; + } else + dev_info(max77693->dev, "device ID: 0x%x\n", reg_data); + + max77693->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); + if (!max77693->muic) { + dev_err(max77693->dev, "Failed to allocate I2C device for MUIC\n"); + return -ENODEV; + } + i2c_set_clientdata(max77693->muic, max77693); + + max77693->haptic = i2c_new_dummy(i2c->adapter, I2C_ADDR_HAPTIC); + if (!max77693->haptic) { + dev_err(max77693->dev, "Failed to allocate I2C device for Haptic\n"); + ret = -ENODEV; + goto err_i2c_haptic; + } + i2c_set_clientdata(max77693->haptic, max77693); + + max77693->regmap_haptic = devm_regmap_init_i2c(max77693->haptic, + &max77693_regmap_haptic_config); + if (IS_ERR(max77693->regmap_haptic)) { + ret = PTR_ERR(max77693->regmap_haptic); + dev_err(max77693->dev, + "failed to initialize haptic register map: %d\n", ret); + goto err_regmap; + } + + /* + * Initialize register map for MUIC device because use regmap-muic + * instance of MUIC device when irq of max77693 is initialized + * before call max77693-muic probe() function. + */ + max77693->regmap_muic = devm_regmap_init_i2c(max77693->muic, + &max77693_regmap_muic_config); + if (IS_ERR(max77693->regmap_muic)) { + ret = PTR_ERR(max77693->regmap_muic); + dev_err(max77693->dev, + "failed to allocate register map: %d\n", ret); + goto err_regmap; + } + + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_led_irq_chip, + &max77693->irq_data_led); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_regmap; + } + + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_topsys_irq_chip, + &max77693->irq_data_topsys); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_irq_topsys; + } + + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_charger_irq_chip, + &max77693->irq_data_charger); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_irq_charger; + } + + ret = regmap_add_irq_chip(max77693->regmap_muic, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_muic_irq_chip, + &max77693->irq_data_muic); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_irq_muic; + } + + /* Unmask interrupts from all blocks in interrupt source register */ + ret = regmap_update_bits(max77693->regmap, + MAX77693_PMIC_REG_INTSRC_MASK, + SRC_IRQ_ALL, (unsigned int)~SRC_IRQ_ALL); + if (ret < 0) { + dev_err(max77693->dev, + "Could not unmask interrupts in INTSRC: %d\n", + ret); + goto err_intsrc; + } + + pm_runtime_set_active(max77693->dev); + + ret = mfd_add_devices(max77693->dev, -1, max77693_devs, + ARRAY_SIZE(max77693_devs), NULL, 0, NULL); + if (ret < 0) + goto err_mfd; + + return ret; + +err_mfd: + mfd_remove_devices(max77693->dev); +err_intsrc: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_muic); +err_irq_muic: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_charger); +err_irq_charger: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_topsys); +err_irq_topsys: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_led); +err_regmap: + i2c_unregister_device(max77693->haptic); +err_i2c_haptic: + i2c_unregister_device(max77693->muic); + return ret; +} + +static int max77693_i2c_remove(struct i2c_client *i2c) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max77693->dev); + + regmap_del_irq_chip(max77693->irq, max77693->irq_data_muic); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_charger); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_topsys); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_led); + + i2c_unregister_device(max77693->muic); + i2c_unregister_device(max77693->haptic); + + return 0; +} + +static const struct i2c_device_id max77693_i2c_id[] = { + { "max77693", TYPE_MAX77693 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77693_i2c_id); + +static int max77693_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) { + enable_irq_wake(max77693->irq); + disable_irq(max77693->irq); + } + + return 0; +} + +static int max77693_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) { + disable_irq_wake(max77693->irq); + enable_irq(max77693->irq); + } + + return 0; +} + +static const struct dev_pm_ops max77693_pm = { + .suspend = max77693_suspend, + .resume = max77693_resume, +}; + +#ifdef CONFIG_OF +static const struct of_device_id max77693_dt_match[] = { + { .compatible = "maxim,max77693" }, + {}, +}; +#endif + +static struct i2c_driver max77693_i2c_driver = { + .driver = { + .name = "max77693", + .owner = THIS_MODULE, + .pm = &max77693_pm, + .of_match_table = of_match_ptr(max77693_dt_match), + }, + .probe = max77693_i2c_probe, + .remove = max77693_i2c_remove, + .id_table = max77693_i2c_id, +}; + +static int __init max77693_i2c_init(void) +{ + return i2c_add_driver(&max77693_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77693_i2c_init); + +static void __exit max77693_i2c_exit(void) +{ + i2c_del_driver(&max77693_i2c_driver); +} +module_exit(max77693_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 77693 multi-function core driver"); +MODULE_AUTHOR("SangYoung, Son <hello.son@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max77843.c b/kernel/drivers/mfd/max77843.c new file mode 100644 index 000000000..a354ac677 --- /dev/null +++ b/kernel/drivers/mfd/max77843.c @@ -0,0 +1,243 @@ +/* + * MFD core driver for the Maxim MAX77843 + * + * Copyright (C) 2015 Samsung Electronics + * Author: Jaewon Kim <jaewon02.kim@samsung.com> + * Author: Beomho Seo <beomho.seo@samsung.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/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77843-private.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +static const struct mfd_cell max77843_devs[] = { + { + .name = "max77843-muic", + .of_compatible = "maxim,max77843-muic", + }, { + .name = "max77843-regulator", + .of_compatible = "maxim,max77843-regulator", + }, { + .name = "max77843-charger", + .of_compatible = "maxim,max77843-charger" + }, { + .name = "max77843-fuelgauge", + .of_compatible = "maxim,max77843-fuelgauge", + }, { + .name = "max77843-haptic", + .of_compatible = "maxim,max77843-haptic", + }, +}; + +static const struct regmap_config max77843_charger_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77843_CHG_REG_END, +}; + +static const struct regmap_config max77843_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77843_SYS_REG_END, +}; + +static const struct regmap_irq max77843_irqs[] = { + /* TOPSYS interrupts */ + { .reg_offset = 0, .mask = MAX77843_SYS_IRQ_SYSUVLO_INT, }, + { .reg_offset = 0, .mask = MAX77843_SYS_IRQ_SYSOVLO_INT, }, + { .reg_offset = 0, .mask = MAX77843_SYS_IRQ_TSHDN_INT, }, + { .reg_offset = 0, .mask = MAX77843_SYS_IRQ_TM_INT, }, +}; + +static const struct regmap_irq_chip max77843_irq_chip = { + .name = "max77843", + .status_base = MAX77843_SYS_REG_SYSINTSRC, + .mask_base = MAX77843_SYS_REG_SYSINTMASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77843_irqs, + .num_irqs = ARRAY_SIZE(max77843_irqs), +}; + +/* Charger and Charger regulator use same regmap. */ +static int max77843_chg_init(struct max77843 *max77843) +{ + int ret; + + max77843->i2c_chg = i2c_new_dummy(max77843->i2c->adapter, I2C_ADDR_CHG); + if (!max77843->i2c_chg) { + dev_err(&max77843->i2c->dev, + "Cannot allocate I2C device for Charger\n"); + return PTR_ERR(max77843->i2c_chg); + } + i2c_set_clientdata(max77843->i2c_chg, max77843); + + max77843->regmap_chg = devm_regmap_init_i2c(max77843->i2c_chg, + &max77843_charger_regmap_config); + if (IS_ERR(max77843->regmap_chg)) { + ret = PTR_ERR(max77843->regmap_chg); + goto err_chg_i2c; + } + + return 0; + +err_chg_i2c: + i2c_unregister_device(max77843->i2c_chg); + + return ret; +} + +static int max77843_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max77843 *max77843; + unsigned int reg_data; + int ret; + + max77843 = devm_kzalloc(&i2c->dev, sizeof(*max77843), GFP_KERNEL); + if (!max77843) + return -ENOMEM; + + i2c_set_clientdata(i2c, max77843); + max77843->dev = &i2c->dev; + max77843->i2c = i2c; + max77843->irq = i2c->irq; + + max77843->regmap = devm_regmap_init_i2c(i2c, + &max77843_regmap_config); + if (IS_ERR(max77843->regmap)) { + dev_err(&i2c->dev, "Failed to allocate topsys register map\n"); + return PTR_ERR(max77843->regmap); + } + + ret = regmap_add_irq_chip(max77843->regmap, max77843->irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77843_irq_chip, &max77843->irq_data); + if (ret) { + dev_err(&i2c->dev, "Failed to add TOPSYS IRQ chip\n"); + return ret; + } + + ret = regmap_read(max77843->regmap, + MAX77843_SYS_REG_PMICID, ®_data); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read PMIC ID\n"); + goto err_pmic_id; + } + dev_info(&i2c->dev, "device ID: 0x%x\n", reg_data); + + ret = max77843_chg_init(max77843); + if (ret) { + dev_err(&i2c->dev, "Failed to init Charger\n"); + goto err_pmic_id; + } + + ret = regmap_update_bits(max77843->regmap, + MAX77843_SYS_REG_INTSRCMASK, + MAX77843_INTSRC_MASK_MASK, + (unsigned int)~MAX77843_INTSRC_MASK_MASK); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to unmask interrupt source\n"); + goto err_pmic_id; + } + + ret = mfd_add_devices(max77843->dev, -1, max77843_devs, + ARRAY_SIZE(max77843_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to add mfd device\n"); + goto err_pmic_id; + } + + device_init_wakeup(max77843->dev, true); + + return 0; + +err_pmic_id: + regmap_del_irq_chip(max77843->irq, max77843->irq_data); + + return ret; +} + +static int max77843_remove(struct i2c_client *i2c) +{ + struct max77843 *max77843 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max77843->dev); + + regmap_del_irq_chip(max77843->irq, max77843->irq_data); + + i2c_unregister_device(max77843->i2c_chg); + + return 0; +} + +static const struct of_device_id max77843_dt_match[] = { + { .compatible = "maxim,max77843", }, + { }, +}; + +static const struct i2c_device_id max77843_id[] = { + { "max77843", }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max77843_id); + +static int __maybe_unused max77843_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77843 *max77843 = i2c_get_clientdata(i2c); + + disable_irq(max77843->irq); + if (device_may_wakeup(dev)) + enable_irq_wake(max77843->irq); + + return 0; +} + +static int __maybe_unused max77843_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77843 *max77843 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(max77843->irq); + enable_irq(max77843->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(max77843_pm, max77843_suspend, max77843_resume); + +static struct i2c_driver max77843_i2c_driver = { + .driver = { + .name = "max77843", + .pm = &max77843_pm, + .of_match_table = max77843_dt_match, + }, + .probe = max77843_probe, + .remove = max77843_remove, + .id_table = max77843_id, +}; + +static int __init max77843_i2c_init(void) +{ + return i2c_add_driver(&max77843_i2c_driver); +} +subsys_initcall(max77843_i2c_init); + +static void __exit max77843_i2c_exit(void) +{ + i2c_del_driver(&max77843_i2c_driver); +} +module_exit(max77843_i2c_exit); diff --git a/kernel/drivers/mfd/max8907.c b/kernel/drivers/mfd/max8907.c new file mode 100644 index 000000000..232749c88 --- /dev/null +++ b/kernel/drivers/mfd/max8907.c @@ -0,0 +1,352 @@ +/* + * max8907.c - mfd driver for MAX8907 + * + * Copyright (C) 2010 Gyungoh Yoo <jack.yoo@maxim-ic.com> + * Copyright (C) 2010-2012, NVIDIA CORPORATION. All rights reserved. + * + * 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. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max8907.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +static const struct mfd_cell max8907_cells[] = { + { .name = "max8907-regulator", }, + { .name = "max8907-rtc", }, +}; + +static bool max8907_gen_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX8907_REG_ON_OFF_IRQ1: + case MAX8907_REG_ON_OFF_STAT: + case MAX8907_REG_ON_OFF_IRQ2: + case MAX8907_REG_CHG_IRQ1: + case MAX8907_REG_CHG_IRQ2: + case MAX8907_REG_CHG_STAT: + return true; + default: + return false; + } +} + +static bool max8907_gen_is_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX8907_REG_ON_OFF_IRQ1: + case MAX8907_REG_ON_OFF_IRQ2: + case MAX8907_REG_CHG_IRQ1: + case MAX8907_REG_CHG_IRQ2: + return true; + default: + return false; + } +} + +static bool max8907_gen_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return !max8907_gen_is_volatile_reg(dev, reg); +} + +static const struct regmap_config max8907_regmap_gen_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max8907_gen_is_volatile_reg, + .precious_reg = max8907_gen_is_precious_reg, + .writeable_reg = max8907_gen_is_writeable_reg, + .max_register = MAX8907_REG_LDO20VOUT, + .cache_type = REGCACHE_RBTREE, +}; + +static bool max8907_rtc_is_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg <= MAX8907_REG_RTC_YEAR2) + return true; + + switch (reg) { + case MAX8907_REG_RTC_STATUS: + case MAX8907_REG_RTC_IRQ: + return true; + default: + return false; + } +} + +static bool max8907_rtc_is_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX8907_REG_RTC_IRQ: + return true; + default: + return false; + } +} + +static bool max8907_rtc_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX8907_REG_RTC_STATUS: + case MAX8907_REG_RTC_IRQ: + return false; + default: + return true; + } +} + +static const struct regmap_config max8907_regmap_rtc_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max8907_rtc_is_volatile_reg, + .precious_reg = max8907_rtc_is_precious_reg, + .writeable_reg = max8907_rtc_is_writeable_reg, + .max_register = MAX8907_REG_MPL_CNTL, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_irq max8907_chg_irqs[] = { + { .reg_offset = 0, .mask = 1 << 0, }, + { .reg_offset = 0, .mask = 1 << 1, }, + { .reg_offset = 0, .mask = 1 << 2, }, + { .reg_offset = 1, .mask = 1 << 0, }, + { .reg_offset = 1, .mask = 1 << 1, }, + { .reg_offset = 1, .mask = 1 << 2, }, + { .reg_offset = 1, .mask = 1 << 3, }, + { .reg_offset = 1, .mask = 1 << 4, }, + { .reg_offset = 1, .mask = 1 << 5, }, + { .reg_offset = 1, .mask = 1 << 6, }, + { .reg_offset = 1, .mask = 1 << 7, }, +}; + +static const struct regmap_irq_chip max8907_chg_irq_chip = { + .name = "max8907 chg", + .status_base = MAX8907_REG_CHG_IRQ1, + .mask_base = MAX8907_REG_CHG_IRQ1_MASK, + .wake_base = MAX8907_REG_CHG_IRQ1_MASK, + .irq_reg_stride = MAX8907_REG_CHG_IRQ2 - MAX8907_REG_CHG_IRQ1, + .num_regs = 2, + .irqs = max8907_chg_irqs, + .num_irqs = ARRAY_SIZE(max8907_chg_irqs), +}; + +static const struct regmap_irq max8907_on_off_irqs[] = { + { .reg_offset = 0, .mask = 1 << 0, }, + { .reg_offset = 0, .mask = 1 << 1, }, + { .reg_offset = 0, .mask = 1 << 2, }, + { .reg_offset = 0, .mask = 1 << 3, }, + { .reg_offset = 0, .mask = 1 << 4, }, + { .reg_offset = 0, .mask = 1 << 5, }, + { .reg_offset = 0, .mask = 1 << 6, }, + { .reg_offset = 0, .mask = 1 << 7, }, + { .reg_offset = 1, .mask = 1 << 0, }, + { .reg_offset = 1, .mask = 1 << 1, }, +}; + +static const struct regmap_irq_chip max8907_on_off_irq_chip = { + .name = "max8907 on_off", + .status_base = MAX8907_REG_ON_OFF_IRQ1, + .mask_base = MAX8907_REG_ON_OFF_IRQ1_MASK, + .irq_reg_stride = MAX8907_REG_ON_OFF_IRQ2 - MAX8907_REG_ON_OFF_IRQ1, + .num_regs = 2, + .irqs = max8907_on_off_irqs, + .num_irqs = ARRAY_SIZE(max8907_on_off_irqs), +}; + +static const struct regmap_irq max8907_rtc_irqs[] = { + { .reg_offset = 0, .mask = 1 << 2, }, + { .reg_offset = 0, .mask = 1 << 3, }, +}; + +static const struct regmap_irq_chip max8907_rtc_irq_chip = { + .name = "max8907 rtc", + .status_base = MAX8907_REG_RTC_IRQ, + .mask_base = MAX8907_REG_RTC_IRQ_MASK, + .num_regs = 1, + .irqs = max8907_rtc_irqs, + .num_irqs = ARRAY_SIZE(max8907_rtc_irqs), +}; + +static struct max8907 *max8907_pm_off; +static void max8907_power_off(void) +{ + regmap_update_bits(max8907_pm_off->regmap_gen, MAX8907_REG_RESET_CNFG, + MAX8907_MASK_POWER_OFF, MAX8907_MASK_POWER_OFF); +} + +static int max8907_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max8907 *max8907; + int ret; + struct max8907_platform_data *pdata = dev_get_platdata(&i2c->dev); + bool pm_off = false; + + if (pdata) + pm_off = pdata->pm_off; + else if (i2c->dev.of_node) + pm_off = of_property_read_bool(i2c->dev.of_node, + "maxim,system-power-controller"); + + max8907 = devm_kzalloc(&i2c->dev, sizeof(struct max8907), GFP_KERNEL); + if (!max8907) { + ret = -ENOMEM; + goto err_alloc_drvdata; + } + + max8907->dev = &i2c->dev; + dev_set_drvdata(max8907->dev, max8907); + + max8907->i2c_gen = i2c; + i2c_set_clientdata(i2c, max8907); + max8907->regmap_gen = devm_regmap_init_i2c(i2c, + &max8907_regmap_gen_config); + if (IS_ERR(max8907->regmap_gen)) { + ret = PTR_ERR(max8907->regmap_gen); + dev_err(&i2c->dev, "gen regmap init failed: %d\n", ret); + goto err_regmap_gen; + } + + max8907->i2c_rtc = i2c_new_dummy(i2c->adapter, MAX8907_RTC_I2C_ADDR); + if (!max8907->i2c_rtc) { + ret = -ENOMEM; + goto err_dummy_rtc; + } + i2c_set_clientdata(max8907->i2c_rtc, max8907); + max8907->regmap_rtc = devm_regmap_init_i2c(max8907->i2c_rtc, + &max8907_regmap_rtc_config); + if (IS_ERR(max8907->regmap_rtc)) { + ret = PTR_ERR(max8907->regmap_rtc); + dev_err(&i2c->dev, "rtc regmap init failed: %d\n", ret); + goto err_regmap_rtc; + } + + irq_set_status_flags(max8907->i2c_gen->irq, IRQ_NOAUTOEN); + + ret = regmap_add_irq_chip(max8907->regmap_gen, max8907->i2c_gen->irq, + IRQF_ONESHOT | IRQF_SHARED, -1, + &max8907_chg_irq_chip, + &max8907->irqc_chg); + if (ret != 0) { + dev_err(&i2c->dev, "failed to add chg irq chip: %d\n", ret); + goto err_irqc_chg; + } + ret = regmap_add_irq_chip(max8907->regmap_gen, max8907->i2c_gen->irq, + IRQF_ONESHOT | IRQF_SHARED, -1, + &max8907_on_off_irq_chip, + &max8907->irqc_on_off); + if (ret != 0) { + dev_err(&i2c->dev, "failed to add on off irq chip: %d\n", ret); + goto err_irqc_on_off; + } + ret = regmap_add_irq_chip(max8907->regmap_rtc, max8907->i2c_gen->irq, + IRQF_ONESHOT | IRQF_SHARED, -1, + &max8907_rtc_irq_chip, + &max8907->irqc_rtc); + if (ret != 0) { + dev_err(&i2c->dev, "failed to add rtc irq chip: %d\n", ret); + goto err_irqc_rtc; + } + + enable_irq(max8907->i2c_gen->irq); + + ret = mfd_add_devices(max8907->dev, -1, max8907_cells, + ARRAY_SIZE(max8907_cells), NULL, 0, NULL); + if (ret != 0) { + dev_err(&i2c->dev, "failed to add MFD devices %d\n", ret); + goto err_add_devices; + } + + if (pm_off && !pm_power_off) { + max8907_pm_off = max8907; + pm_power_off = max8907_power_off; + } + + return 0; + +err_add_devices: + regmap_del_irq_chip(max8907->i2c_gen->irq, max8907->irqc_rtc); +err_irqc_rtc: + regmap_del_irq_chip(max8907->i2c_gen->irq, max8907->irqc_on_off); +err_irqc_on_off: + regmap_del_irq_chip(max8907->i2c_gen->irq, max8907->irqc_chg); +err_irqc_chg: +err_regmap_rtc: + i2c_unregister_device(max8907->i2c_rtc); +err_dummy_rtc: +err_regmap_gen: +err_alloc_drvdata: + return ret; +} + +static int max8907_i2c_remove(struct i2c_client *i2c) +{ + struct max8907 *max8907 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max8907->dev); + + regmap_del_irq_chip(max8907->i2c_gen->irq, max8907->irqc_rtc); + regmap_del_irq_chip(max8907->i2c_gen->irq, max8907->irqc_on_off); + regmap_del_irq_chip(max8907->i2c_gen->irq, max8907->irqc_chg); + + i2c_unregister_device(max8907->i2c_rtc); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id max8907_of_match[] = { + { .compatible = "maxim,max8907" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max8907_of_match); +#endif + +static const struct i2c_device_id max8907_i2c_id[] = { + {"max8907", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max8907_i2c_id); + +static struct i2c_driver max8907_i2c_driver = { + .driver = { + .name = "max8907", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max8907_of_match), + }, + .probe = max8907_i2c_probe, + .remove = max8907_i2c_remove, + .id_table = max8907_i2c_id, +}; + +static int __init max8907_i2c_init(void) +{ + int ret = -ENODEV; + + ret = i2c_add_driver(&max8907_i2c_driver); + if (ret != 0) + pr_err("Failed to register I2C driver: %d\n", ret); + + return ret; +} +subsys_initcall(max8907_i2c_init); + +static void __exit max8907_i2c_exit(void) +{ + i2c_del_driver(&max8907_i2c_driver); +} +module_exit(max8907_i2c_exit); + +MODULE_DESCRIPTION("MAX8907 multi-function core driver"); +MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@maxim-ic.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/max8925-core.c b/kernel/drivers/mfd/max8925-core.c new file mode 100644 index 000000000..97a787ab3 --- /dev/null +++ b/kernel/drivers/mfd/max8925-core.c @@ -0,0 +1,929 @@ +/* + * Base driver for Maxim MAX8925 + * + * Copyright (C) 2009-2010 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/platform_device.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max8925.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +static struct resource bk_resources[] = { + { 0x84, 0x84, "mode control", IORESOURCE_REG, }, + { 0x85, 0x85, "control", IORESOURCE_REG, }, +}; + +static struct mfd_cell bk_devs[] = { + { + .name = "max8925-backlight", + .num_resources = ARRAY_SIZE(bk_resources), + .resources = &bk_resources[0], + .id = -1, + }, +}; + +static struct resource touch_resources[] = { + { + .name = "max8925-tsc", + .start = MAX8925_TSC_IRQ, + .end = MAX8925_ADC_RES_END, + .flags = IORESOURCE_REG, + }, +}; + +static const struct mfd_cell touch_devs[] = { + { + .name = "max8925-touch", + .num_resources = 1, + .resources = &touch_resources[0], + .id = -1, + }, +}; + +static struct resource power_supply_resources[] = { + { + .name = "max8925-power", + .start = MAX8925_CHG_IRQ1, + .end = MAX8925_CHG_IRQ1_MASK, + .flags = IORESOURCE_REG, + }, +}; + +static const struct mfd_cell power_devs[] = { + { + .name = "max8925-power", + .num_resources = 1, + .resources = &power_supply_resources[0], + .id = -1, + }, +}; + +static struct resource rtc_resources[] = { + { + .name = "max8925-rtc", + .start = MAX8925_IRQ_RTC_ALARM0, + .end = MAX8925_IRQ_RTC_ALARM0, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell rtc_devs[] = { + { + .name = "max8925-rtc", + .num_resources = 1, + .resources = &rtc_resources[0], + .id = -1, + }, +}; + +static struct resource onkey_resources[] = { + { + .name = "max8925-onkey", + .start = MAX8925_IRQ_GPM_SW_R, + .end = MAX8925_IRQ_GPM_SW_R, + .flags = IORESOURCE_IRQ, + }, { + .name = "max8925-onkey", + .start = MAX8925_IRQ_GPM_SW_F, + .end = MAX8925_IRQ_GPM_SW_F, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell onkey_devs[] = { + { + .name = "max8925-onkey", + .num_resources = 2, + .resources = &onkey_resources[0], + .id = -1, + }, +}; + +static struct resource sd1_resources[] = { + {0x06, 0x06, "sdv", IORESOURCE_REG, }, +}; + +static struct resource sd2_resources[] = { + {0x09, 0x09, "sdv", IORESOURCE_REG, }, +}; + +static struct resource sd3_resources[] = { + {0x0c, 0x0c, "sdv", IORESOURCE_REG, }, +}; + +static struct resource ldo1_resources[] = { + {0x1a, 0x1a, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo2_resources[] = { + {0x1e, 0x1e, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo3_resources[] = { + {0x22, 0x22, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo4_resources[] = { + {0x26, 0x26, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo5_resources[] = { + {0x2a, 0x2a, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo6_resources[] = { + {0x2e, 0x2e, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo7_resources[] = { + {0x32, 0x32, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo8_resources[] = { + {0x36, 0x36, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo9_resources[] = { + {0x3a, 0x3a, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo10_resources[] = { + {0x3e, 0x3e, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo11_resources[] = { + {0x42, 0x42, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo12_resources[] = { + {0x46, 0x46, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo13_resources[] = { + {0x4a, 0x4a, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo14_resources[] = { + {0x4e, 0x4e, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo15_resources[] = { + {0x52, 0x52, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo16_resources[] = { + {0x12, 0x12, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo17_resources[] = { + {0x16, 0x16, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo18_resources[] = { + {0x74, 0x74, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo19_resources[] = { + {0x5e, 0x5e, "ldov", IORESOURCE_REG, }, +}; + +static struct resource ldo20_resources[] = { + {0x9e, 0x9e, "ldov", IORESOURCE_REG, }, +}; + +static struct mfd_cell reg_devs[] = { + { + .name = "max8925-regulator", + .id = 0, + .num_resources = ARRAY_SIZE(sd1_resources), + .resources = sd1_resources, + }, { + .name = "max8925-regulator", + .id = 1, + .num_resources = ARRAY_SIZE(sd2_resources), + .resources = sd2_resources, + }, { + .name = "max8925-regulator", + .id = 2, + .num_resources = ARRAY_SIZE(sd3_resources), + .resources = sd3_resources, + }, { + .name = "max8925-regulator", + .id = 3, + .num_resources = ARRAY_SIZE(ldo1_resources), + .resources = ldo1_resources, + }, { + .name = "max8925-regulator", + .id = 4, + .num_resources = ARRAY_SIZE(ldo2_resources), + .resources = ldo2_resources, + }, { + .name = "max8925-regulator", + .id = 5, + .num_resources = ARRAY_SIZE(ldo3_resources), + .resources = ldo3_resources, + }, { + .name = "max8925-regulator", + .id = 6, + .num_resources = ARRAY_SIZE(ldo4_resources), + .resources = ldo4_resources, + }, { + .name = "max8925-regulator", + .id = 7, + .num_resources = ARRAY_SIZE(ldo5_resources), + .resources = ldo5_resources, + }, { + .name = "max8925-regulator", + .id = 8, + .num_resources = ARRAY_SIZE(ldo6_resources), + .resources = ldo6_resources, + }, { + .name = "max8925-regulator", + .id = 9, + .num_resources = ARRAY_SIZE(ldo7_resources), + .resources = ldo7_resources, + }, { + .name = "max8925-regulator", + .id = 10, + .num_resources = ARRAY_SIZE(ldo8_resources), + .resources = ldo8_resources, + }, { + .name = "max8925-regulator", + .id = 11, + .num_resources = ARRAY_SIZE(ldo9_resources), + .resources = ldo9_resources, + }, { + .name = "max8925-regulator", + .id = 12, + .num_resources = ARRAY_SIZE(ldo10_resources), + .resources = ldo10_resources, + }, { + .name = "max8925-regulator", + .id = 13, + .num_resources = ARRAY_SIZE(ldo11_resources), + .resources = ldo11_resources, + }, { + .name = "max8925-regulator", + .id = 14, + .num_resources = ARRAY_SIZE(ldo12_resources), + .resources = ldo12_resources, + }, { + .name = "max8925-regulator", + .id = 15, + .num_resources = ARRAY_SIZE(ldo13_resources), + .resources = ldo13_resources, + }, { + .name = "max8925-regulator", + .id = 16, + .num_resources = ARRAY_SIZE(ldo14_resources), + .resources = ldo14_resources, + }, { + .name = "max8925-regulator", + .id = 17, + .num_resources = ARRAY_SIZE(ldo15_resources), + .resources = ldo15_resources, + }, { + .name = "max8925-regulator", + .id = 18, + .num_resources = ARRAY_SIZE(ldo16_resources), + .resources = ldo16_resources, + }, { + .name = "max8925-regulator", + .id = 19, + .num_resources = ARRAY_SIZE(ldo17_resources), + .resources = ldo17_resources, + }, { + .name = "max8925-regulator", + .id = 20, + .num_resources = ARRAY_SIZE(ldo18_resources), + .resources = ldo18_resources, + }, { + .name = "max8925-regulator", + .id = 21, + .num_resources = ARRAY_SIZE(ldo19_resources), + .resources = ldo19_resources, + }, { + .name = "max8925-regulator", + .id = 22, + .num_resources = ARRAY_SIZE(ldo20_resources), + .resources = ldo20_resources, + }, +}; + +enum { + FLAGS_ADC = 1, /* register in ADC component */ + FLAGS_RTC, /* register in RTC component */ +}; + +struct max8925_irq_data { + int reg; + int mask_reg; + int enable; /* enable or not */ + int offs; /* bit offset in mask register */ + int flags; + int tsc_irq; +}; + +static struct max8925_irq_data max8925_irqs[] = { + [MAX8925_IRQ_VCHG_DC_OVP] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_VCHG_DC_F] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_VCHG_DC_R] = { + .reg = MAX8925_CHG_IRQ1, + .mask_reg = MAX8925_CHG_IRQ1_MASK, + .offs = 1 << 2, + }, + [MAX8925_IRQ_VCHG_THM_OK_R] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_VCHG_THM_OK_F] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_VCHG_SYSLOW_F] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 2, + }, + [MAX8925_IRQ_VCHG_SYSLOW_R] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 3, + }, + [MAX8925_IRQ_VCHG_RST] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 4, + }, + [MAX8925_IRQ_VCHG_DONE] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 5, + }, + [MAX8925_IRQ_VCHG_TOPOFF] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 6, + }, + [MAX8925_IRQ_VCHG_TMR_FAULT] = { + .reg = MAX8925_CHG_IRQ2, + .mask_reg = MAX8925_CHG_IRQ2_MASK, + .offs = 1 << 7, + }, + [MAX8925_IRQ_GPM_RSTIN] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_GPM_MPL] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_GPM_SW_3SEC] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 2, + }, + [MAX8925_IRQ_GPM_EXTON_F] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 3, + }, + [MAX8925_IRQ_GPM_EXTON_R] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 4, + }, + [MAX8925_IRQ_GPM_SW_1SEC] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 5, + }, + [MAX8925_IRQ_GPM_SW_F] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 6, + }, + [MAX8925_IRQ_GPM_SW_R] = { + .reg = MAX8925_ON_OFF_IRQ1, + .mask_reg = MAX8925_ON_OFF_IRQ1_MASK, + .offs = 1 << 7, + }, + [MAX8925_IRQ_GPM_SYSCKEN_F] = { + .reg = MAX8925_ON_OFF_IRQ2, + .mask_reg = MAX8925_ON_OFF_IRQ2_MASK, + .offs = 1 << 0, + }, + [MAX8925_IRQ_GPM_SYSCKEN_R] = { + .reg = MAX8925_ON_OFF_IRQ2, + .mask_reg = MAX8925_ON_OFF_IRQ2_MASK, + .offs = 1 << 1, + }, + [MAX8925_IRQ_RTC_ALARM1] = { + .reg = MAX8925_RTC_IRQ, + .mask_reg = MAX8925_RTC_IRQ_MASK, + .offs = 1 << 2, + .flags = FLAGS_RTC, + }, + [MAX8925_IRQ_RTC_ALARM0] = { + .reg = MAX8925_RTC_IRQ, + .mask_reg = MAX8925_RTC_IRQ_MASK, + .offs = 1 << 3, + .flags = FLAGS_RTC, + }, + [MAX8925_IRQ_TSC_STICK] = { + .reg = MAX8925_TSC_IRQ, + .mask_reg = MAX8925_TSC_IRQ_MASK, + .offs = 1 << 0, + .flags = FLAGS_ADC, + .tsc_irq = 1, + }, + [MAX8925_IRQ_TSC_NSTICK] = { + .reg = MAX8925_TSC_IRQ, + .mask_reg = MAX8925_TSC_IRQ_MASK, + .offs = 1 << 1, + .flags = FLAGS_ADC, + .tsc_irq = 1, + }, +}; + +static inline struct max8925_irq_data *irq_to_max8925(struct max8925_chip *chip, + int irq) +{ + return &max8925_irqs[irq - chip->irq_base]; +} + +static irqreturn_t max8925_irq(int irq, void *data) +{ + struct max8925_chip *chip = data; + struct max8925_irq_data *irq_data; + struct i2c_client *i2c; + int read_reg = -1, value = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + irq_data = &max8925_irqs[i]; + /* TSC IRQ should be serviced in max8925_tsc_irq() */ + if (irq_data->tsc_irq) + continue; + if (irq_data->flags == FLAGS_RTC) + i2c = chip->rtc; + else if (irq_data->flags == FLAGS_ADC) + i2c = chip->adc; + else + i2c = chip->i2c; + if (read_reg != irq_data->reg) { + read_reg = irq_data->reg; + value = max8925_reg_read(i2c, irq_data->reg); + } + if (value & irq_data->enable) + handle_nested_irq(chip->irq_base + i); + } + return IRQ_HANDLED; +} + +static irqreturn_t max8925_tsc_irq(int irq, void *data) +{ + struct max8925_chip *chip = data; + struct max8925_irq_data *irq_data; + struct i2c_client *i2c; + int read_reg = -1, value = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + irq_data = &max8925_irqs[i]; + /* non TSC IRQ should be serviced in max8925_irq() */ + if (!irq_data->tsc_irq) + continue; + if (irq_data->flags == FLAGS_RTC) + i2c = chip->rtc; + else if (irq_data->flags == FLAGS_ADC) + i2c = chip->adc; + else + i2c = chip->i2c; + if (read_reg != irq_data->reg) { + read_reg = irq_data->reg; + value = max8925_reg_read(i2c, irq_data->reg); + } + if (value & irq_data->enable) + handle_nested_irq(chip->irq_base + i); + } + return IRQ_HANDLED; +} + +static void max8925_irq_lock(struct irq_data *data) +{ + struct max8925_chip *chip = irq_data_get_irq_chip_data(data); + + mutex_lock(&chip->irq_lock); +} + +static void max8925_irq_sync_unlock(struct irq_data *data) +{ + struct max8925_chip *chip = irq_data_get_irq_chip_data(data); + struct max8925_irq_data *irq_data; + static unsigned char cache_chg[2] = {0xff, 0xff}; + static unsigned char cache_on[2] = {0xff, 0xff}; + static unsigned char cache_rtc = 0xff, cache_tsc = 0xff; + unsigned char irq_chg[2], irq_on[2]; + unsigned char irq_rtc, irq_tsc; + int i; + + /* Load cached value. In initial, all IRQs are masked */ + irq_chg[0] = cache_chg[0]; + irq_chg[1] = cache_chg[1]; + irq_on[0] = cache_on[0]; + irq_on[1] = cache_on[1]; + irq_rtc = cache_rtc; + irq_tsc = cache_tsc; + for (i = 0; i < ARRAY_SIZE(max8925_irqs); i++) { + irq_data = &max8925_irqs[i]; + /* 1 -- disable, 0 -- enable */ + switch (irq_data->mask_reg) { + case MAX8925_CHG_IRQ1_MASK: + irq_chg[0] &= ~irq_data->enable; + break; + case MAX8925_CHG_IRQ2_MASK: + irq_chg[1] &= ~irq_data->enable; + break; + case MAX8925_ON_OFF_IRQ1_MASK: + irq_on[0] &= ~irq_data->enable; + break; + case MAX8925_ON_OFF_IRQ2_MASK: + irq_on[1] &= ~irq_data->enable; + break; + case MAX8925_RTC_IRQ_MASK: + irq_rtc &= ~irq_data->enable; + break; + case MAX8925_TSC_IRQ_MASK: + irq_tsc &= ~irq_data->enable; + break; + default: + dev_err(chip->dev, "wrong IRQ\n"); + break; + } + } + /* update mask into registers */ + if (cache_chg[0] != irq_chg[0]) { + cache_chg[0] = irq_chg[0]; + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK, + irq_chg[0]); + } + if (cache_chg[1] != irq_chg[1]) { + cache_chg[1] = irq_chg[1]; + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ2_MASK, + irq_chg[1]); + } + if (cache_on[0] != irq_on[0]) { + cache_on[0] = irq_on[0]; + max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK, + irq_on[0]); + } + if (cache_on[1] != irq_on[1]) { + cache_on[1] = irq_on[1]; + max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK, + irq_on[1]); + } + if (cache_rtc != irq_rtc) { + cache_rtc = irq_rtc; + max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, irq_rtc); + } + if (cache_tsc != irq_tsc) { + cache_tsc = irq_tsc; + max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, irq_tsc); + } + + mutex_unlock(&chip->irq_lock); +} + +static void max8925_irq_enable(struct irq_data *data) +{ + struct max8925_chip *chip = irq_data_get_irq_chip_data(data); + + max8925_irqs[data->irq - chip->irq_base].enable + = max8925_irqs[data->irq - chip->irq_base].offs; +} + +static void max8925_irq_disable(struct irq_data *data) +{ + struct max8925_chip *chip = irq_data_get_irq_chip_data(data); + + max8925_irqs[data->irq - chip->irq_base].enable = 0; +} + +static struct irq_chip max8925_irq_chip = { + .name = "max8925", + .irq_bus_lock = max8925_irq_lock, + .irq_bus_sync_unlock = max8925_irq_sync_unlock, + .irq_enable = max8925_irq_enable, + .irq_disable = max8925_irq_disable, +}; + +static int max8925_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_data(virq, d->host_data); + irq_set_chip_and_handler(virq, &max8925_irq_chip, handle_edge_irq); + irq_set_nested_thread(virq, 1); +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + return 0; +} + +static struct irq_domain_ops max8925_irq_domain_ops = { + .map = max8925_irq_domain_map, + .xlate = irq_domain_xlate_onetwocell, +}; + + +static int max8925_irq_init(struct max8925_chip *chip, int irq, + struct max8925_platform_data *pdata) +{ + unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + int ret; + struct device_node *node = chip->dev->of_node; + + /* clear all interrupts */ + max8925_reg_read(chip->i2c, MAX8925_CHG_IRQ1); + max8925_reg_read(chip->i2c, MAX8925_CHG_IRQ2); + max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ1); + max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ2); + max8925_reg_read(chip->rtc, MAX8925_RTC_IRQ); + max8925_reg_read(chip->adc, MAX8925_TSC_IRQ); + /* mask all interrupts except for TSC */ + max8925_reg_write(chip->rtc, MAX8925_ALARM0_CNTL, 0); + max8925_reg_write(chip->rtc, MAX8925_ALARM1_CNTL, 0); + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK, 0xff); + max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ2_MASK, 0xff); + max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff); + max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK, 0xff); + max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, 0xff); + + mutex_init(&chip->irq_lock); + chip->irq_base = irq_alloc_descs(-1, 0, MAX8925_NR_IRQS, 0); + if (chip->irq_base < 0) { + dev_err(chip->dev, "Failed to allocate interrupts, ret:%d\n", + chip->irq_base); + return -EBUSY; + } + + irq_domain_add_legacy(node, MAX8925_NR_IRQS, chip->irq_base, 0, + &max8925_irq_domain_ops, chip); + + /* request irq handler for pmic main irq*/ + chip->core_irq = irq; + if (!chip->core_irq) + return -EBUSY; + ret = request_threaded_irq(irq, NULL, max8925_irq, flags | IRQF_ONESHOT, + "max8925", chip); + if (ret) { + dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret); + chip->core_irq = 0; + return -EBUSY; + } + + /* request irq handler for pmic tsc irq*/ + + /* mask TSC interrupt */ + max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, 0x0f); + + if (!pdata->tsc_irq) { + dev_warn(chip->dev, "No interrupt support on TSC IRQ\n"); + return 0; + } + chip->tsc_irq = pdata->tsc_irq; + ret = request_threaded_irq(chip->tsc_irq, NULL, max8925_tsc_irq, + flags | IRQF_ONESHOT, "max8925-tsc", chip); + if (ret) { + dev_err(chip->dev, "Failed to request TSC IRQ: %d\n", ret); + chip->tsc_irq = 0; + } + return 0; +} + +static void init_regulator(struct max8925_chip *chip, + struct max8925_platform_data *pdata) +{ + int ret; + + if (!pdata) + return; + if (pdata->sd1) { + reg_devs[0].platform_data = pdata->sd1; + reg_devs[0].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->sd2) { + reg_devs[1].platform_data = pdata->sd2; + reg_devs[1].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->sd3) { + reg_devs[2].platform_data = pdata->sd3; + reg_devs[2].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo1) { + reg_devs[3].platform_data = pdata->ldo1; + reg_devs[3].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo2) { + reg_devs[4].platform_data = pdata->ldo2; + reg_devs[4].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo3) { + reg_devs[5].platform_data = pdata->ldo3; + reg_devs[5].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo4) { + reg_devs[6].platform_data = pdata->ldo4; + reg_devs[6].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo5) { + reg_devs[7].platform_data = pdata->ldo5; + reg_devs[7].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo6) { + reg_devs[8].platform_data = pdata->ldo6; + reg_devs[8].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo7) { + reg_devs[9].platform_data = pdata->ldo7; + reg_devs[9].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo8) { + reg_devs[10].platform_data = pdata->ldo8; + reg_devs[10].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo9) { + reg_devs[11].platform_data = pdata->ldo9; + reg_devs[11].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo10) { + reg_devs[12].platform_data = pdata->ldo10; + reg_devs[12].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo11) { + reg_devs[13].platform_data = pdata->ldo11; + reg_devs[13].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo12) { + reg_devs[14].platform_data = pdata->ldo12; + reg_devs[14].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo13) { + reg_devs[15].platform_data = pdata->ldo13; + reg_devs[15].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo14) { + reg_devs[16].platform_data = pdata->ldo14; + reg_devs[16].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo15) { + reg_devs[17].platform_data = pdata->ldo15; + reg_devs[17].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo16) { + reg_devs[18].platform_data = pdata->ldo16; + reg_devs[18].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo17) { + reg_devs[19].platform_data = pdata->ldo17; + reg_devs[19].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo18) { + reg_devs[20].platform_data = pdata->ldo18; + reg_devs[20].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo19) { + reg_devs[21].platform_data = pdata->ldo19; + reg_devs[21].pdata_size = sizeof(struct regulator_init_data); + } + if (pdata->ldo20) { + reg_devs[22].platform_data = pdata->ldo20; + reg_devs[22].pdata_size = sizeof(struct regulator_init_data); + } + ret = mfd_add_devices(chip->dev, 0, reg_devs, ARRAY_SIZE(reg_devs), + NULL, 0, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regulator subdev\n"); + return; + } +} + +int max8925_device_init(struct max8925_chip *chip, + struct max8925_platform_data *pdata) +{ + int ret; + + max8925_irq_init(chip, chip->i2c->irq, pdata); + + if (pdata && (pdata->power || pdata->touch)) { + /* enable ADC to control internal reference */ + max8925_set_bits(chip->i2c, MAX8925_RESET_CNFG, 1, 1); + /* enable internal reference for ADC */ + max8925_set_bits(chip->adc, MAX8925_TSC_CNFG1, 3, 2); + /* check for internal reference IRQ */ + do { + ret = max8925_reg_read(chip->adc, MAX8925_TSC_IRQ); + } while (ret & MAX8925_NREF_OK); + /* enaable ADC scheduler, interval is 1 second */ + max8925_set_bits(chip->adc, MAX8925_ADC_SCHED, 3, 2); + } + + /* enable Momentary Power Loss */ + max8925_set_bits(chip->rtc, MAX8925_MPL_CNTL, 1 << 4, 1 << 4); + + ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0], + ARRAY_SIZE(rtc_devs), + NULL, chip->irq_base, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add rtc subdev\n"); + goto out; + } + + ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], + ARRAY_SIZE(onkey_devs), + NULL, chip->irq_base, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add onkey subdev\n"); + goto out_dev; + } + + init_regulator(chip, pdata); + + if (pdata && pdata->backlight) { + bk_devs[0].platform_data = &pdata->backlight; + bk_devs[0].pdata_size = sizeof(struct max8925_backlight_pdata); + } + ret = mfd_add_devices(chip->dev, 0, bk_devs, ARRAY_SIZE(bk_devs), + NULL, 0, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add backlight subdev\n"); + goto out_dev; + } + + ret = mfd_add_devices(chip->dev, 0, &power_devs[0], + ARRAY_SIZE(power_devs), + NULL, 0, NULL); + if (ret < 0) { + dev_err(chip->dev, + "Failed to add power supply subdev, err = %d\n", ret); + goto out_dev; + } + + if (pdata && pdata->touch) { + ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], + ARRAY_SIZE(touch_devs), + NULL, chip->tsc_irq, NULL); + if (ret < 0) { + dev_err(chip->dev, "Failed to add touch subdev\n"); + goto out_dev; + } + } + + return 0; +out_dev: + mfd_remove_devices(chip->dev); +out: + return ret; +} + +void max8925_device_exit(struct max8925_chip *chip) +{ + if (chip->core_irq) + free_irq(chip->core_irq, chip); + if (chip->tsc_irq) + free_irq(chip->tsc_irq, chip); + mfd_remove_devices(chip->dev); +} + + +MODULE_DESCRIPTION("PMIC Driver for Maxim MAX8925"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max8925-i2c.c b/kernel/drivers/mfd/max8925-i2c.c new file mode 100644 index 000000000..c880c895c --- /dev/null +++ b/kernel/drivers/mfd/max8925-i2c.c @@ -0,0 +1,277 @@ +/* + * I2C driver for Maxim MAX8925 + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/mfd/max8925.h> +#include <linux/slab.h> + +#define RTC_I2C_ADDR 0x68 +#define ADC_I2C_ADDR 0x47 + +static inline int max8925_read_device(struct i2c_client *i2c, + int reg, int bytes, void *dest) +{ + int ret; + + if (bytes > 1) + ret = i2c_smbus_read_i2c_block_data(i2c, reg, bytes, dest); + else { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret < 0) + return ret; + *(unsigned char *)dest = (unsigned char)ret; + } + return ret; +} + +static inline int max8925_write_device(struct i2c_client *i2c, + int reg, int bytes, void *src) +{ + unsigned char buf[9]; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + return 0; +} + +int max8925_reg_read(struct i2c_client *i2c, int reg) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + unsigned char data = 0; + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(max8925_reg_read); + +int max8925_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_write_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_reg_write); + +int max8925_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_bulk_read); + +int max8925_bulk_write(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_write_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(max8925_bulk_write); + +int max8925_set_bits(struct i2c_client *i2c, int reg, + unsigned char mask, unsigned char data) +{ + struct max8925_chip *chip = i2c_get_clientdata(i2c); + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + ret = max8925_read_device(i2c, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = max8925_write_device(i2c, reg, 1, &value); +out: + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(max8925_set_bits); + + +static const struct i2c_device_id max8925_id_table[] = { + { "max8925", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max8925_id_table); + +static int max8925_dt_init(struct device_node *np, struct device *dev, + struct max8925_platform_data *pdata) +{ + int ret; + + ret = of_property_read_u32(np, "maxim,tsc-irq", &pdata->tsc_irq); + if (ret) { + dev_err(dev, "Not found maxim,tsc-irq property\n"); + return -EINVAL; + } + return 0; +} + +static int max8925_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max8925_platform_data *pdata = dev_get_platdata(&client->dev); + static struct max8925_chip *chip; + struct device_node *node = client->dev.of_node; + + if (node && !pdata) { + /* parse DT to get platform data */ + pdata = devm_kzalloc(&client->dev, + sizeof(struct max8925_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (max8925_dt_init(node, &client->dev, pdata)) + return -EINVAL; + } else if (!pdata) { + pr_info("%s: platform data is missing\n", __func__); + return -EINVAL; + } + + chip = devm_kzalloc(&client->dev, + sizeof(struct max8925_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->i2c = client; + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + dev_set_drvdata(chip->dev, chip); + mutex_init(&chip->io_lock); + + chip->rtc = i2c_new_dummy(chip->i2c->adapter, RTC_I2C_ADDR); + if (!chip->rtc) { + dev_err(chip->dev, "Failed to allocate I2C device for RTC\n"); + return -ENODEV; + } + i2c_set_clientdata(chip->rtc, chip); + + chip->adc = i2c_new_dummy(chip->i2c->adapter, ADC_I2C_ADDR); + if (!chip->adc) { + dev_err(chip->dev, "Failed to allocate I2C device for ADC\n"); + i2c_unregister_device(chip->rtc); + return -ENODEV; + } + i2c_set_clientdata(chip->adc, chip); + + device_init_wakeup(&client->dev, 1); + + max8925_device_init(chip, pdata); + + return 0; +} + +static int max8925_remove(struct i2c_client *client) +{ + struct max8925_chip *chip = i2c_get_clientdata(client); + + max8925_device_exit(chip); + i2c_unregister_device(chip->adc); + i2c_unregister_device(chip->rtc); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max8925_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max8925_chip *chip = i2c_get_clientdata(client); + + if (device_may_wakeup(dev) && chip->wakeup_flag) + enable_irq_wake(chip->core_irq); + return 0; +} + +static int max8925_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct max8925_chip *chip = i2c_get_clientdata(client); + + if (device_may_wakeup(dev) && chip->wakeup_flag) + disable_irq_wake(chip->core_irq); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max8925_pm_ops, max8925_suspend, max8925_resume); + +static const struct of_device_id max8925_dt_ids[] = { + { .compatible = "maxim,max8925", }, + {}, +}; +MODULE_DEVICE_TABLE(of, max8925_dt_ids); + +static struct i2c_driver max8925_driver = { + .driver = { + .name = "max8925", + .owner = THIS_MODULE, + .pm = &max8925_pm_ops, + .of_match_table = max8925_dt_ids, + }, + .probe = max8925_probe, + .remove = max8925_remove, + .id_table = max8925_id_table, +}; + +static int __init max8925_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&max8925_driver); + if (ret != 0) + pr_err("Failed to register MAX8925 I2C driver: %d\n", ret); + + return ret; +} +subsys_initcall(max8925_i2c_init); + +static void __exit max8925_i2c_exit(void) +{ + i2c_del_driver(&max8925_driver); +} +module_exit(max8925_i2c_exit); + +MODULE_DESCRIPTION("I2C Driver for Maxim 8925"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max8997-irq.c b/kernel/drivers/mfd/max8997-irq.c new file mode 100644 index 000000000..43fa61413 --- /dev/null +++ b/kernel/drivers/mfd/max8997-irq.c @@ -0,0 +1,387 @@ +/* + * max8997-irq.c - Interrupt controller support for MAX8997 + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * MyungJoo Ham <myungjoo.ham@samsung.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8998-irq.c + */ + +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +static const u8 max8997_mask_reg[] = { + [PMIC_INT1] = MAX8997_REG_INT1MSK, + [PMIC_INT2] = MAX8997_REG_INT2MSK, + [PMIC_INT3] = MAX8997_REG_INT3MSK, + [PMIC_INT4] = MAX8997_REG_INT4MSK, + [FUEL_GAUGE] = MAX8997_REG_INVALID, + [MUIC_INT1] = MAX8997_MUIC_REG_INTMASK1, + [MUIC_INT2] = MAX8997_MUIC_REG_INTMASK2, + [MUIC_INT3] = MAX8997_MUIC_REG_INTMASK3, + [GPIO_LOW] = MAX8997_REG_INVALID, + [GPIO_HI] = MAX8997_REG_INVALID, + [FLASH_STATUS] = MAX8997_REG_INVALID, +}; + +static struct i2c_client *get_i2c(struct max8997_dev *max8997, + enum max8997_irq_source src) +{ + switch (src) { + case PMIC_INT1 ... PMIC_INT4: + return max8997->i2c; + case FUEL_GAUGE: + return NULL; + case MUIC_INT1 ... MUIC_INT3: + return max8997->muic; + case GPIO_LOW ... GPIO_HI: + return max8997->i2c; + case FLASH_STATUS: + return max8997->i2c; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max8997_irq_data { + int mask; + enum max8997_irq_source group; +}; + +#define DECLARE_IRQ(idx, _group, _mask) \ + [(idx)] = { .group = (_group), .mask = (_mask) } +static const struct max8997_irq_data max8997_irqs[] = { + DECLARE_IRQ(MAX8997_PMICIRQ_PWRONR, PMIC_INT1, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_PWRONF, PMIC_INT1, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_PWRON1SEC, PMIC_INT1, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_JIGONR, PMIC_INT1, 1 << 4), + DECLARE_IRQ(MAX8997_PMICIRQ_JIGONF, PMIC_INT1, 1 << 5), + DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT2, PMIC_INT1, 1 << 6), + DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT1, PMIC_INT1, 1 << 7), + + DECLARE_IRQ(MAX8997_PMICIRQ_JIGR, PMIC_INT2, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_JIGF, PMIC_INT2, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_MR, PMIC_INT2, 1 << 2), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS1OK, PMIC_INT2, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS2OK, PMIC_INT2, 1 << 4), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS3OK, PMIC_INT2, 1 << 5), + DECLARE_IRQ(MAX8997_PMICIRQ_DVS4OK, PMIC_INT2, 1 << 6), + + DECLARE_IRQ(MAX8997_PMICIRQ_CHGINS, PMIC_INT3, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_CHGRM, PMIC_INT3, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_DCINOVP, PMIC_INT3, 1 << 2), + DECLARE_IRQ(MAX8997_PMICIRQ_TOPOFFR, PMIC_INT3, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_CHGRSTF, PMIC_INT3, 1 << 5), + DECLARE_IRQ(MAX8997_PMICIRQ_MBCHGTMEXPD, PMIC_INT3, 1 << 7), + + DECLARE_IRQ(MAX8997_PMICIRQ_RTC60S, PMIC_INT4, 1 << 0), + DECLARE_IRQ(MAX8997_PMICIRQ_RTCA1, PMIC_INT4, 1 << 1), + DECLARE_IRQ(MAX8997_PMICIRQ_RTCA2, PMIC_INT4, 1 << 2), + DECLARE_IRQ(MAX8997_PMICIRQ_SMPL_INT, PMIC_INT4, 1 << 3), + DECLARE_IRQ(MAX8997_PMICIRQ_RTC1S, PMIC_INT4, 1 << 4), + DECLARE_IRQ(MAX8997_PMICIRQ_WTSR, PMIC_INT4, 1 << 5), + + DECLARE_IRQ(MAX8997_MUICIRQ_ADCError, MUIC_INT1, 1 << 2), + DECLARE_IRQ(MAX8997_MUICIRQ_ADCLow, MUIC_INT1, 1 << 1), + DECLARE_IRQ(MAX8997_MUICIRQ_ADC, MUIC_INT1, 1 << 0), + + DECLARE_IRQ(MAX8997_MUICIRQ_VBVolt, MUIC_INT2, 1 << 4), + DECLARE_IRQ(MAX8997_MUICIRQ_DBChg, MUIC_INT2, 1 << 3), + DECLARE_IRQ(MAX8997_MUICIRQ_DCDTmr, MUIC_INT2, 1 << 2), + DECLARE_IRQ(MAX8997_MUICIRQ_ChgDetRun, MUIC_INT2, 1 << 1), + DECLARE_IRQ(MAX8997_MUICIRQ_ChgTyp, MUIC_INT2, 1 << 0), + + DECLARE_IRQ(MAX8997_MUICIRQ_OVP, MUIC_INT3, 1 << 2), +}; + +static void max8997_irq_lock(struct irq_data *data) +{ + struct max8997_dev *max8997 = irq_get_chip_data(data->irq); + + mutex_lock(&max8997->irqlock); +} + +static void max8997_irq_sync_unlock(struct irq_data *data) +{ + struct max8997_dev *max8997 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) { + u8 mask_reg = max8997_mask_reg[i]; + struct i2c_client *i2c = get_i2c(max8997, i); + + if (mask_reg == MAX8997_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + max8997->irq_masks_cache[i] = max8997->irq_masks_cur[i]; + + max8997_write_reg(i2c, max8997_mask_reg[i], + max8997->irq_masks_cur[i]); + } + + mutex_unlock(&max8997->irqlock); +} + +static const inline struct max8997_irq_data * +irq_to_max8997_irq(struct max8997_dev *max8997, int irq) +{ + struct irq_data *data = irq_get_irq_data(irq); + return &max8997_irqs[data->hwirq]; +} + +static void max8997_irq_mask(struct irq_data *data) +{ + struct max8997_dev *max8997 = irq_get_chip_data(data->irq); + const struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, + data->irq); + + max8997->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void max8997_irq_unmask(struct irq_data *data) +{ + struct max8997_dev *max8997 = irq_get_chip_data(data->irq); + const struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, + data->irq); + + max8997->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +static struct irq_chip max8997_irq_chip = { + .name = "max8997", + .irq_bus_lock = max8997_irq_lock, + .irq_bus_sync_unlock = max8997_irq_sync_unlock, + .irq_mask = max8997_irq_mask, + .irq_unmask = max8997_irq_unmask, +}; + +#define MAX8997_IRQSRC_PMIC (1 << 1) +#define MAX8997_IRQSRC_FUELGAUGE (1 << 2) +#define MAX8997_IRQSRC_MUIC (1 << 3) +#define MAX8997_IRQSRC_GPIO (1 << 4) +#define MAX8997_IRQSRC_FLASH (1 << 5) +static irqreturn_t max8997_irq_thread(int irq, void *data) +{ + struct max8997_dev *max8997 = data; + u8 irq_reg[MAX8997_IRQ_GROUP_NR] = {}; + u8 irq_src; + int ret; + int i, cur_irq; + + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_INTSRC, &irq_src); + if (ret < 0) { + dev_err(max8997->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + if (irq_src & MAX8997_IRQSRC_PMIC) { + /* PMIC INT1 ~ INT4 */ + max8997_bulk_read(max8997->i2c, MAX8997_REG_INT1, 4, + &irq_reg[PMIC_INT1]); + } + if (irq_src & MAX8997_IRQSRC_FUELGAUGE) { + /* + * TODO: FUEL GAUGE + * + * This is to be supported by Max17042 driver. When + * an interrupt incurs here, it should be relayed to a + * Max17042 device that is connected (probably by + * platform-data). However, we do not have interrupt + * handling in Max17042 driver currently. The Max17042 IRQ + * driver should be ready to be used as a stand-alone device and + * a Max8997-dependent device. Because it is not ready in + * Max17042-side and it is not too critical in operating + * Max8997, we do not implement this in initial releases. + */ + irq_reg[FUEL_GAUGE] = 0; + } + if (irq_src & MAX8997_IRQSRC_MUIC) { + /* MUIC INT1 ~ INT3 */ + max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, 3, + &irq_reg[MUIC_INT1]); + } + if (irq_src & MAX8997_IRQSRC_GPIO) { + /* GPIO Interrupt */ + u8 gpio_info[MAX8997_NUM_GPIO]; + + irq_reg[GPIO_LOW] = 0; + irq_reg[GPIO_HI] = 0; + + max8997_bulk_read(max8997->i2c, MAX8997_REG_GPIOCNTL1, + MAX8997_NUM_GPIO, gpio_info); + for (i = 0; i < MAX8997_NUM_GPIO; i++) { + bool interrupt = false; + + switch (gpio_info[i] & MAX8997_GPIO_INT_MASK) { + case MAX8997_GPIO_INT_BOTH: + if (max8997->gpio_status[i] != gpio_info[i]) + interrupt = true; + break; + case MAX8997_GPIO_INT_RISE: + if ((max8997->gpio_status[i] != gpio_info[i]) && + (gpio_info[i] & MAX8997_GPIO_DATA_MASK)) + interrupt = true; + break; + case MAX8997_GPIO_INT_FALL: + if ((max8997->gpio_status[i] != gpio_info[i]) && + !(gpio_info[i] & MAX8997_GPIO_DATA_MASK)) + interrupt = true; + break; + default: + break; + } + + if (interrupt) { + if (i < 8) + irq_reg[GPIO_LOW] |= (1 << i); + else + irq_reg[GPIO_HI] |= (1 << (i - 8)); + } + + } + } + if (irq_src & MAX8997_IRQSRC_FLASH) { + /* Flash Status Interrupt */ + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_FLASHSTATUS, + &irq_reg[FLASH_STATUS]); + } + + /* Apply masking */ + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~max8997->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < MAX8997_IRQ_NR; i++) { + if (irq_reg[max8997_irqs[i].group] & max8997_irqs[i].mask) { + cur_irq = irq_find_mapping(max8997->irq_domain, i); + if (cur_irq) + handle_nested_irq(cur_irq); + } + } + + return IRQ_HANDLED; +} + +int max8997_irq_resume(struct max8997_dev *max8997) +{ + if (max8997->irq && max8997->irq_domain) + max8997_irq_thread(0, max8997); + return 0; +} + +static int max8997_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct max8997_dev *max8997 = d->host_data; + + irq_set_chip_data(irq, max8997); + irq_set_chip_and_handler(irq, &max8997_irq_chip, handle_edge_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + return 0; +} + +static struct irq_domain_ops max8997_irq_domain_ops = { + .map = max8997_irq_domain_map, +}; + +int max8997_irq_init(struct max8997_dev *max8997) +{ + struct irq_domain *domain; + int i; + int ret; + u8 val; + + if (!max8997->irq) { + dev_warn(max8997->dev, "No interrupt specified.\n"); + return 0; + } + + mutex_init(&max8997->irqlock); + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + + max8997->irq_masks_cur[i] = 0xff; + max8997->irq_masks_cache[i] = 0xff; + i2c = get_i2c(max8997, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max8997_mask_reg[i] == MAX8997_REG_INVALID) + continue; + + max8997_write_reg(i2c, max8997_mask_reg[i], 0xff); + } + + for (i = 0; i < MAX8997_NUM_GPIO; i++) { + max8997->gpio_status[i] = (max8997_read_reg(max8997->i2c, + MAX8997_REG_GPIOCNTL1 + i, + &val) + & MAX8997_GPIO_DATA_MASK) ? + true : false; + } + + domain = irq_domain_add_linear(NULL, MAX8997_IRQ_NR, + &max8997_irq_domain_ops, max8997); + if (!domain) { + dev_err(max8997->dev, "could not create irq domain\n"); + return -ENODEV; + } + max8997->irq_domain = domain; + + ret = request_threaded_irq(max8997->irq, NULL, max8997_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max8997-irq", max8997); + + if (ret) { + dev_err(max8997->dev, "Failed to request IRQ %d: %d\n", + max8997->irq, ret); + return ret; + } + + if (!max8997->ono) + return 0; + + ret = request_threaded_irq(max8997->ono, NULL, max8997_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "max8997-ono", max8997); + + if (ret) + dev_err(max8997->dev, "Failed to request ono-IRQ %d: %d\n", + max8997->ono, ret); + + return 0; +} + +void max8997_irq_exit(struct max8997_dev *max8997) +{ + if (max8997->ono) + free_irq(max8997->ono, max8997); + + if (max8997->irq) + free_irq(max8997->irq, max8997); +} diff --git a/kernel/drivers/mfd/max8997.c b/kernel/drivers/mfd/max8997.c new file mode 100644 index 000000000..595364ee1 --- /dev/null +++ b/kernel/drivers/mfd/max8997.c @@ -0,0 +1,535 @@ +/* + * max8997.c - mfd core driver for the Maxim 8966 and 8997 + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@smasung.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This driver is based on max8998.c + */ + +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max8997.h> +#include <linux/mfd/max8997-private.h> + +#define I2C_ADDR_PMIC (0xCC >> 1) +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_BATTERY (0x6C >> 1) +#define I2C_ADDR_RTC (0x0C >> 1) +#define I2C_ADDR_HAPTIC (0x90 >> 1) + +static const struct mfd_cell max8997_devs[] = { + { .name = "max8997-pmic", }, + { .name = "max8997-rtc", }, + { .name = "max8997-battery", }, + { .name = "max8997-haptic", }, + { .name = "max8997-muic", }, + { .name = "max8997-led", .id = 1 }, + { .name = "max8997-led", .id = 2 }, +}; + +#ifdef CONFIG_OF +static const struct of_device_id max8997_pmic_dt_match[] = { + { .compatible = "maxim,max8997-pmic", .data = (void *)TYPE_MAX8997 }, + {}, +}; +#endif + +int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&max8997->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(max8997_read_reg); + +int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max8997->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max8997_bulk_read); + +int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&max8997->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max8997_write_reg); + +int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max8997->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max8997_bulk_write); + +int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8997->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&max8997->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max8997_update_reg); + +/* + * Only the common platform data elements for max8997 are parsed here from the + * device tree. Other sub-modules of max8997 such as pmic, rtc and others have + * to parse their own platform data elements from device tree. + * + * The max8997 platform data structure is instantiated here and the drivers for + * the sub-modules need not instantiate another instance while parsing their + * platform data. + */ +static struct max8997_platform_data *max8997_i2c_parse_dt_pdata( + struct device *dev) +{ + struct max8997_platform_data *pd; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) { + dev_err(dev, "could not allocate memory for pdata\n"); + return ERR_PTR(-ENOMEM); + } + + pd->ono = irq_of_parse_and_map(dev->of_node, 1); + + /* + * ToDo: the 'wakeup' member in the platform data is more of a linux + * specfic information. Hence, there is no binding for that yet and + * not parsed here. + */ + + return pd; +} + +static inline unsigned long max8997_i2c_get_driver_data(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + if (IS_ENABLED(CONFIG_OF) && i2c->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(max8997_pmic_dt_match, i2c->dev.of_node); + return (unsigned long)match->data; + } + return id->driver_data; +} + +static int max8997_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max8997_dev *max8997; + struct max8997_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret = 0; + + max8997 = devm_kzalloc(&i2c->dev, sizeof(struct max8997_dev), + GFP_KERNEL); + if (max8997 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max8997); + max8997->dev = &i2c->dev; + max8997->i2c = i2c; + max8997->type = max8997_i2c_get_driver_data(i2c, id); + max8997->irq = i2c->irq; + + if (IS_ENABLED(CONFIG_OF) && max8997->dev->of_node) { + pdata = max8997_i2c_parse_dt_pdata(max8997->dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + if (!pdata) + return ret; + + max8997->pdata = pdata; + max8997->ono = pdata->ono; + + mutex_init(&max8997->iolock); + + max8997->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC); + if (!max8997->rtc) { + dev_err(max8997->dev, "Failed to allocate I2C device for RTC\n"); + return -ENODEV; + } + i2c_set_clientdata(max8997->rtc, max8997); + + max8997->haptic = i2c_new_dummy(i2c->adapter, I2C_ADDR_HAPTIC); + if (!max8997->haptic) { + dev_err(max8997->dev, "Failed to allocate I2C device for Haptic\n"); + ret = -ENODEV; + goto err_i2c_haptic; + } + i2c_set_clientdata(max8997->haptic, max8997); + + max8997->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); + if (!max8997->muic) { + dev_err(max8997->dev, "Failed to allocate I2C device for MUIC\n"); + ret = -ENODEV; + goto err_i2c_muic; + } + i2c_set_clientdata(max8997->muic, max8997); + + pm_runtime_set_active(max8997->dev); + + max8997_irq_init(max8997); + + ret = mfd_add_devices(max8997->dev, -1, max8997_devs, + ARRAY_SIZE(max8997_devs), + NULL, 0, NULL); + if (ret < 0) { + dev_err(max8997->dev, "failed to add MFD devices %d\n", ret); + goto err_mfd; + } + + /* + * TODO: enable others (flash, muic, rtc, battery, ...) and + * check the return value + */ + + /* MAX8997 has a power button input. */ + device_init_wakeup(max8997->dev, pdata->wakeup); + + return ret; + +err_mfd: + mfd_remove_devices(max8997->dev); + i2c_unregister_device(max8997->muic); +err_i2c_muic: + i2c_unregister_device(max8997->haptic); +err_i2c_haptic: + i2c_unregister_device(max8997->rtc); + return ret; +} + +static int max8997_i2c_remove(struct i2c_client *i2c) +{ + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max8997->dev); + i2c_unregister_device(max8997->muic); + i2c_unregister_device(max8997->haptic); + i2c_unregister_device(max8997->rtc); + + return 0; +} + +static const struct i2c_device_id max8997_i2c_id[] = { + { "max8997", TYPE_MAX8997 }, + { "max8966", TYPE_MAX8966 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8998_i2c_id); + +static u8 max8997_dumpaddr_pmic[] = { + MAX8997_REG_INT1MSK, + MAX8997_REG_INT2MSK, + MAX8997_REG_INT3MSK, + MAX8997_REG_INT4MSK, + MAX8997_REG_MAINCON1, + MAX8997_REG_MAINCON2, + MAX8997_REG_BUCKRAMP, + MAX8997_REG_BUCK1CTRL, + MAX8997_REG_BUCK1DVS1, + MAX8997_REG_BUCK1DVS2, + MAX8997_REG_BUCK1DVS3, + MAX8997_REG_BUCK1DVS4, + MAX8997_REG_BUCK1DVS5, + MAX8997_REG_BUCK1DVS6, + MAX8997_REG_BUCK1DVS7, + MAX8997_REG_BUCK1DVS8, + MAX8997_REG_BUCK2CTRL, + MAX8997_REG_BUCK2DVS1, + MAX8997_REG_BUCK2DVS2, + MAX8997_REG_BUCK2DVS3, + MAX8997_REG_BUCK2DVS4, + MAX8997_REG_BUCK2DVS5, + MAX8997_REG_BUCK2DVS6, + MAX8997_REG_BUCK2DVS7, + MAX8997_REG_BUCK2DVS8, + MAX8997_REG_BUCK3CTRL, + MAX8997_REG_BUCK3DVS, + MAX8997_REG_BUCK4CTRL, + MAX8997_REG_BUCK4DVS, + MAX8997_REG_BUCK5CTRL, + MAX8997_REG_BUCK5DVS1, + MAX8997_REG_BUCK5DVS2, + MAX8997_REG_BUCK5DVS3, + MAX8997_REG_BUCK5DVS4, + MAX8997_REG_BUCK5DVS5, + MAX8997_REG_BUCK5DVS6, + MAX8997_REG_BUCK5DVS7, + MAX8997_REG_BUCK5DVS8, + MAX8997_REG_BUCK6CTRL, + MAX8997_REG_BUCK6BPSKIPCTRL, + MAX8997_REG_BUCK7CTRL, + MAX8997_REG_BUCK7DVS, + MAX8997_REG_LDO1CTRL, + MAX8997_REG_LDO2CTRL, + MAX8997_REG_LDO3CTRL, + MAX8997_REG_LDO4CTRL, + MAX8997_REG_LDO5CTRL, + MAX8997_REG_LDO6CTRL, + MAX8997_REG_LDO7CTRL, + MAX8997_REG_LDO8CTRL, + MAX8997_REG_LDO9CTRL, + MAX8997_REG_LDO10CTRL, + MAX8997_REG_LDO11CTRL, + MAX8997_REG_LDO12CTRL, + MAX8997_REG_LDO13CTRL, + MAX8997_REG_LDO14CTRL, + MAX8997_REG_LDO15CTRL, + MAX8997_REG_LDO16CTRL, + MAX8997_REG_LDO17CTRL, + MAX8997_REG_LDO18CTRL, + MAX8997_REG_LDO21CTRL, + MAX8997_REG_MBCCTRL1, + MAX8997_REG_MBCCTRL2, + MAX8997_REG_MBCCTRL3, + MAX8997_REG_MBCCTRL4, + MAX8997_REG_MBCCTRL5, + MAX8997_REG_MBCCTRL6, + MAX8997_REG_OTPCGHCVS, + MAX8997_REG_SAFEOUTCTRL, + MAX8997_REG_LBCNFG1, + MAX8997_REG_LBCNFG2, + MAX8997_REG_BBCCTRL, + + MAX8997_REG_FLASH1_CUR, + MAX8997_REG_FLASH2_CUR, + MAX8997_REG_MOVIE_CUR, + MAX8997_REG_GSMB_CUR, + MAX8997_REG_BOOST_CNTL, + MAX8997_REG_LEN_CNTL, + MAX8997_REG_FLASH_CNTL, + MAX8997_REG_WDT_CNTL, + MAX8997_REG_MAXFLASH1, + MAX8997_REG_MAXFLASH2, + MAX8997_REG_FLASHSTATUSMASK, + + MAX8997_REG_GPIOCNTL1, + MAX8997_REG_GPIOCNTL2, + MAX8997_REG_GPIOCNTL3, + MAX8997_REG_GPIOCNTL4, + MAX8997_REG_GPIOCNTL5, + MAX8997_REG_GPIOCNTL6, + MAX8997_REG_GPIOCNTL7, + MAX8997_REG_GPIOCNTL8, + MAX8997_REG_GPIOCNTL9, + MAX8997_REG_GPIOCNTL10, + MAX8997_REG_GPIOCNTL11, + MAX8997_REG_GPIOCNTL12, + + MAX8997_REG_LDO1CONFIG, + MAX8997_REG_LDO2CONFIG, + MAX8997_REG_LDO3CONFIG, + MAX8997_REG_LDO4CONFIG, + MAX8997_REG_LDO5CONFIG, + MAX8997_REG_LDO6CONFIG, + MAX8997_REG_LDO7CONFIG, + MAX8997_REG_LDO8CONFIG, + MAX8997_REG_LDO9CONFIG, + MAX8997_REG_LDO10CONFIG, + MAX8997_REG_LDO11CONFIG, + MAX8997_REG_LDO12CONFIG, + MAX8997_REG_LDO13CONFIG, + MAX8997_REG_LDO14CONFIG, + MAX8997_REG_LDO15CONFIG, + MAX8997_REG_LDO16CONFIG, + MAX8997_REG_LDO17CONFIG, + MAX8997_REG_LDO18CONFIG, + MAX8997_REG_LDO21CONFIG, + + MAX8997_REG_DVSOKTIMER1, + MAX8997_REG_DVSOKTIMER2, + MAX8997_REG_DVSOKTIMER4, + MAX8997_REG_DVSOKTIMER5, +}; + +static u8 max8997_dumpaddr_muic[] = { + MAX8997_MUIC_REG_INTMASK1, + MAX8997_MUIC_REG_INTMASK2, + MAX8997_MUIC_REG_INTMASK3, + MAX8997_MUIC_REG_CDETCTRL, + MAX8997_MUIC_REG_CONTROL1, + MAX8997_MUIC_REG_CONTROL2, + MAX8997_MUIC_REG_CONTROL3, +}; + +static u8 max8997_dumpaddr_haptic[] = { + MAX8997_HAPTIC_REG_CONF1, + MAX8997_HAPTIC_REG_CONF2, + MAX8997_HAPTIC_REG_DRVCONF, + MAX8997_HAPTIC_REG_CYCLECONF1, + MAX8997_HAPTIC_REG_CYCLECONF2, + MAX8997_HAPTIC_REG_SIGCONF1, + MAX8997_HAPTIC_REG_SIGCONF2, + MAX8997_HAPTIC_REG_SIGCONF3, + MAX8997_HAPTIC_REG_SIGCONF4, + MAX8997_HAPTIC_REG_SIGDC1, + MAX8997_HAPTIC_REG_SIGDC2, + MAX8997_HAPTIC_REG_SIGPWMDC1, + MAX8997_HAPTIC_REG_SIGPWMDC2, + MAX8997_HAPTIC_REG_SIGPWMDC3, + MAX8997_HAPTIC_REG_SIGPWMDC4, +}; + +static int max8997_freeze(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++) + max8997_read_reg(i2c, max8997_dumpaddr_pmic[i], + &max8997->reg_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++) + max8997_read_reg(i2c, max8997_dumpaddr_muic[i], + &max8997->reg_dump[i + MAX8997_REG_PMIC_END]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++) + max8997_read_reg(i2c, max8997_dumpaddr_haptic[i], + &max8997->reg_dump[i + MAX8997_REG_PMIC_END + + MAX8997_MUIC_REG_END]); + + return 0; +} + +static int max8997_restore(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++) + max8997_write_reg(i2c, max8997_dumpaddr_pmic[i], + max8997->reg_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++) + max8997_write_reg(i2c, max8997_dumpaddr_muic[i], + max8997->reg_dump[i + MAX8997_REG_PMIC_END]); + + for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++) + max8997_write_reg(i2c, max8997_dumpaddr_haptic[i], + max8997->reg_dump[i + MAX8997_REG_PMIC_END + + MAX8997_MUIC_REG_END]); + + return 0; +} + +static int max8997_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + irq_set_irq_wake(max8997->irq, 1); + return 0; +} + +static int max8997_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8997_dev *max8997 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + irq_set_irq_wake(max8997->irq, 0); + return max8997_irq_resume(max8997); +} + +static const struct dev_pm_ops max8997_pm = { + .suspend = max8997_suspend, + .resume = max8997_resume, + .freeze = max8997_freeze, + .restore = max8997_restore, +}; + +static struct i2c_driver max8997_i2c_driver = { + .driver = { + .name = "max8997", + .owner = THIS_MODULE, + .pm = &max8997_pm, + .of_match_table = of_match_ptr(max8997_pmic_dt_match), + }, + .probe = max8997_i2c_probe, + .remove = max8997_i2c_remove, + .id_table = max8997_i2c_id, +}; + +static int __init max8997_i2c_init(void) +{ + return i2c_add_driver(&max8997_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max8997_i2c_init); + +static void __exit max8997_i2c_exit(void) +{ + i2c_del_driver(&max8997_i2c_driver); +} +module_exit(max8997_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 8997 multi-function core driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/max8998-irq.c b/kernel/drivers/mfd/max8998-irq.c new file mode 100644 index 000000000..c469477eb --- /dev/null +++ b/kernel/drivers/mfd/max8998-irq.c @@ -0,0 +1,282 @@ +/* + * Interrupt controller support for MAX8998 + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim <jy0922.shim@samsung.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/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/mfd/max8998-private.h> + +struct max8998_irq_data { + int reg; + int mask; +}; + +static struct max8998_irq_data max8998_irqs[] = { + [MAX8998_IRQ_DCINF] = { + .reg = 1, + .mask = MAX8998_IRQ_DCINF_MASK, + }, + [MAX8998_IRQ_DCINR] = { + .reg = 1, + .mask = MAX8998_IRQ_DCINR_MASK, + }, + [MAX8998_IRQ_JIGF] = { + .reg = 1, + .mask = MAX8998_IRQ_JIGF_MASK, + }, + [MAX8998_IRQ_JIGR] = { + .reg = 1, + .mask = MAX8998_IRQ_JIGR_MASK, + }, + [MAX8998_IRQ_PWRONF] = { + .reg = 1, + .mask = MAX8998_IRQ_PWRONF_MASK, + }, + [MAX8998_IRQ_PWRONR] = { + .reg = 1, + .mask = MAX8998_IRQ_PWRONR_MASK, + }, + [MAX8998_IRQ_WTSREVNT] = { + .reg = 2, + .mask = MAX8998_IRQ_WTSREVNT_MASK, + }, + [MAX8998_IRQ_SMPLEVNT] = { + .reg = 2, + .mask = MAX8998_IRQ_SMPLEVNT_MASK, + }, + [MAX8998_IRQ_ALARM1] = { + .reg = 2, + .mask = MAX8998_IRQ_ALARM1_MASK, + }, + [MAX8998_IRQ_ALARM0] = { + .reg = 2, + .mask = MAX8998_IRQ_ALARM0_MASK, + }, + [MAX8998_IRQ_ONKEY1S] = { + .reg = 3, + .mask = MAX8998_IRQ_ONKEY1S_MASK, + }, + [MAX8998_IRQ_TOPOFFR] = { + .reg = 3, + .mask = MAX8998_IRQ_TOPOFFR_MASK, + }, + [MAX8998_IRQ_DCINOVPR] = { + .reg = 3, + .mask = MAX8998_IRQ_DCINOVPR_MASK, + }, + [MAX8998_IRQ_CHGRSTF] = { + .reg = 3, + .mask = MAX8998_IRQ_CHGRSTF_MASK, + }, + [MAX8998_IRQ_DONER] = { + .reg = 3, + .mask = MAX8998_IRQ_DONER_MASK, + }, + [MAX8998_IRQ_CHGFAULT] = { + .reg = 3, + .mask = MAX8998_IRQ_CHGFAULT_MASK, + }, + [MAX8998_IRQ_LOBAT1] = { + .reg = 4, + .mask = MAX8998_IRQ_LOBAT1_MASK, + }, + [MAX8998_IRQ_LOBAT2] = { + .reg = 4, + .mask = MAX8998_IRQ_LOBAT2_MASK, + }, +}; + +static inline struct max8998_irq_data * +irq_to_max8998_irq(struct max8998_dev *max8998, int irq) +{ + struct irq_data *data = irq_get_irq_data(irq); + return &max8998_irqs[data->hwirq]; +} + +static void max8998_irq_lock(struct irq_data *data) +{ + struct max8998_dev *max8998 = irq_data_get_irq_chip_data(data); + + mutex_lock(&max8998->irqlock); +} + +static void max8998_irq_sync_unlock(struct irq_data *data) +{ + struct max8998_dev *max8998 = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(max8998->irq_masks_cur); i++) { + /* + * If there's been a change in the mask write it back + * to the hardware. + */ + if (max8998->irq_masks_cur[i] != max8998->irq_masks_cache[i]) { + max8998->irq_masks_cache[i] = max8998->irq_masks_cur[i]; + max8998_write_reg(max8998->i2c, MAX8998_REG_IRQM1 + i, + max8998->irq_masks_cur[i]); + } + } + + mutex_unlock(&max8998->irqlock); +} + +static void max8998_irq_unmask(struct irq_data *data) +{ + struct max8998_dev *max8998 = irq_data_get_irq_chip_data(data); + struct max8998_irq_data *irq_data = irq_to_max8998_irq(max8998, + data->irq); + + max8998->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void max8998_irq_mask(struct irq_data *data) +{ + struct max8998_dev *max8998 = irq_data_get_irq_chip_data(data); + struct max8998_irq_data *irq_data = irq_to_max8998_irq(max8998, + data->irq); + + max8998->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static struct irq_chip max8998_irq_chip = { + .name = "max8998", + .irq_bus_lock = max8998_irq_lock, + .irq_bus_sync_unlock = max8998_irq_sync_unlock, + .irq_mask = max8998_irq_mask, + .irq_unmask = max8998_irq_unmask, +}; + +static irqreturn_t max8998_irq_thread(int irq, void *data) +{ + struct max8998_dev *max8998 = data; + u8 irq_reg[MAX8998_NUM_IRQ_REGS]; + int ret; + int i; + + ret = max8998_bulk_read(max8998->i2c, MAX8998_REG_IRQ1, + MAX8998_NUM_IRQ_REGS, irq_reg); + if (ret < 0) { + dev_err(max8998->dev, "Failed to read interrupt register: %d\n", + ret); + return IRQ_NONE; + } + + /* Apply masking */ + for (i = 0; i < MAX8998_NUM_IRQ_REGS; i++) + irq_reg[i] &= ~max8998->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < MAX8998_IRQ_NR; i++) { + if (irq_reg[max8998_irqs[i].reg - 1] & max8998_irqs[i].mask) { + irq = irq_find_mapping(max8998->irq_domain, i); + if (WARN_ON(!irq)) { + disable_irq_nosync(max8998->irq); + return IRQ_NONE; + } + handle_nested_irq(irq); + } + } + + return IRQ_HANDLED; +} + +int max8998_irq_resume(struct max8998_dev *max8998) +{ + if (max8998->irq && max8998->irq_domain) + max8998_irq_thread(max8998->irq, max8998); + return 0; +} + +static int max8998_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct max8997_dev *max8998 = d->host_data; + + irq_set_chip_data(irq, max8998); + irq_set_chip_and_handler(irq, &max8998_irq_chip, handle_edge_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + return 0; +} + +static struct irq_domain_ops max8998_irq_domain_ops = { + .map = max8998_irq_domain_map, +}; + +int max8998_irq_init(struct max8998_dev *max8998) +{ + int i; + int ret; + struct irq_domain *domain; + + if (!max8998->irq) { + dev_warn(max8998->dev, + "No interrupt specified, no interrupts\n"); + return 0; + } + + mutex_init(&max8998->irqlock); + + /* Mask the individual interrupt sources */ + for (i = 0; i < MAX8998_NUM_IRQ_REGS; i++) { + max8998->irq_masks_cur[i] = 0xff; + max8998->irq_masks_cache[i] = 0xff; + max8998_write_reg(max8998->i2c, MAX8998_REG_IRQM1 + i, 0xff); + } + + max8998_write_reg(max8998->i2c, MAX8998_REG_STATUSM1, 0xff); + max8998_write_reg(max8998->i2c, MAX8998_REG_STATUSM2, 0xff); + + domain = irq_domain_add_simple(NULL, MAX8998_IRQ_NR, + max8998->irq_base, &max8998_irq_domain_ops, max8998); + if (!domain) { + dev_err(max8998->dev, "could not create irq domain\n"); + return -ENODEV; + } + max8998->irq_domain = domain; + + ret = request_threaded_irq(max8998->irq, NULL, max8998_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max8998-irq", max8998); + if (ret) { + dev_err(max8998->dev, "Failed to request IRQ %d: %d\n", + max8998->irq, ret); + return ret; + } + + if (!max8998->ono) + return 0; + + ret = request_threaded_irq(max8998->ono, NULL, max8998_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "max8998-ono", max8998); + if (ret) + dev_err(max8998->dev, "Failed to request IRQ %d: %d\n", + max8998->ono, ret); + + return 0; +} + +void max8998_irq_exit(struct max8998_dev *max8998) +{ + if (max8998->ono) + free_irq(max8998->ono, max8998); + + if (max8998->irq) + free_irq(max8998->irq, max8998); +} diff --git a/kernel/drivers/mfd/max8998.c b/kernel/drivers/mfd/max8998.c new file mode 100644 index 000000000..a37cb7444 --- /dev/null +++ b/kernel/drivers/mfd/max8998.c @@ -0,0 +1,404 @@ +/* + * max8998.c - mfd core driver for the Maxim 8998 + * + * Copyright (C) 2009-2010 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * Marek Szyprowski <m.szyprowski@samsung.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max8998.h> +#include <linux/mfd/max8998-private.h> + +#define RTC_I2C_ADDR (0x0c >> 1) + +static const struct mfd_cell max8998_devs[] = { + { + .name = "max8998-pmic", + }, { + .name = "max8998-rtc", + }, { + .name = "max8998-battery", + }, +}; + +static const struct mfd_cell lp3974_devs[] = { + { + .name = "lp3974-pmic", + }, { + .name = "lp3974-rtc", + }, +}; + +int max8998_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8998->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&max8998->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL(max8998_read_reg); + +int max8998_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8998->iolock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max8998->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(max8998_bulk_read); + +int max8998_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8998->iolock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&max8998->iolock); + return ret; +} +EXPORT_SYMBOL(max8998_write_reg); + +int max8998_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8998->iolock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max8998->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(max8998_bulk_write); + +int max8998_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max8998->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&max8998->iolock); + return ret; +} +EXPORT_SYMBOL(max8998_update_reg); + +#ifdef CONFIG_OF +static const struct of_device_id max8998_dt_match[] = { + { .compatible = "maxim,max8998", .data = (void *)TYPE_MAX8998 }, + { .compatible = "national,lp3974", .data = (void *)TYPE_LP3974 }, + { .compatible = "ti,lp3974", .data = (void *)TYPE_LP3974 }, + {}, +}; +MODULE_DEVICE_TABLE(of, max8998_dt_match); +#endif + +/* + * Only the common platform data elements for max8998 are parsed here from the + * device tree. Other sub-modules of max8998 such as pmic, rtc and others have + * to parse their own platform data elements from device tree. + * + * The max8998 platform data structure is instantiated here and the drivers for + * the sub-modules need not instantiate another instance while parsing their + * platform data. + */ +static struct max8998_platform_data *max8998_i2c_parse_dt_pdata( + struct device *dev) +{ + struct max8998_platform_data *pd; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + pd->ono = irq_of_parse_and_map(dev->of_node, 1); + + /* + * ToDo: the 'wakeup' member in the platform data is more of a linux + * specfic information. Hence, there is no binding for that yet and + * not parsed here. + */ + return pd; +} + +static inline unsigned long max8998_i2c_get_driver_data(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + if (IS_ENABLED(CONFIG_OF) && i2c->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(max8998_dt_match, i2c->dev.of_node); + return (unsigned long)match->data; + } + + return id->driver_data; +} + +static int max8998_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max8998_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct max8998_dev *max8998; + int ret = 0; + + max8998 = devm_kzalloc(&i2c->dev, sizeof(struct max8998_dev), + GFP_KERNEL); + if (max8998 == NULL) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_OF) && i2c->dev.of_node) { + pdata = max8998_i2c_parse_dt_pdata(&i2c->dev); + if (IS_ERR(pdata)) { + ret = PTR_ERR(pdata); + goto err; + } + } + + i2c_set_clientdata(i2c, max8998); + max8998->dev = &i2c->dev; + max8998->i2c = i2c; + max8998->irq = i2c->irq; + max8998->type = max8998_i2c_get_driver_data(i2c, id); + max8998->pdata = pdata; + if (pdata) { + max8998->ono = pdata->ono; + max8998->irq_base = pdata->irq_base; + max8998->wakeup = pdata->wakeup; + } + mutex_init(&max8998->iolock); + + max8998->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR); + if (!max8998->rtc) { + dev_err(&i2c->dev, "Failed to allocate I2C device for RTC\n"); + return -ENODEV; + } + i2c_set_clientdata(max8998->rtc, max8998); + + max8998_irq_init(max8998); + + pm_runtime_set_active(max8998->dev); + + switch (max8998->type) { + case TYPE_LP3974: + ret = mfd_add_devices(max8998->dev, -1, + lp3974_devs, ARRAY_SIZE(lp3974_devs), + NULL, 0, NULL); + break; + case TYPE_MAX8998: + ret = mfd_add_devices(max8998->dev, -1, + max8998_devs, ARRAY_SIZE(max8998_devs), + NULL, 0, NULL); + break; + default: + ret = -EINVAL; + } + + if (ret < 0) + goto err; + + device_init_wakeup(max8998->dev, max8998->wakeup); + + return ret; + +err: + mfd_remove_devices(max8998->dev); + max8998_irq_exit(max8998); + i2c_unregister_device(max8998->rtc); + return ret; +} + +static int max8998_i2c_remove(struct i2c_client *i2c) +{ + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max8998->dev); + max8998_irq_exit(max8998); + i2c_unregister_device(max8998->rtc); + + return 0; +} + +static const struct i2c_device_id max8998_i2c_id[] = { + { "max8998", TYPE_MAX8998 }, + { "lp3974", TYPE_LP3974}, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8998_i2c_id); + +static int max8998_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + irq_set_irq_wake(max8998->irq, 1); + return 0; +} + +static int max8998_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max8998_dev *max8998 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + irq_set_irq_wake(max8998->irq, 0); + /* + * In LP3974, if IRQ registers are not "read & clear" + * when it's set during sleep, the interrupt becomes + * disabled. + */ + return max8998_irq_resume(i2c_get_clientdata(i2c)); +} + +struct max8998_reg_dump { + u8 addr; + u8 val; +}; +#define SAVE_ITEM(x) { .addr = (x), .val = 0x0, } +static struct max8998_reg_dump max8998_dump[] = { + SAVE_ITEM(MAX8998_REG_IRQM1), + SAVE_ITEM(MAX8998_REG_IRQM2), + SAVE_ITEM(MAX8998_REG_IRQM3), + SAVE_ITEM(MAX8998_REG_IRQM4), + SAVE_ITEM(MAX8998_REG_STATUSM1), + SAVE_ITEM(MAX8998_REG_STATUSM2), + SAVE_ITEM(MAX8998_REG_CHGR1), + SAVE_ITEM(MAX8998_REG_CHGR2), + SAVE_ITEM(MAX8998_REG_LDO_ACTIVE_DISCHARGE1), + SAVE_ITEM(MAX8998_REG_LDO_ACTIVE_DISCHARGE1), + SAVE_ITEM(MAX8998_REG_BUCK_ACTIVE_DISCHARGE3), + SAVE_ITEM(MAX8998_REG_ONOFF1), + SAVE_ITEM(MAX8998_REG_ONOFF2), + SAVE_ITEM(MAX8998_REG_ONOFF3), + SAVE_ITEM(MAX8998_REG_ONOFF4), + SAVE_ITEM(MAX8998_REG_BUCK1_VOLTAGE1), + SAVE_ITEM(MAX8998_REG_BUCK1_VOLTAGE2), + SAVE_ITEM(MAX8998_REG_BUCK1_VOLTAGE3), + SAVE_ITEM(MAX8998_REG_BUCK1_VOLTAGE4), + SAVE_ITEM(MAX8998_REG_BUCK2_VOLTAGE1), + SAVE_ITEM(MAX8998_REG_BUCK2_VOLTAGE2), + SAVE_ITEM(MAX8998_REG_LDO2_LDO3), + SAVE_ITEM(MAX8998_REG_LDO4), + SAVE_ITEM(MAX8998_REG_LDO5), + SAVE_ITEM(MAX8998_REG_LDO6), + SAVE_ITEM(MAX8998_REG_LDO7), + SAVE_ITEM(MAX8998_REG_LDO8_LDO9), + SAVE_ITEM(MAX8998_REG_LDO10_LDO11), + SAVE_ITEM(MAX8998_REG_LDO12), + SAVE_ITEM(MAX8998_REG_LDO13), + SAVE_ITEM(MAX8998_REG_LDO14), + SAVE_ITEM(MAX8998_REG_LDO15), + SAVE_ITEM(MAX8998_REG_LDO16), + SAVE_ITEM(MAX8998_REG_LDO17), + SAVE_ITEM(MAX8998_REG_BKCHR), + SAVE_ITEM(MAX8998_REG_LBCNFG1), + SAVE_ITEM(MAX8998_REG_LBCNFG2), +}; +/* Save registers before hibernation */ +static int max8998_freeze(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + int i; + + for (i = 0; i < ARRAY_SIZE(max8998_dump); i++) + max8998_read_reg(i2c, max8998_dump[i].addr, + &max8998_dump[i].val); + + return 0; +} + +/* Restore registers after hibernation */ +static int max8998_restore(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + int i; + + for (i = 0; i < ARRAY_SIZE(max8998_dump); i++) + max8998_write_reg(i2c, max8998_dump[i].addr, + max8998_dump[i].val); + + return 0; +} + +static const struct dev_pm_ops max8998_pm = { + .suspend = max8998_suspend, + .resume = max8998_resume, + .freeze = max8998_freeze, + .restore = max8998_restore, +}; + +static struct i2c_driver max8998_i2c_driver = { + .driver = { + .name = "max8998", + .owner = THIS_MODULE, + .pm = &max8998_pm, + .of_match_table = of_match_ptr(max8998_dt_match), + }, + .probe = max8998_i2c_probe, + .remove = max8998_i2c_remove, + .id_table = max8998_i2c_id, +}; + +static int __init max8998_i2c_init(void) +{ + return i2c_add_driver(&max8998_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max8998_i2c_init); + +static void __exit max8998_i2c_exit(void) +{ + i2c_del_driver(&max8998_i2c_driver); +} +module_exit(max8998_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 8998 multi-function core driver"); +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/mc13xxx-core.c b/kernel/drivers/mfd/mc13xxx-core.c new file mode 100644 index 000000000..25fd71164 --- /dev/null +++ b/kernel/drivers/mfd/mc13xxx-core.c @@ -0,0 +1,500 @@ +/* + * Copyright 2009-2010 Pengutronix + * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de> + * + * loosely based on an earlier driver that has + * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> + +#include "mc13xxx.h" + +#define MC13XXX_IRQSTAT0 0 +#define MC13XXX_IRQMASK0 1 +#define MC13XXX_IRQSTAT1 3 +#define MC13XXX_IRQMASK1 4 + +#define MC13XXX_REVISION 7 +#define MC13XXX_REVISION_REVMETAL (0x07 << 0) +#define MC13XXX_REVISION_REVFULL (0x03 << 3) +#define MC13XXX_REVISION_ICID (0x07 << 6) +#define MC13XXX_REVISION_FIN (0x03 << 9) +#define MC13XXX_REVISION_FAB (0x03 << 11) +#define MC13XXX_REVISION_ICIDCODE (0x3f << 13) + +#define MC34708_REVISION_REVMETAL (0x07 << 0) +#define MC34708_REVISION_REVFULL (0x07 << 3) +#define MC34708_REVISION_FIN (0x07 << 6) +#define MC34708_REVISION_FAB (0x07 << 9) + +#define MC13XXX_PWRCTRL 15 +#define MC13XXX_PWRCTRL_WDIRESET (1 << 12) + +#define MC13XXX_ADC1 44 +#define MC13XXX_ADC1_ADEN (1 << 0) +#define MC13XXX_ADC1_RAND (1 << 1) +#define MC13XXX_ADC1_ADSEL (1 << 3) +#define MC13XXX_ADC1_ASC (1 << 20) +#define MC13XXX_ADC1_ADTRIGIGN (1 << 21) + +#define MC13XXX_ADC2 45 + +void mc13xxx_lock(struct mc13xxx *mc13xxx) +{ + if (!mutex_trylock(&mc13xxx->lock)) { + dev_dbg(mc13xxx->dev, "wait for %s from %ps\n", + __func__, __builtin_return_address(0)); + + mutex_lock(&mc13xxx->lock); + } + dev_dbg(mc13xxx->dev, "%s from %ps\n", + __func__, __builtin_return_address(0)); +} +EXPORT_SYMBOL(mc13xxx_lock); + +void mc13xxx_unlock(struct mc13xxx *mc13xxx) +{ + dev_dbg(mc13xxx->dev, "%s from %ps\n", + __func__, __builtin_return_address(0)); + mutex_unlock(&mc13xxx->lock); +} +EXPORT_SYMBOL(mc13xxx_unlock); + +int mc13xxx_reg_read(struct mc13xxx *mc13xxx, unsigned int offset, u32 *val) +{ + int ret; + + ret = regmap_read(mc13xxx->regmap, offset, val); + dev_vdbg(mc13xxx->dev, "[0x%02x] -> 0x%06x\n", offset, *val); + + return ret; +} +EXPORT_SYMBOL(mc13xxx_reg_read); + +int mc13xxx_reg_write(struct mc13xxx *mc13xxx, unsigned int offset, u32 val) +{ + dev_vdbg(mc13xxx->dev, "[0x%02x] <- 0x%06x\n", offset, val); + + if (val >= BIT(24)) + return -EINVAL; + + return regmap_write(mc13xxx->regmap, offset, val); +} +EXPORT_SYMBOL(mc13xxx_reg_write); + +int mc13xxx_reg_rmw(struct mc13xxx *mc13xxx, unsigned int offset, + u32 mask, u32 val) +{ + BUG_ON(val & ~mask); + dev_vdbg(mc13xxx->dev, "[0x%02x] <- 0x%06x (mask: 0x%06x)\n", + offset, val, mask); + + return regmap_update_bits(mc13xxx->regmap, offset, mask, val); +} +EXPORT_SYMBOL(mc13xxx_reg_rmw); + +int mc13xxx_irq_mask(struct mc13xxx *mc13xxx, int irq) +{ + int virq = regmap_irq_get_virq(mc13xxx->irq_data, irq); + + disable_irq_nosync(virq); + + return 0; +} +EXPORT_SYMBOL(mc13xxx_irq_mask); + +int mc13xxx_irq_unmask(struct mc13xxx *mc13xxx, int irq) +{ + int virq = regmap_irq_get_virq(mc13xxx->irq_data, irq); + + enable_irq(virq); + + return 0; +} +EXPORT_SYMBOL(mc13xxx_irq_unmask); + +int mc13xxx_irq_status(struct mc13xxx *mc13xxx, int irq, + int *enabled, int *pending) +{ + int ret; + unsigned int offmask = irq < 24 ? MC13XXX_IRQMASK0 : MC13XXX_IRQMASK1; + unsigned int offstat = irq < 24 ? MC13XXX_IRQSTAT0 : MC13XXX_IRQSTAT1; + u32 irqbit = 1 << (irq < 24 ? irq : irq - 24); + + if (irq < 0 || irq >= ARRAY_SIZE(mc13xxx->irqs)) + return -EINVAL; + + if (enabled) { + u32 mask; + + ret = mc13xxx_reg_read(mc13xxx, offmask, &mask); + if (ret) + return ret; + + *enabled = mask & irqbit; + } + + if (pending) { + u32 stat; + + ret = mc13xxx_reg_read(mc13xxx, offstat, &stat); + if (ret) + return ret; + + *pending = stat & irqbit; + } + + return 0; +} +EXPORT_SYMBOL(mc13xxx_irq_status); + +int mc13xxx_irq_request(struct mc13xxx *mc13xxx, int irq, + irq_handler_t handler, const char *name, void *dev) +{ + int virq = regmap_irq_get_virq(mc13xxx->irq_data, irq); + + return devm_request_threaded_irq(mc13xxx->dev, virq, NULL, handler, + 0, name, dev); +} +EXPORT_SYMBOL(mc13xxx_irq_request); + +int mc13xxx_irq_free(struct mc13xxx *mc13xxx, int irq, void *dev) +{ + int virq = regmap_irq_get_virq(mc13xxx->irq_data, irq); + + devm_free_irq(mc13xxx->dev, virq, dev); + + return 0; +} +EXPORT_SYMBOL(mc13xxx_irq_free); + +#define maskval(reg, mask) (((reg) & (mask)) >> __ffs(mask)) +static void mc13xxx_print_revision(struct mc13xxx *mc13xxx, u32 revision) +{ + dev_info(mc13xxx->dev, "%s: rev: %d.%d, " + "fin: %d, fab: %d, icid: %d/%d\n", + mc13xxx->variant->name, + maskval(revision, MC13XXX_REVISION_REVFULL), + maskval(revision, MC13XXX_REVISION_REVMETAL), + maskval(revision, MC13XXX_REVISION_FIN), + maskval(revision, MC13XXX_REVISION_FAB), + maskval(revision, MC13XXX_REVISION_ICID), + maskval(revision, MC13XXX_REVISION_ICIDCODE)); +} + +static void mc34708_print_revision(struct mc13xxx *mc13xxx, u32 revision) +{ + dev_info(mc13xxx->dev, "%s: rev %d.%d, fin: %d, fab: %d\n", + mc13xxx->variant->name, + maskval(revision, MC34708_REVISION_REVFULL), + maskval(revision, MC34708_REVISION_REVMETAL), + maskval(revision, MC34708_REVISION_FIN), + maskval(revision, MC34708_REVISION_FAB)); +} + +/* These are only exported for mc13xxx-i2c and mc13xxx-spi */ +struct mc13xxx_variant mc13xxx_variant_mc13783 = { + .name = "mc13783", + .print_revision = mc13xxx_print_revision, +}; +EXPORT_SYMBOL_GPL(mc13xxx_variant_mc13783); + +struct mc13xxx_variant mc13xxx_variant_mc13892 = { + .name = "mc13892", + .print_revision = mc13xxx_print_revision, +}; +EXPORT_SYMBOL_GPL(mc13xxx_variant_mc13892); + +struct mc13xxx_variant mc13xxx_variant_mc34708 = { + .name = "mc34708", + .print_revision = mc34708_print_revision, +}; +EXPORT_SYMBOL_GPL(mc13xxx_variant_mc34708); + +static const char *mc13xxx_get_chipname(struct mc13xxx *mc13xxx) +{ + return mc13xxx->variant->name; +} + +int mc13xxx_get_flags(struct mc13xxx *mc13xxx) +{ + return mc13xxx->flags; +} +EXPORT_SYMBOL(mc13xxx_get_flags); + +#define MC13XXX_ADC1_CHAN0_SHIFT 5 +#define MC13XXX_ADC1_CHAN1_SHIFT 8 +#define MC13783_ADC1_ATO_SHIFT 11 +#define MC13783_ADC1_ATOX (1 << 19) + +struct mc13xxx_adcdone_data { + struct mc13xxx *mc13xxx; + struct completion done; +}; + +static irqreturn_t mc13xxx_handler_adcdone(int irq, void *data) +{ + struct mc13xxx_adcdone_data *adcdone_data = data; + + complete_all(&adcdone_data->done); + + return IRQ_HANDLED; +} + +#define MC13XXX_ADC_WORKING (1 << 0) + +int mc13xxx_adc_do_conversion(struct mc13xxx *mc13xxx, unsigned int mode, + unsigned int channel, u8 ato, bool atox, + unsigned int *sample) +{ + u32 adc0, adc1, old_adc0; + int i, ret; + struct mc13xxx_adcdone_data adcdone_data = { + .mc13xxx = mc13xxx, + }; + init_completion(&adcdone_data.done); + + dev_dbg(mc13xxx->dev, "%s\n", __func__); + + mc13xxx_lock(mc13xxx); + + if (mc13xxx->adcflags & MC13XXX_ADC_WORKING) { + ret = -EBUSY; + goto out; + } + + mc13xxx->adcflags |= MC13XXX_ADC_WORKING; + + mc13xxx_reg_read(mc13xxx, MC13XXX_ADC0, &old_adc0); + + adc0 = MC13XXX_ADC0_ADINC1 | MC13XXX_ADC0_ADINC2; + adc1 = MC13XXX_ADC1_ADEN | MC13XXX_ADC1_ADTRIGIGN | MC13XXX_ADC1_ASC; + + if (channel > 7) + adc1 |= MC13XXX_ADC1_ADSEL; + + switch (mode) { + case MC13XXX_ADC_MODE_TS: + adc0 |= MC13XXX_ADC0_ADREFEN | MC13XXX_ADC0_TSMOD0 | + MC13XXX_ADC0_TSMOD1; + adc1 |= 4 << MC13XXX_ADC1_CHAN1_SHIFT; + break; + + case MC13XXX_ADC_MODE_SINGLE_CHAN: + adc0 |= old_adc0 & MC13XXX_ADC0_CONFIG_MASK; + adc1 |= (channel & 0x7) << MC13XXX_ADC1_CHAN0_SHIFT; + adc1 |= MC13XXX_ADC1_RAND; + break; + + case MC13XXX_ADC_MODE_MULT_CHAN: + adc0 |= old_adc0 & MC13XXX_ADC0_CONFIG_MASK; + adc1 |= 4 << MC13XXX_ADC1_CHAN1_SHIFT; + break; + + default: + mc13xxx_unlock(mc13xxx); + return -EINVAL; + } + + adc1 |= ato << MC13783_ADC1_ATO_SHIFT; + if (atox) + adc1 |= MC13783_ADC1_ATOX; + + dev_dbg(mc13xxx->dev, "%s: request irq\n", __func__); + mc13xxx_irq_request(mc13xxx, MC13XXX_IRQ_ADCDONE, + mc13xxx_handler_adcdone, __func__, &adcdone_data); + + mc13xxx_reg_write(mc13xxx, MC13XXX_ADC0, adc0); + mc13xxx_reg_write(mc13xxx, MC13XXX_ADC1, adc1); + + mc13xxx_unlock(mc13xxx); + + ret = wait_for_completion_interruptible_timeout(&adcdone_data.done, HZ); + + if (!ret) + ret = -ETIMEDOUT; + + mc13xxx_lock(mc13xxx); + + mc13xxx_irq_free(mc13xxx, MC13XXX_IRQ_ADCDONE, &adcdone_data); + + if (ret > 0) + for (i = 0; i < 4; ++i) { + ret = mc13xxx_reg_read(mc13xxx, + MC13XXX_ADC2, &sample[i]); + if (ret) + break; + } + + if (mode == MC13XXX_ADC_MODE_TS) + /* restore TSMOD */ + mc13xxx_reg_write(mc13xxx, MC13XXX_ADC0, old_adc0); + + mc13xxx->adcflags &= ~MC13XXX_ADC_WORKING; +out: + mc13xxx_unlock(mc13xxx); + + return ret; +} +EXPORT_SYMBOL_GPL(mc13xxx_adc_do_conversion); + +static int mc13xxx_add_subdevice_pdata(struct mc13xxx *mc13xxx, + const char *format, void *pdata, size_t pdata_size) +{ + char buf[30]; + const char *name = mc13xxx_get_chipname(mc13xxx); + + struct mfd_cell cell = { + .platform_data = pdata, + .pdata_size = pdata_size, + }; + + /* there is no asnprintf in the kernel :-( */ + if (snprintf(buf, sizeof(buf), format, name) > sizeof(buf)) + return -E2BIG; + + cell.name = kmemdup(buf, strlen(buf) + 1, GFP_KERNEL); + if (!cell.name) + return -ENOMEM; + + return mfd_add_devices(mc13xxx->dev, -1, &cell, 1, NULL, 0, + regmap_irq_get_domain(mc13xxx->irq_data)); +} + +static int mc13xxx_add_subdevice(struct mc13xxx *mc13xxx, const char *format) +{ + return mc13xxx_add_subdevice_pdata(mc13xxx, format, NULL, 0); +} + +#ifdef CONFIG_OF +static int mc13xxx_probe_flags_dt(struct mc13xxx *mc13xxx) +{ + struct device_node *np = mc13xxx->dev->of_node; + + if (!np) + return -ENODEV; + + if (of_get_property(np, "fsl,mc13xxx-uses-adc", NULL)) + mc13xxx->flags |= MC13XXX_USE_ADC; + + if (of_get_property(np, "fsl,mc13xxx-uses-codec", NULL)) + mc13xxx->flags |= MC13XXX_USE_CODEC; + + if (of_get_property(np, "fsl,mc13xxx-uses-rtc", NULL)) + mc13xxx->flags |= MC13XXX_USE_RTC; + + if (of_get_property(np, "fsl,mc13xxx-uses-touch", NULL)) + mc13xxx->flags |= MC13XXX_USE_TOUCHSCREEN; + + return 0; +} +#else +static inline int mc13xxx_probe_flags_dt(struct mc13xxx *mc13xxx) +{ + return -ENODEV; +} +#endif + +int mc13xxx_common_init(struct device *dev) +{ + struct mc13xxx_platform_data *pdata = dev_get_platdata(dev); + struct mc13xxx *mc13xxx = dev_get_drvdata(dev); + u32 revision; + int i, ret; + + mc13xxx->dev = dev; + + ret = mc13xxx_reg_read(mc13xxx, MC13XXX_REVISION, &revision); + if (ret) + return ret; + + mc13xxx->variant->print_revision(mc13xxx, revision); + + ret = mc13xxx_reg_rmw(mc13xxx, MC13XXX_PWRCTRL, + MC13XXX_PWRCTRL_WDIRESET, MC13XXX_PWRCTRL_WDIRESET); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(mc13xxx->irqs); i++) { + mc13xxx->irqs[i].reg_offset = i / MC13XXX_IRQ_PER_REG; + mc13xxx->irqs[i].mask = BIT(i % MC13XXX_IRQ_PER_REG); + } + + mc13xxx->irq_chip.name = dev_name(dev); + mc13xxx->irq_chip.status_base = MC13XXX_IRQSTAT0; + mc13xxx->irq_chip.mask_base = MC13XXX_IRQMASK0; + mc13xxx->irq_chip.ack_base = MC13XXX_IRQSTAT0; + mc13xxx->irq_chip.irq_reg_stride = MC13XXX_IRQSTAT1 - MC13XXX_IRQSTAT0; + mc13xxx->irq_chip.init_ack_masked = true; + mc13xxx->irq_chip.use_ack = true; + mc13xxx->irq_chip.num_regs = MC13XXX_IRQ_REG_CNT; + mc13xxx->irq_chip.irqs = mc13xxx->irqs; + mc13xxx->irq_chip.num_irqs = ARRAY_SIZE(mc13xxx->irqs); + + ret = regmap_add_irq_chip(mc13xxx->regmap, mc13xxx->irq, IRQF_ONESHOT, + 0, &mc13xxx->irq_chip, &mc13xxx->irq_data); + if (ret) + return ret; + + mutex_init(&mc13xxx->lock); + + if (mc13xxx_probe_flags_dt(mc13xxx) < 0 && pdata) + mc13xxx->flags = pdata->flags; + + if (pdata) { + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-regulator", + &pdata->regulators, sizeof(pdata->regulators)); + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-led", + pdata->leds, sizeof(*pdata->leds)); + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-pwrbutton", + pdata->buttons, sizeof(*pdata->buttons)); + if (mc13xxx->flags & MC13XXX_USE_CODEC) + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-codec", + pdata->codec, sizeof(*pdata->codec)); + if (mc13xxx->flags & MC13XXX_USE_TOUCHSCREEN) + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-ts", + &pdata->touch, sizeof(pdata->touch)); + } else { + mc13xxx_add_subdevice(mc13xxx, "%s-regulator"); + mc13xxx_add_subdevice(mc13xxx, "%s-led"); + mc13xxx_add_subdevice(mc13xxx, "%s-pwrbutton"); + if (mc13xxx->flags & MC13XXX_USE_CODEC) + mc13xxx_add_subdevice(mc13xxx, "%s-codec"); + if (mc13xxx->flags & MC13XXX_USE_TOUCHSCREEN) + mc13xxx_add_subdevice(mc13xxx, "%s-ts"); + } + + if (mc13xxx->flags & MC13XXX_USE_ADC) + mc13xxx_add_subdevice(mc13xxx, "%s-adc"); + + if (mc13xxx->flags & MC13XXX_USE_RTC) + mc13xxx_add_subdevice(mc13xxx, "%s-rtc"); + + return 0; +} +EXPORT_SYMBOL_GPL(mc13xxx_common_init); + +int mc13xxx_common_exit(struct device *dev) +{ + struct mc13xxx *mc13xxx = dev_get_drvdata(dev); + + mfd_remove_devices(dev); + regmap_del_irq_chip(mc13xxx->irq, mc13xxx->irq_data); + mutex_destroy(&mc13xxx->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(mc13xxx_common_exit); + +MODULE_DESCRIPTION("Core driver for Freescale MC13XXX PMIC"); +MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/mc13xxx-i2c.c b/kernel/drivers/mfd/mc13xxx-i2c.c new file mode 100644 index 000000000..68b844811 --- /dev/null +++ b/kernel/drivers/mfd/mc13xxx-i2c.c @@ -0,0 +1,121 @@ +/* + * Copyright 2009-2010 Creative Product Design + * Marc Reilly marc@cpdesign.com.au + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/mc13xxx.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/i2c.h> +#include <linux/err.h> + +#include "mc13xxx.h" + +static const struct i2c_device_id mc13xxx_i2c_device_id[] = { + { + .name = "mc13892", + .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc13892, + }, { + .name = "mc34708", + .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc34708, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(i2c, mc13xxx_i2c_device_id); + +static const struct of_device_id mc13xxx_dt_ids[] = { + { + .compatible = "fsl,mc13892", + .data = &mc13xxx_variant_mc13892, + }, { + .compatible = "fsl,mc34708", + .data = &mc13xxx_variant_mc34708, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, mc13xxx_dt_ids); + +static const struct regmap_config mc13xxx_regmap_i2c_config = { + .reg_bits = 8, + .val_bits = 24, + + .max_register = MC13XXX_NUMREGS, + + .cache_type = REGCACHE_NONE, +}; + +static int mc13xxx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mc13xxx *mc13xxx; + int ret; + + mc13xxx = devm_kzalloc(&client->dev, sizeof(*mc13xxx), GFP_KERNEL); + if (!mc13xxx) + return -ENOMEM; + + dev_set_drvdata(&client->dev, mc13xxx); + + mc13xxx->irq = client->irq; + + mc13xxx->regmap = devm_regmap_init_i2c(client, + &mc13xxx_regmap_i2c_config); + if (IS_ERR(mc13xxx->regmap)) { + ret = PTR_ERR(mc13xxx->regmap); + dev_err(&client->dev, "Failed to initialize regmap: %d\n", ret); + return ret; + } + + if (client->dev.of_node) { + const struct of_device_id *of_id = + of_match_device(mc13xxx_dt_ids, &client->dev); + mc13xxx->variant = of_id->data; + } else { + mc13xxx->variant = (void *)id->driver_data; + } + + return mc13xxx_common_init(&client->dev); +} + +static int mc13xxx_i2c_remove(struct i2c_client *client) +{ + return mc13xxx_common_exit(&client->dev); +} + +static struct i2c_driver mc13xxx_i2c_driver = { + .id_table = mc13xxx_i2c_device_id, + .driver = { + .owner = THIS_MODULE, + .name = "mc13xxx", + .of_match_table = mc13xxx_dt_ids, + }, + .probe = mc13xxx_i2c_probe, + .remove = mc13xxx_i2c_remove, +}; + +static int __init mc13xxx_i2c_init(void) +{ + return i2c_add_driver(&mc13xxx_i2c_driver); +} +subsys_initcall(mc13xxx_i2c_init); + +static void __exit mc13xxx_i2c_exit(void) +{ + i2c_del_driver(&mc13xxx_i2c_driver); +} +module_exit(mc13xxx_i2c_exit); + +MODULE_DESCRIPTION("i2c driver for Freescale MC13XXX PMIC"); +MODULE_AUTHOR("Marc Reilly <marc@cpdesign.com.au"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/mc13xxx-spi.c b/kernel/drivers/mfd/mc13xxx-spi.c new file mode 100644 index 000000000..58a170e45 --- /dev/null +++ b/kernel/drivers/mfd/mc13xxx-spi.c @@ -0,0 +1,201 @@ +/* + * Copyright 2009-2010 Pengutronix + * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de> + * + * loosely based on an earlier driver that has + * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> + * + * 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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/mfd/core.h> +#include <linux/mfd/mc13xxx.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/err.h> +#include <linux/spi/spi.h> + +#include "mc13xxx.h" + +static const struct spi_device_id mc13xxx_device_id[] = { + { + .name = "mc13783", + .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc13783, + }, { + .name = "mc13892", + .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc13892, + }, { + .name = "mc34708", + .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc34708, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(spi, mc13xxx_device_id); + +static const struct of_device_id mc13xxx_dt_ids[] = { + { .compatible = "fsl,mc13783", .data = &mc13xxx_variant_mc13783, }, + { .compatible = "fsl,mc13892", .data = &mc13xxx_variant_mc13892, }, + { .compatible = "fsl,mc34708", .data = &mc13xxx_variant_mc34708, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mc13xxx_dt_ids); + +static const struct regmap_config mc13xxx_regmap_spi_config = { + .reg_bits = 7, + .pad_bits = 1, + .val_bits = 24, + .write_flag_mask = 0x80, + + .max_register = MC13XXX_NUMREGS, + + .cache_type = REGCACHE_NONE, + .use_single_rw = 1, +}; + +static int mc13xxx_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + unsigned char w[4] = { *((unsigned char *) reg), 0, 0, 0}; + unsigned char r[4]; + unsigned char *p = val; + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + struct spi_transfer t = { + .tx_buf = w, + .rx_buf = r, + .len = 4, + }; + + struct spi_message m; + int ret; + + if (val_size != 3 || reg_size != 1) + return -ENOTSUPP; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + memcpy(p, &r[1], 3); + + return ret; +} + +static int mc13xxx_spi_write(void *context, const void *data, size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + const char *reg = data; + + if (count != 4) + return -ENOTSUPP; + + /* include errata fix for spi audio problems */ + if (*reg == MC13783_AUDIO_CODEC || *reg == MC13783_AUDIO_DAC) + spi_write(spi, data, count); + + return spi_write(spi, data, count); +} + +/* + * We cannot use regmap-spi generic bus implementation here. + * The MC13783 chip will get corrupted if CS signal is deasserted + * and on i.Mx31 SoC (the target SoC for MC13783 PMIC) the SPI controller + * has the following errata (DSPhl22960): + * "The CSPI negates SS when the FIFO becomes empty with + * SSCTL= 0. Software cannot guarantee that the FIFO will not + * drain because of higher priority interrupts and the + * non-realtime characteristics of the operating system. As a + * result, the SS will negate before all of the data has been + * transferred to/from the peripheral." + * We workaround this by accessing the SPI controller with a + * single transfert. + */ + +static struct regmap_bus regmap_mc13xxx_bus = { + .write = mc13xxx_spi_write, + .read = mc13xxx_spi_read, +}; + +static int mc13xxx_spi_probe(struct spi_device *spi) +{ + struct mc13xxx *mc13xxx; + int ret; + + mc13xxx = devm_kzalloc(&spi->dev, sizeof(*mc13xxx), GFP_KERNEL); + if (!mc13xxx) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, mc13xxx); + + spi->mode = SPI_MODE_0 | SPI_CS_HIGH; + + mc13xxx->irq = spi->irq; + + spi->max_speed_hz = spi->max_speed_hz ? : 26000000; + ret = spi_setup(spi); + if (ret) + return ret; + + mc13xxx->regmap = devm_regmap_init(&spi->dev, ®map_mc13xxx_bus, + &spi->dev, + &mc13xxx_regmap_spi_config); + if (IS_ERR(mc13xxx->regmap)) { + ret = PTR_ERR(mc13xxx->regmap); + dev_err(&spi->dev, "Failed to initialize regmap: %d\n", ret); + return ret; + } + + if (spi->dev.of_node) { + const struct of_device_id *of_id = + of_match_device(mc13xxx_dt_ids, &spi->dev); + + mc13xxx->variant = of_id->data; + } else { + const struct spi_device_id *id_entry = spi_get_device_id(spi); + + mc13xxx->variant = (void *)id_entry->driver_data; + } + + return mc13xxx_common_init(&spi->dev); +} + +static int mc13xxx_spi_remove(struct spi_device *spi) +{ + return mc13xxx_common_exit(&spi->dev); +} + +static struct spi_driver mc13xxx_spi_driver = { + .id_table = mc13xxx_device_id, + .driver = { + .name = "mc13xxx", + .owner = THIS_MODULE, + .of_match_table = mc13xxx_dt_ids, + }, + .probe = mc13xxx_spi_probe, + .remove = mc13xxx_spi_remove, +}; + +static int __init mc13xxx_init(void) +{ + return spi_register_driver(&mc13xxx_spi_driver); +} +subsys_initcall(mc13xxx_init); + +static void __exit mc13xxx_exit(void) +{ + spi_unregister_driver(&mc13xxx_spi_driver); +} +module_exit(mc13xxx_exit); + +MODULE_DESCRIPTION("Core driver for Freescale MC13XXX PMIC"); +MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/mc13xxx.h b/kernel/drivers/mfd/mc13xxx.h new file mode 100644 index 000000000..33677d1dc --- /dev/null +++ b/kernel/drivers/mfd/mc13xxx.h @@ -0,0 +1,52 @@ +/* + * Copyright 2012 Creative Product Design + * Marc Reilly <marc@cpdesign.com.au> + * + * 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. + */ +#ifndef __DRIVERS_MFD_MC13XXX_H +#define __DRIVERS_MFD_MC13XXX_H + +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/mfd/mc13xxx.h> + +#define MC13XXX_NUMREGS 0x3f +#define MC13XXX_IRQ_REG_CNT 2 +#define MC13XXX_IRQ_PER_REG 24 + +struct mc13xxx; + +struct mc13xxx_variant { + const char *name; + void (*print_revision)(struct mc13xxx *mc13xxx, u32 revision); +}; + +extern struct mc13xxx_variant + mc13xxx_variant_mc13783, + mc13xxx_variant_mc13892, + mc13xxx_variant_mc34708; + +struct mc13xxx { + struct regmap *regmap; + + struct device *dev; + const struct mc13xxx_variant *variant; + + struct regmap_irq irqs[MC13XXX_IRQ_PER_REG * MC13XXX_IRQ_REG_CNT]; + struct regmap_irq_chip irq_chip; + struct regmap_irq_chip_data *irq_data; + + struct mutex lock; + int irq; + int flags; + + int adcflags; +}; + +int mc13xxx_common_init(struct device *dev); +int mc13xxx_common_exit(struct device *dev); + +#endif /* __DRIVERS_MFD_MC13XXX_H */ diff --git a/kernel/drivers/mfd/mcp-core.c b/kernel/drivers/mfd/mcp-core.c new file mode 100644 index 000000000..7f5066e39 --- /dev/null +++ b/kernel/drivers/mfd/mcp-core.c @@ -0,0 +1,239 @@ +/* + * linux/drivers/mfd/mcp-core.c + * + * Copyright (C) 2001 Russell King + * + * 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. + * + * Generic MCP (Multimedia Communications Port) layer. All MCP locking + * is solely held within this file. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/smp.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/mfd/mcp.h> + + +#define to_mcp(d) container_of(d, struct mcp, attached_device) +#define to_mcp_driver(d) container_of(d, struct mcp_driver, drv) + +static int mcp_bus_match(struct device *dev, struct device_driver *drv) +{ + return 1; +} + +static int mcp_bus_probe(struct device *dev) +{ + struct mcp *mcp = to_mcp(dev); + struct mcp_driver *drv = to_mcp_driver(dev->driver); + + return drv->probe(mcp); +} + +static int mcp_bus_remove(struct device *dev) +{ + struct mcp *mcp = to_mcp(dev); + struct mcp_driver *drv = to_mcp_driver(dev->driver); + + drv->remove(mcp); + return 0; +} + +static struct bus_type mcp_bus_type = { + .name = "mcp", + .match = mcp_bus_match, + .probe = mcp_bus_probe, + .remove = mcp_bus_remove, +}; + +/** + * mcp_set_telecom_divisor - set the telecom divisor + * @mcp: MCP interface structure + * @div: SIB clock divisor + * + * Set the telecom divisor on the MCP interface. The resulting + * sample rate is SIBCLOCK/div. + */ +void mcp_set_telecom_divisor(struct mcp *mcp, unsigned int div) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + mcp->ops->set_telecom_divisor(mcp, div); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_set_telecom_divisor); + +/** + * mcp_set_audio_divisor - set the audio divisor + * @mcp: MCP interface structure + * @div: SIB clock divisor + * + * Set the audio divisor on the MCP interface. + */ +void mcp_set_audio_divisor(struct mcp *mcp, unsigned int div) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + mcp->ops->set_audio_divisor(mcp, div); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_set_audio_divisor); + +/** + * mcp_reg_write - write a device register + * @mcp: MCP interface structure + * @reg: 4-bit register index + * @val: 16-bit data value + * + * Write a device register. The MCP interface must be enabled + * to prevent this function hanging. + */ +void mcp_reg_write(struct mcp *mcp, unsigned int reg, unsigned int val) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + mcp->ops->reg_write(mcp, reg, val); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_reg_write); + +/** + * mcp_reg_read - read a device register + * @mcp: MCP interface structure + * @reg: 4-bit register index + * + * Read a device register and return its value. The MCP interface + * must be enabled to prevent this function hanging. + */ +unsigned int mcp_reg_read(struct mcp *mcp, unsigned int reg) +{ + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(&mcp->lock, flags); + val = mcp->ops->reg_read(mcp, reg); + spin_unlock_irqrestore(&mcp->lock, flags); + + return val; +} +EXPORT_SYMBOL(mcp_reg_read); + +/** + * mcp_enable - enable the MCP interface + * @mcp: MCP interface to enable + * + * Enable the MCP interface. Each call to mcp_enable will need + * a corresponding call to mcp_disable to disable the interface. + */ +void mcp_enable(struct mcp *mcp) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + if (mcp->use_count++ == 0) + mcp->ops->enable(mcp); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_enable); + +/** + * mcp_disable - disable the MCP interface + * @mcp: MCP interface to disable + * + * Disable the MCP interface. The MCP interface will only be + * disabled once the number of calls to mcp_enable matches the + * number of calls to mcp_disable. + */ +void mcp_disable(struct mcp *mcp) +{ + unsigned long flags; + + spin_lock_irqsave(&mcp->lock, flags); + if (--mcp->use_count == 0) + mcp->ops->disable(mcp); + spin_unlock_irqrestore(&mcp->lock, flags); +} +EXPORT_SYMBOL(mcp_disable); + +static void mcp_release(struct device *dev) +{ + struct mcp *mcp = container_of(dev, struct mcp, attached_device); + + kfree(mcp); +} + +struct mcp *mcp_host_alloc(struct device *parent, size_t size) +{ + struct mcp *mcp; + + mcp = kzalloc(sizeof(struct mcp) + size, GFP_KERNEL); + if (mcp) { + spin_lock_init(&mcp->lock); + device_initialize(&mcp->attached_device); + mcp->attached_device.parent = parent; + mcp->attached_device.bus = &mcp_bus_type; + mcp->attached_device.dma_mask = parent->dma_mask; + mcp->attached_device.release = mcp_release; + } + return mcp; +} +EXPORT_SYMBOL(mcp_host_alloc); + +int mcp_host_add(struct mcp *mcp, void *pdata) +{ + mcp->attached_device.platform_data = pdata; + dev_set_name(&mcp->attached_device, "mcp0"); + return device_add(&mcp->attached_device); +} +EXPORT_SYMBOL(mcp_host_add); + +void mcp_host_del(struct mcp *mcp) +{ + device_del(&mcp->attached_device); +} +EXPORT_SYMBOL(mcp_host_del); + +void mcp_host_free(struct mcp *mcp) +{ + put_device(&mcp->attached_device); +} +EXPORT_SYMBOL(mcp_host_free); + +int mcp_driver_register(struct mcp_driver *mcpdrv) +{ + mcpdrv->drv.bus = &mcp_bus_type; + return driver_register(&mcpdrv->drv); +} +EXPORT_SYMBOL(mcp_driver_register); + +void mcp_driver_unregister(struct mcp_driver *mcpdrv) +{ + driver_unregister(&mcpdrv->drv); +} +EXPORT_SYMBOL(mcp_driver_unregister); + +static int __init mcp_init(void) +{ + return bus_register(&mcp_bus_type); +} + +static void __exit mcp_exit(void) +{ + bus_unregister(&mcp_bus_type); +} + +module_init(mcp_init); +module_exit(mcp_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("Core multimedia communications port driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/mcp-sa11x0.c b/kernel/drivers/mfd/mcp-sa11x0.c new file mode 100644 index 000000000..1fc458128 --- /dev/null +++ b/kernel/drivers/mfd/mcp-sa11x0.c @@ -0,0 +1,314 @@ +/* + * linux/drivers/mfd/mcp-sa11x0.c + * + * Copyright (C) 2001-2005 Russell King + * + * 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. + * + * SA11x0 MCP (Multimedia Communications Port) driver. + * + * MCP read/write timeouts from Jordi Colomer, rehacked by rmk. + */ +#include <linux/module.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/mfd/mcp.h> + +#include <mach/hardware.h> +#include <asm/mach-types.h> +#include <linux/platform_data/mfd-mcp-sa11x0.h> + +#define DRIVER_NAME "sa11x0-mcp" + +struct mcp_sa11x0 { + void __iomem *base0; + void __iomem *base1; + u32 mccr0; + u32 mccr1; +}; + +/* Register offsets */ +#define MCCR0(m) ((m)->base0 + 0x00) +#define MCDR0(m) ((m)->base0 + 0x08) +#define MCDR1(m) ((m)->base0 + 0x0c) +#define MCDR2(m) ((m)->base0 + 0x10) +#define MCSR(m) ((m)->base0 + 0x18) +#define MCCR1(m) ((m)->base1 + 0x00) + +#define priv(mcp) ((struct mcp_sa11x0 *)mcp_priv(mcp)) + +static void +mcp_sa11x0_set_telecom_divisor(struct mcp *mcp, unsigned int divisor) +{ + struct mcp_sa11x0 *m = priv(mcp); + + divisor /= 32; + + m->mccr0 &= ~0x00007f00; + m->mccr0 |= divisor << 8; + writel_relaxed(m->mccr0, MCCR0(m)); +} + +static void +mcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor) +{ + struct mcp_sa11x0 *m = priv(mcp); + + divisor /= 32; + + m->mccr0 &= ~0x0000007f; + m->mccr0 |= divisor; + writel_relaxed(m->mccr0, MCCR0(m)); +} + +/* + * Write data to the device. The bit should be set after 3 subframe + * times (each frame is 64 clocks). We wait a maximum of 6 subframes. + * We really should try doing something more productive while we + * wait. + */ +static void +mcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val) +{ + struct mcp_sa11x0 *m = priv(mcp); + int ret = -ETIME; + int i; + + writel_relaxed(reg << 17 | MCDR2_Wr | (val & 0xffff), MCDR2(m)); + + for (i = 0; i < 2; i++) { + udelay(mcp->rw_timeout); + if (readl_relaxed(MCSR(m)) & MCSR_CWC) { + ret = 0; + break; + } + } + + if (ret < 0) + printk(KERN_WARNING "mcp: write timed out\n"); +} + +/* + * Read data from the device. The bit should be set after 3 subframe + * times (each frame is 64 clocks). We wait a maximum of 6 subframes. + * We really should try doing something more productive while we + * wait. + */ +static unsigned int +mcp_sa11x0_read(struct mcp *mcp, unsigned int reg) +{ + struct mcp_sa11x0 *m = priv(mcp); + int ret = -ETIME; + int i; + + writel_relaxed(reg << 17 | MCDR2_Rd, MCDR2(m)); + + for (i = 0; i < 2; i++) { + udelay(mcp->rw_timeout); + if (readl_relaxed(MCSR(m)) & MCSR_CRC) { + ret = readl_relaxed(MCDR2(m)) & 0xffff; + break; + } + } + + if (ret < 0) + printk(KERN_WARNING "mcp: read timed out\n"); + + return ret; +} + +static void mcp_sa11x0_enable(struct mcp *mcp) +{ + struct mcp_sa11x0 *m = priv(mcp); + + writel(-1, MCSR(m)); + m->mccr0 |= MCCR0_MCE; + writel_relaxed(m->mccr0, MCCR0(m)); +} + +static void mcp_sa11x0_disable(struct mcp *mcp) +{ + struct mcp_sa11x0 *m = priv(mcp); + + m->mccr0 &= ~MCCR0_MCE; + writel_relaxed(m->mccr0, MCCR0(m)); +} + +/* + * Our methods. + */ +static struct mcp_ops mcp_sa11x0 = { + .set_telecom_divisor = mcp_sa11x0_set_telecom_divisor, + .set_audio_divisor = mcp_sa11x0_set_audio_divisor, + .reg_write = mcp_sa11x0_write, + .reg_read = mcp_sa11x0_read, + .enable = mcp_sa11x0_enable, + .disable = mcp_sa11x0_disable, +}; + +static int mcp_sa11x0_probe(struct platform_device *dev) +{ + struct mcp_plat_data *data = dev_get_platdata(&dev->dev); + struct resource *mem0, *mem1; + struct mcp_sa11x0 *m; + struct mcp *mcp; + int ret; + + if (!data) + return -ENODEV; + + mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0); + mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1); + if (!mem0 || !mem1) + return -ENXIO; + + if (!request_mem_region(mem0->start, resource_size(mem0), + DRIVER_NAME)) { + ret = -EBUSY; + goto err_mem0; + } + + if (!request_mem_region(mem1->start, resource_size(mem1), + DRIVER_NAME)) { + ret = -EBUSY; + goto err_mem1; + } + + mcp = mcp_host_alloc(&dev->dev, sizeof(struct mcp_sa11x0)); + if (!mcp) { + ret = -ENOMEM; + goto err_alloc; + } + + mcp->owner = THIS_MODULE; + mcp->ops = &mcp_sa11x0; + mcp->sclk_rate = data->sclk_rate; + + m = priv(mcp); + m->mccr0 = data->mccr0 | 0x7f7f; + m->mccr1 = data->mccr1; + + m->base0 = ioremap(mem0->start, resource_size(mem0)); + m->base1 = ioremap(mem1->start, resource_size(mem1)); + if (!m->base0 || !m->base1) { + ret = -ENOMEM; + goto err_ioremap; + } + + platform_set_drvdata(dev, mcp); + + /* + * Initialise device. Note that we initially + * set the sampling rate to minimum. + */ + writel_relaxed(-1, MCSR(m)); + writel_relaxed(m->mccr1, MCCR1(m)); + writel_relaxed(m->mccr0, MCCR0(m)); + + /* + * Calculate the read/write timeout (us) from the bit clock + * rate. This is the period for 3 64-bit frames. Always + * round this time up. + */ + mcp->rw_timeout = (64 * 3 * 1000000 + mcp->sclk_rate - 1) / + mcp->sclk_rate; + + ret = mcp_host_add(mcp, data->codec_pdata); + if (ret == 0) + return 0; + + err_ioremap: + iounmap(m->base1); + iounmap(m->base0); + mcp_host_free(mcp); + err_alloc: + release_mem_region(mem1->start, resource_size(mem1)); + err_mem1: + release_mem_region(mem0->start, resource_size(mem0)); + err_mem0: + return ret; +} + +static int mcp_sa11x0_remove(struct platform_device *dev) +{ + struct mcp *mcp = platform_get_drvdata(dev); + struct mcp_sa11x0 *m = priv(mcp); + struct resource *mem0, *mem1; + + if (m->mccr0 & MCCR0_MCE) + dev_warn(&dev->dev, + "device left active (missing disable call?)\n"); + + mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0); + mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1); + + mcp_host_del(mcp); + iounmap(m->base1); + iounmap(m->base0); + mcp_host_free(mcp); + release_mem_region(mem1->start, resource_size(mem1)); + release_mem_region(mem0->start, resource_size(mem0)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mcp_sa11x0_suspend(struct device *dev) +{ + struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev)); + + if (m->mccr0 & MCCR0_MCE) + dev_warn(dev, "device left active (missing disable call?)\n"); + + writel(m->mccr0 & ~MCCR0_MCE, MCCR0(m)); + + return 0; +} + +static int mcp_sa11x0_resume(struct device *dev) +{ + struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev)); + + writel_relaxed(m->mccr1, MCCR1(m)); + writel_relaxed(m->mccr0, MCCR0(m)); + + return 0; +} +#endif + +static const struct dev_pm_ops mcp_sa11x0_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = mcp_sa11x0_suspend, + .freeze = mcp_sa11x0_suspend, + .poweroff = mcp_sa11x0_suspend, + .resume_noirq = mcp_sa11x0_resume, + .thaw_noirq = mcp_sa11x0_resume, + .restore_noirq = mcp_sa11x0_resume, +#endif +}; + +static struct platform_driver mcp_sa11x0_driver = { + .probe = mcp_sa11x0_probe, + .remove = mcp_sa11x0_remove, + .driver = { + .name = DRIVER_NAME, + .pm = &mcp_sa11x0_pm_ops, + }, +}; + +/* + * This needs re-working + */ +module_platform_driver(mcp_sa11x0_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("SA11x0 multimedia communications port driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/menelaus.c b/kernel/drivers/mfd/menelaus.c new file mode 100644 index 000000000..3ac36f5cc --- /dev/null +++ b/kernel/drivers/mfd/menelaus.c @@ -0,0 +1,1268 @@ +/* + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Some parts based tps65010.c: + * Copyright (C) 2004 Texas Instruments and + * Copyright (C) 2004-2005 David Brownell + * + * Some parts based on tlv320aic24.c: + * Copyright (C) by Kai Svahn <kai.svahn@nokia.com> + * + * Changes for interrupt handling and clean-up by + * Tony Lindgren <tony@atomide.com> and Imre Deak <imre.deak@nokia.com> + * Cleanup and generalized support for voltage setting by + * Juha Yrjola + * Added support for controlling VCORE and regulator sleep states, + * Amit Kucheria <amit.kucheria@nokia.com> + * Copyright (C) 2005, 2006 Nokia Corporation + * + * 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/slab.h> +#include <linux/mfd/menelaus.h> + +#include <asm/mach/irq.h> + +#include <asm/gpio.h> + +#define DRIVER_NAME "menelaus" + +#define MENELAUS_I2C_ADDRESS 0x72 + +#define MENELAUS_REV 0x01 +#define MENELAUS_VCORE_CTRL1 0x02 +#define MENELAUS_VCORE_CTRL2 0x03 +#define MENELAUS_VCORE_CTRL3 0x04 +#define MENELAUS_VCORE_CTRL4 0x05 +#define MENELAUS_VCORE_CTRL5 0x06 +#define MENELAUS_DCDC_CTRL1 0x07 +#define MENELAUS_DCDC_CTRL2 0x08 +#define MENELAUS_DCDC_CTRL3 0x09 +#define MENELAUS_LDO_CTRL1 0x0A +#define MENELAUS_LDO_CTRL2 0x0B +#define MENELAUS_LDO_CTRL3 0x0C +#define MENELAUS_LDO_CTRL4 0x0D +#define MENELAUS_LDO_CTRL5 0x0E +#define MENELAUS_LDO_CTRL6 0x0F +#define MENELAUS_LDO_CTRL7 0x10 +#define MENELAUS_LDO_CTRL8 0x11 +#define MENELAUS_SLEEP_CTRL1 0x12 +#define MENELAUS_SLEEP_CTRL2 0x13 +#define MENELAUS_DEVICE_OFF 0x14 +#define MENELAUS_OSC_CTRL 0x15 +#define MENELAUS_DETECT_CTRL 0x16 +#define MENELAUS_INT_MASK1 0x17 +#define MENELAUS_INT_MASK2 0x18 +#define MENELAUS_INT_STATUS1 0x19 +#define MENELAUS_INT_STATUS2 0x1A +#define MENELAUS_INT_ACK1 0x1B +#define MENELAUS_INT_ACK2 0x1C +#define MENELAUS_GPIO_CTRL 0x1D +#define MENELAUS_GPIO_IN 0x1E +#define MENELAUS_GPIO_OUT 0x1F +#define MENELAUS_BBSMS 0x20 +#define MENELAUS_RTC_CTRL 0x21 +#define MENELAUS_RTC_UPDATE 0x22 +#define MENELAUS_RTC_SEC 0x23 +#define MENELAUS_RTC_MIN 0x24 +#define MENELAUS_RTC_HR 0x25 +#define MENELAUS_RTC_DAY 0x26 +#define MENELAUS_RTC_MON 0x27 +#define MENELAUS_RTC_YR 0x28 +#define MENELAUS_RTC_WKDAY 0x29 +#define MENELAUS_RTC_AL_SEC 0x2A +#define MENELAUS_RTC_AL_MIN 0x2B +#define MENELAUS_RTC_AL_HR 0x2C +#define MENELAUS_RTC_AL_DAY 0x2D +#define MENELAUS_RTC_AL_MON 0x2E +#define MENELAUS_RTC_AL_YR 0x2F +#define MENELAUS_RTC_COMP_MSB 0x30 +#define MENELAUS_RTC_COMP_LSB 0x31 +#define MENELAUS_S1_PULL_EN 0x32 +#define MENELAUS_S1_PULL_DIR 0x33 +#define MENELAUS_S2_PULL_EN 0x34 +#define MENELAUS_S2_PULL_DIR 0x35 +#define MENELAUS_MCT_CTRL1 0x36 +#define MENELAUS_MCT_CTRL2 0x37 +#define MENELAUS_MCT_CTRL3 0x38 +#define MENELAUS_MCT_PIN_ST 0x39 +#define MENELAUS_DEBOUNCE1 0x3A + +#define IH_MENELAUS_IRQS 12 +#define MENELAUS_MMC_S1CD_IRQ 0 /* MMC slot 1 card change */ +#define MENELAUS_MMC_S2CD_IRQ 1 /* MMC slot 2 card change */ +#define MENELAUS_MMC_S1D1_IRQ 2 /* MMC DAT1 low in slot 1 */ +#define MENELAUS_MMC_S2D1_IRQ 3 /* MMC DAT1 low in slot 2 */ +#define MENELAUS_LOWBAT_IRQ 4 /* Low battery */ +#define MENELAUS_HOTDIE_IRQ 5 /* Hot die detect */ +#define MENELAUS_UVLO_IRQ 6 /* UVLO detect */ +#define MENELAUS_TSHUT_IRQ 7 /* Thermal shutdown */ +#define MENELAUS_RTCTMR_IRQ 8 /* RTC timer */ +#define MENELAUS_RTCALM_IRQ 9 /* RTC alarm */ +#define MENELAUS_RTCERR_IRQ 10 /* RTC error */ +#define MENELAUS_PSHBTN_IRQ 11 /* Push button */ +#define MENELAUS_RESERVED12_IRQ 12 /* Reserved */ +#define MENELAUS_RESERVED13_IRQ 13 /* Reserved */ +#define MENELAUS_RESERVED14_IRQ 14 /* Reserved */ +#define MENELAUS_RESERVED15_IRQ 15 /* Reserved */ + +/* VCORE_CTRL1 register */ +#define VCORE_CTRL1_BYP_COMP (1 << 5) +#define VCORE_CTRL1_HW_NSW (1 << 7) + +/* GPIO_CTRL register */ +#define GPIO_CTRL_SLOTSELEN (1 << 5) +#define GPIO_CTRL_SLPCTLEN (1 << 6) +#define GPIO1_DIR_INPUT (1 << 0) +#define GPIO2_DIR_INPUT (1 << 1) +#define GPIO3_DIR_INPUT (1 << 2) + +/* MCT_CTRL1 register */ +#define MCT_CTRL1_S1_CMD_OD (1 << 2) +#define MCT_CTRL1_S2_CMD_OD (1 << 3) + +/* MCT_CTRL2 register */ +#define MCT_CTRL2_VS2_SEL_D0 (1 << 0) +#define MCT_CTRL2_VS2_SEL_D1 (1 << 1) +#define MCT_CTRL2_S1CD_BUFEN (1 << 4) +#define MCT_CTRL2_S2CD_BUFEN (1 << 5) +#define MCT_CTRL2_S1CD_DBEN (1 << 6) +#define MCT_CTRL2_S2CD_BEN (1 << 7) + +/* MCT_CTRL3 register */ +#define MCT_CTRL3_SLOT1_EN (1 << 0) +#define MCT_CTRL3_SLOT2_EN (1 << 1) +#define MCT_CTRL3_S1_AUTO_EN (1 << 2) +#define MCT_CTRL3_S2_AUTO_EN (1 << 3) + +/* MCT_PIN_ST register */ +#define MCT_PIN_ST_S1_CD_ST (1 << 0) +#define MCT_PIN_ST_S2_CD_ST (1 << 1) + +static void menelaus_work(struct work_struct *_menelaus); + +struct menelaus_chip { + struct mutex lock; + struct i2c_client *client; + struct work_struct work; +#ifdef CONFIG_RTC_DRV_TWL92330 + struct rtc_device *rtc; + u8 rtc_control; + unsigned uie:1; +#endif + unsigned vcore_hw_mode:1; + u8 mask1, mask2; + void (*handlers[16])(struct menelaus_chip *); + void (*mmc_callback)(void *data, u8 mask); + void *mmc_callback_data; +}; + +static struct menelaus_chip *the_menelaus; + +static int menelaus_write_reg(int reg, u8 value) +{ + int val = i2c_smbus_write_byte_data(the_menelaus->client, reg, value); + + if (val < 0) { + pr_err(DRIVER_NAME ": write error"); + return val; + } + + return 0; +} + +static int menelaus_read_reg(int reg) +{ + int val = i2c_smbus_read_byte_data(the_menelaus->client, reg); + + if (val < 0) + pr_err(DRIVER_NAME ": read error"); + + return val; +} + +static int menelaus_enable_irq(int irq) +{ + if (irq > 7) { + irq -= 8; + the_menelaus->mask2 &= ~(1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK2, + the_menelaus->mask2); + } else { + the_menelaus->mask1 &= ~(1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK1, + the_menelaus->mask1); + } +} + +static int menelaus_disable_irq(int irq) +{ + if (irq > 7) { + irq -= 8; + the_menelaus->mask2 |= (1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK2, + the_menelaus->mask2); + } else { + the_menelaus->mask1 |= (1 << irq); + return menelaus_write_reg(MENELAUS_INT_MASK1, + the_menelaus->mask1); + } +} + +static int menelaus_ack_irq(int irq) +{ + if (irq > 7) + return menelaus_write_reg(MENELAUS_INT_ACK2, 1 << (irq - 8)); + else + return menelaus_write_reg(MENELAUS_INT_ACK1, 1 << irq); +} + +/* Adds a handler for an interrupt. Does not run in interrupt context */ +static int menelaus_add_irq_work(int irq, + void (*handler)(struct menelaus_chip *)) +{ + int ret = 0; + + mutex_lock(&the_menelaus->lock); + the_menelaus->handlers[irq] = handler; + ret = menelaus_enable_irq(irq); + mutex_unlock(&the_menelaus->lock); + + return ret; +} + +/* Removes handler for an interrupt */ +static int menelaus_remove_irq_work(int irq) +{ + int ret = 0; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_disable_irq(irq); + the_menelaus->handlers[irq] = NULL; + mutex_unlock(&the_menelaus->lock); + + return ret; +} + +/* + * Gets scheduled when a card detect interrupt happens. Note that in some cases + * this line is wired to card cover switch rather than the card detect switch + * in each slot. In this case the cards are not seen by menelaus. + * FIXME: Add handling for D1 too + */ +static void menelaus_mmc_cd_work(struct menelaus_chip *menelaus_hw) +{ + int reg; + unsigned char card_mask = 0; + + reg = menelaus_read_reg(MENELAUS_MCT_PIN_ST); + if (reg < 0) + return; + + if (!(reg & 0x1)) + card_mask |= MCT_PIN_ST_S1_CD_ST; + + if (!(reg & 0x2)) + card_mask |= MCT_PIN_ST_S2_CD_ST; + + if (menelaus_hw->mmc_callback) + menelaus_hw->mmc_callback(menelaus_hw->mmc_callback_data, + card_mask); +} + +/* + * Toggles the MMC slots between open-drain and push-pull mode. + */ +int menelaus_set_mmc_opendrain(int slot, int enable) +{ + int ret, val; + + if (slot != 1 && slot != 2) + return -EINVAL; + mutex_lock(&the_menelaus->lock); + ret = menelaus_read_reg(MENELAUS_MCT_CTRL1); + if (ret < 0) { + mutex_unlock(&the_menelaus->lock); + return ret; + } + val = ret; + if (slot == 1) { + if (enable) + val |= MCT_CTRL1_S1_CMD_OD; + else + val &= ~MCT_CTRL1_S1_CMD_OD; + } else { + if (enable) + val |= MCT_CTRL1_S2_CMD_OD; + else + val &= ~MCT_CTRL1_S2_CMD_OD; + } + ret = menelaus_write_reg(MENELAUS_MCT_CTRL1, val); + mutex_unlock(&the_menelaus->lock); + + return ret; +} +EXPORT_SYMBOL(menelaus_set_mmc_opendrain); + +int menelaus_set_slot_sel(int enable) +{ + int ret; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); + if (ret < 0) + goto out; + ret |= GPIO2_DIR_INPUT; + if (enable) + ret |= GPIO_CTRL_SLOTSELEN; + else + ret &= ~GPIO_CTRL_SLOTSELEN; + ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} +EXPORT_SYMBOL(menelaus_set_slot_sel); + +int menelaus_set_mmc_slot(int slot, int enable, int power, int cd_en) +{ + int ret, val; + + if (slot != 1 && slot != 2) + return -EINVAL; + if (power >= 3) + return -EINVAL; + + mutex_lock(&the_menelaus->lock); + + ret = menelaus_read_reg(MENELAUS_MCT_CTRL2); + if (ret < 0) + goto out; + val = ret; + if (slot == 1) { + if (cd_en) + val |= MCT_CTRL2_S1CD_BUFEN | MCT_CTRL2_S1CD_DBEN; + else + val &= ~(MCT_CTRL2_S1CD_BUFEN | MCT_CTRL2_S1CD_DBEN); + } else { + if (cd_en) + val |= MCT_CTRL2_S2CD_BUFEN | MCT_CTRL2_S2CD_BEN; + else + val &= ~(MCT_CTRL2_S2CD_BUFEN | MCT_CTRL2_S2CD_BEN); + } + ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, val); + if (ret < 0) + goto out; + + ret = menelaus_read_reg(MENELAUS_MCT_CTRL3); + if (ret < 0) + goto out; + val = ret; + if (slot == 1) { + if (enable) + val |= MCT_CTRL3_SLOT1_EN; + else + val &= ~MCT_CTRL3_SLOT1_EN; + } else { + int b; + + if (enable) + val |= MCT_CTRL3_SLOT2_EN; + else + val &= ~MCT_CTRL3_SLOT2_EN; + b = menelaus_read_reg(MENELAUS_MCT_CTRL2); + b &= ~(MCT_CTRL2_VS2_SEL_D0 | MCT_CTRL2_VS2_SEL_D1); + b |= power; + ret = menelaus_write_reg(MENELAUS_MCT_CTRL2, b); + if (ret < 0) + goto out; + } + /* Disable autonomous shutdown */ + val &= ~(MCT_CTRL3_S1_AUTO_EN | MCT_CTRL3_S2_AUTO_EN); + ret = menelaus_write_reg(MENELAUS_MCT_CTRL3, val); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} +EXPORT_SYMBOL(menelaus_set_mmc_slot); + +int menelaus_register_mmc_callback(void (*callback)(void *data, u8 card_mask), + void *data) +{ + int ret = 0; + + the_menelaus->mmc_callback_data = data; + the_menelaus->mmc_callback = callback; + ret = menelaus_add_irq_work(MENELAUS_MMC_S1CD_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S2CD_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S1D1_IRQ, + menelaus_mmc_cd_work); + if (ret < 0) + return ret; + ret = menelaus_add_irq_work(MENELAUS_MMC_S2D1_IRQ, + menelaus_mmc_cd_work); + + return ret; +} +EXPORT_SYMBOL(menelaus_register_mmc_callback); + +void menelaus_unregister_mmc_callback(void) +{ + menelaus_remove_irq_work(MENELAUS_MMC_S1CD_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S2CD_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S1D1_IRQ); + menelaus_remove_irq_work(MENELAUS_MMC_S2D1_IRQ); + + the_menelaus->mmc_callback = NULL; + the_menelaus->mmc_callback_data = NULL; +} +EXPORT_SYMBOL(menelaus_unregister_mmc_callback); + +struct menelaus_vtg { + const char *name; + u8 vtg_reg; + u8 vtg_shift; + u8 vtg_bits; + u8 mode_reg; +}; + +struct menelaus_vtg_value { + u16 vtg; + u16 val; +}; + +static int menelaus_set_voltage(const struct menelaus_vtg *vtg, int mV, + int vtg_val, int mode) +{ + int val, ret; + struct i2c_client *c = the_menelaus->client; + + mutex_lock(&the_menelaus->lock); + + ret = menelaus_read_reg(vtg->vtg_reg); + if (ret < 0) + goto out; + val = ret & ~(((1 << vtg->vtg_bits) - 1) << vtg->vtg_shift); + val |= vtg_val << vtg->vtg_shift; + + dev_dbg(&c->dev, "Setting voltage '%s'" + "to %d mV (reg 0x%02x, val 0x%02x)\n", + vtg->name, mV, vtg->vtg_reg, val); + + ret = menelaus_write_reg(vtg->vtg_reg, val); + if (ret < 0) + goto out; + ret = menelaus_write_reg(vtg->mode_reg, mode); +out: + mutex_unlock(&the_menelaus->lock); + if (ret == 0) { + /* Wait for voltage to stabilize */ + msleep(1); + } + return ret; +} + +static int menelaus_get_vtg_value(int vtg, const struct menelaus_vtg_value *tbl, + int n) +{ + int i; + + for (i = 0; i < n; i++, tbl++) + if (tbl->vtg == vtg) + return tbl->val; + return -EINVAL; +} + +/* + * Vcore can be programmed in two ways: + * SW-controlled: Required voltage is programmed into VCORE_CTRL1 + * HW-controlled: Required range (roof-floor) is programmed into VCORE_CTRL3 + * and VCORE_CTRL4 + * + * Call correct 'set' function accordingly + */ + +static const struct menelaus_vtg_value vcore_values[] = { + { 1000, 0 }, + { 1025, 1 }, + { 1050, 2 }, + { 1075, 3 }, + { 1100, 4 }, + { 1125, 5 }, + { 1150, 6 }, + { 1175, 7 }, + { 1200, 8 }, + { 1225, 9 }, + { 1250, 10 }, + { 1275, 11 }, + { 1300, 12 }, + { 1325, 13 }, + { 1350, 14 }, + { 1375, 15 }, + { 1400, 16 }, + { 1425, 17 }, + { 1450, 18 }, +}; + +int menelaus_set_vcore_hw(unsigned int roof_mV, unsigned int floor_mV) +{ + int fval, rval, val, ret; + struct i2c_client *c = the_menelaus->client; + + rval = menelaus_get_vtg_value(roof_mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (rval < 0) + return -EINVAL; + fval = menelaus_get_vtg_value(floor_mV, vcore_values, + ARRAY_SIZE(vcore_values)); + if (fval < 0) + return -EINVAL; + + dev_dbg(&c->dev, "Setting VCORE FLOOR to %d mV and ROOF to %d mV\n", + floor_mV, roof_mV); + + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL3, fval); + if (ret < 0) + goto out; + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL4, rval); + if (ret < 0) + goto out; + if (!the_menelaus->vcore_hw_mode) { + val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + /* HW mode, turn OFF byte comparator */ + val |= (VCORE_CTRL1_HW_NSW | VCORE_CTRL1_BYP_COMP); + ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val); + the_menelaus->vcore_hw_mode = 1; + } + msleep(1); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} + +static const struct menelaus_vtg vmem_vtg = { + .name = "VMEM", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 0, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL3, +}; + +static const struct menelaus_vtg_value vmem_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 1900, 2 }, + { 2500, 3 }, +}; + +int menelaus_set_vmem(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vmem_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vmem_values, ARRAY_SIZE(vmem_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vmem_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vmem); + +static const struct menelaus_vtg vio_vtg = { + .name = "VIO", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 2, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL4, +}; + +static const struct menelaus_vtg_value vio_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2500, 2 }, + { 2800, 3 }, +}; + +int menelaus_set_vio(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vio_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vio_values, ARRAY_SIZE(vio_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vio_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vio); + +static const struct menelaus_vtg_value vdcdc_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2000, 2 }, + { 2200, 3 }, + { 2400, 4 }, + { 2800, 5 }, + { 3000, 6 }, + { 3300, 7 }, +}; + +static const struct menelaus_vtg vdcdc2_vtg = { + .name = "VDCDC2", + .vtg_reg = MENELAUS_DCDC_CTRL1, + .vtg_shift = 0, + .vtg_bits = 3, + .mode_reg = MENELAUS_DCDC_CTRL2, +}; + +static const struct menelaus_vtg vdcdc3_vtg = { + .name = "VDCDC3", + .vtg_reg = MENELAUS_DCDC_CTRL1, + .vtg_shift = 3, + .vtg_bits = 3, + .mode_reg = MENELAUS_DCDC_CTRL3, +}; + +int menelaus_set_vdcdc(int dcdc, unsigned int mV) +{ + const struct menelaus_vtg *vtg; + int val; + + if (dcdc != 2 && dcdc != 3) + return -EINVAL; + if (dcdc == 2) + vtg = &vdcdc2_vtg; + else + vtg = &vdcdc3_vtg; + + if (mV == 0) + return menelaus_set_voltage(vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vdcdc_values, + ARRAY_SIZE(vdcdc_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(vtg, mV, val, 0x03); +} + +static const struct menelaus_vtg_value vmmc_values[] = { + { 1850, 0 }, + { 2800, 1 }, + { 3000, 2 }, + { 3100, 3 }, +}; + +static const struct menelaus_vtg vmmc_vtg = { + .name = "VMMC", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 6, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL7, +}; + +int menelaus_set_vmmc(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vmmc_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vmmc_values, ARRAY_SIZE(vmmc_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vmmc_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vmmc); + + +static const struct menelaus_vtg_value vaux_values[] = { + { 1500, 0 }, + { 1800, 1 }, + { 2500, 2 }, + { 2800, 3 }, +}; + +static const struct menelaus_vtg vaux_vtg = { + .name = "VAUX", + .vtg_reg = MENELAUS_LDO_CTRL1, + .vtg_shift = 4, + .vtg_bits = 2, + .mode_reg = MENELAUS_LDO_CTRL6, +}; + +int menelaus_set_vaux(unsigned int mV) +{ + int val; + + if (mV == 0) + return menelaus_set_voltage(&vaux_vtg, 0, 0, 0); + + val = menelaus_get_vtg_value(mV, vaux_values, ARRAY_SIZE(vaux_values)); + if (val < 0) + return -EINVAL; + return menelaus_set_voltage(&vaux_vtg, mV, val, 0x02); +} +EXPORT_SYMBOL(menelaus_set_vaux); + +int menelaus_get_slot_pin_states(void) +{ + return menelaus_read_reg(MENELAUS_MCT_PIN_ST); +} +EXPORT_SYMBOL(menelaus_get_slot_pin_states); + +int menelaus_set_regulator_sleep(int enable, u32 val) +{ + int t, ret; + struct i2c_client *c = the_menelaus->client; + + mutex_lock(&the_menelaus->lock); + ret = menelaus_write_reg(MENELAUS_SLEEP_CTRL2, val); + if (ret < 0) + goto out; + + dev_dbg(&c->dev, "regulator sleep configuration: %02x\n", val); + + ret = menelaus_read_reg(MENELAUS_GPIO_CTRL); + if (ret < 0) + goto out; + t = (GPIO_CTRL_SLPCTLEN | GPIO3_DIR_INPUT); + if (enable) + ret |= t; + else + ret &= ~t; + ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret); +out: + mutex_unlock(&the_menelaus->lock); + return ret; +} + +/*-----------------------------------------------------------------------*/ + +/* Handles Menelaus interrupts. Does not run in interrupt context */ +static void menelaus_work(struct work_struct *_menelaus) +{ + struct menelaus_chip *menelaus = + container_of(_menelaus, struct menelaus_chip, work); + void (*handler)(struct menelaus_chip *menelaus); + + while (1) { + unsigned isr; + + isr = (menelaus_read_reg(MENELAUS_INT_STATUS2) + & ~menelaus->mask2) << 8; + isr |= menelaus_read_reg(MENELAUS_INT_STATUS1) + & ~menelaus->mask1; + if (!isr) + break; + + while (isr) { + int irq = fls(isr) - 1; + isr &= ~(1 << irq); + + mutex_lock(&menelaus->lock); + menelaus_disable_irq(irq); + menelaus_ack_irq(irq); + handler = menelaus->handlers[irq]; + if (handler) + handler(menelaus); + menelaus_enable_irq(irq); + mutex_unlock(&menelaus->lock); + } + } + enable_irq(menelaus->client->irq); +} + +/* + * We cannot use I2C in interrupt context, so we just schedule work. + */ +static irqreturn_t menelaus_irq(int irq, void *_menelaus) +{ + struct menelaus_chip *menelaus = _menelaus; + + disable_irq_nosync(irq); + (void)schedule_work(&menelaus->work); + + return IRQ_HANDLED; +} + +/*-----------------------------------------------------------------------*/ + +/* + * The RTC needs to be set once, then it runs on backup battery power. + * It supports alarms, including system wake alarms (from some modes); + * and 1/second IRQs if requested. + */ +#ifdef CONFIG_RTC_DRV_TWL92330 + +#define RTC_CTRL_RTC_EN (1 << 0) +#define RTC_CTRL_AL_EN (1 << 1) +#define RTC_CTRL_MODE12 (1 << 2) +#define RTC_CTRL_EVERY_MASK (3 << 3) +#define RTC_CTRL_EVERY_SEC (0 << 3) +#define RTC_CTRL_EVERY_MIN (1 << 3) +#define RTC_CTRL_EVERY_HR (2 << 3) +#define RTC_CTRL_EVERY_DAY (3 << 3) + +#define RTC_UPDATE_EVERY 0x08 + +#define RTC_HR_PM (1 << 7) + +static void menelaus_to_time(char *regs, struct rtc_time *t) +{ + t->tm_sec = bcd2bin(regs[0]); + t->tm_min = bcd2bin(regs[1]); + if (the_menelaus->rtc_control & RTC_CTRL_MODE12) { + t->tm_hour = bcd2bin(regs[2] & 0x1f) - 1; + if (regs[2] & RTC_HR_PM) + t->tm_hour += 12; + } else + t->tm_hour = bcd2bin(regs[2] & 0x3f); + t->tm_mday = bcd2bin(regs[3]); + t->tm_mon = bcd2bin(regs[4]) - 1; + t->tm_year = bcd2bin(regs[5]) + 100; +} + +static int time_to_menelaus(struct rtc_time *t, int regnum) +{ + int hour, status; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_sec)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_min)); + if (status < 0) + goto fail; + + if (the_menelaus->rtc_control & RTC_CTRL_MODE12) { + hour = t->tm_hour + 1; + if (hour > 12) + hour = RTC_HR_PM | bin2bcd(hour - 12); + else + hour = bin2bcd(hour); + } else + hour = bin2bcd(t->tm_hour); + status = menelaus_write_reg(regnum++, hour); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_mday)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_mon + 1)); + if (status < 0) + goto fail; + + status = menelaus_write_reg(regnum++, bin2bcd(t->tm_year - 100)); + if (status < 0) + goto fail; + + return 0; +fail: + dev_err(&the_menelaus->client->dev, "rtc write reg %02x, err %d\n", + --regnum, status); + return status; +} + +static int menelaus_read_time(struct device *dev, struct rtc_time *t) +{ + struct i2c_msg msg[2]; + char regs[7]; + int status; + + /* block read date and time registers */ + regs[0] = MENELAUS_RTC_SEC; + + msg[0].addr = MENELAUS_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = regs; + + msg[1].addr = MENELAUS_I2C_ADDRESS; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(regs); + msg[1].buf = regs; + + status = i2c_transfer(the_menelaus->client->adapter, msg, 2); + if (status != 2) { + dev_err(dev, "%s error %d\n", "read", status); + return -EIO; + } + + menelaus_to_time(regs, t); + t->tm_wday = bcd2bin(regs[6]); + + return 0; +} + +static int menelaus_set_time(struct device *dev, struct rtc_time *t) +{ + int status; + + /* write date and time registers */ + status = time_to_menelaus(t, MENELAUS_RTC_SEC); + if (status < 0) + return status; + status = menelaus_write_reg(MENELAUS_RTC_WKDAY, bin2bcd(t->tm_wday)); + if (status < 0) { + dev_err(&the_menelaus->client->dev, "rtc write reg %02x " + "err %d\n", MENELAUS_RTC_WKDAY, status); + return status; + } + + /* now commit the write */ + status = menelaus_write_reg(MENELAUS_RTC_UPDATE, RTC_UPDATE_EVERY); + if (status < 0) + dev_err(&the_menelaus->client->dev, "rtc commit time, err %d\n", + status); + + return 0; +} + +static int menelaus_read_alarm(struct device *dev, struct rtc_wkalrm *w) +{ + struct i2c_msg msg[2]; + char regs[6]; + int status; + + /* block read alarm registers */ + regs[0] = MENELAUS_RTC_AL_SEC; + + msg[0].addr = MENELAUS_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = regs; + + msg[1].addr = MENELAUS_I2C_ADDRESS; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(regs); + msg[1].buf = regs; + + status = i2c_transfer(the_menelaus->client->adapter, msg, 2); + if (status != 2) { + dev_err(dev, "%s error %d\n", "alarm read", status); + return -EIO; + } + + menelaus_to_time(regs, &w->time); + + w->enabled = !!(the_menelaus->rtc_control & RTC_CTRL_AL_EN); + + /* NOTE we *could* check if actually pending... */ + w->pending = 0; + + return 0; +} + +static int menelaus_set_alarm(struct device *dev, struct rtc_wkalrm *w) +{ + int status; + + if (the_menelaus->client->irq <= 0 && w->enabled) + return -ENODEV; + + /* clear previous alarm enable */ + if (the_menelaus->rtc_control & RTC_CTRL_AL_EN) { + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + status = menelaus_write_reg(MENELAUS_RTC_CTRL, + the_menelaus->rtc_control); + if (status < 0) + return status; + } + + /* write alarm registers */ + status = time_to_menelaus(&w->time, MENELAUS_RTC_AL_SEC); + if (status < 0) + return status; + + /* enable alarm if requested */ + if (w->enabled) { + the_menelaus->rtc_control |= RTC_CTRL_AL_EN; + status = menelaus_write_reg(MENELAUS_RTC_CTRL, + the_menelaus->rtc_control); + } + + return status; +} + +#ifdef CONFIG_RTC_INTF_DEV + +static void menelaus_rtc_update_work(struct menelaus_chip *m) +{ + /* report 1/sec update */ + local_irq_disable(); + rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_UF); + local_irq_enable(); +} + +static int menelaus_ioctl(struct device *dev, unsigned cmd, unsigned long arg) +{ + int status; + + if (the_menelaus->client->irq <= 0) + return -ENOIOCTLCMD; + + switch (cmd) { + /* alarm IRQ */ + case RTC_AIE_ON: + if (the_menelaus->rtc_control & RTC_CTRL_AL_EN) + return 0; + the_menelaus->rtc_control |= RTC_CTRL_AL_EN; + break; + case RTC_AIE_OFF: + if (!(the_menelaus->rtc_control & RTC_CTRL_AL_EN)) + return 0; + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + break; + /* 1/second "update" IRQ */ + case RTC_UIE_ON: + if (the_menelaus->uie) + return 0; + status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ); + status = menelaus_add_irq_work(MENELAUS_RTCTMR_IRQ, + menelaus_rtc_update_work); + if (status == 0) + the_menelaus->uie = 1; + return status; + case RTC_UIE_OFF: + if (!the_menelaus->uie) + return 0; + status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ); + if (status == 0) + the_menelaus->uie = 0; + return status; + default: + return -ENOIOCTLCMD; + } + return menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control); +} + +#else +#define menelaus_ioctl NULL +#endif + +/* REVISIT no compensation register support ... */ + +static const struct rtc_class_ops menelaus_rtc_ops = { + .ioctl = menelaus_ioctl, + .read_time = menelaus_read_time, + .set_time = menelaus_set_time, + .read_alarm = menelaus_read_alarm, + .set_alarm = menelaus_set_alarm, +}; + +static void menelaus_rtc_alarm_work(struct menelaus_chip *m) +{ + /* report alarm */ + local_irq_disable(); + rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_AF); + local_irq_enable(); + + /* then disable it; alarms are oneshot */ + the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN; + menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control); +} + +static inline void menelaus_rtc_init(struct menelaus_chip *m) +{ + int alarm = (m->client->irq > 0); + + /* assume 32KDETEN pin is pulled high */ + if (!(menelaus_read_reg(MENELAUS_OSC_CTRL) & 0x80)) { + dev_dbg(&m->client->dev, "no 32k oscillator\n"); + return; + } + + /* support RTC alarm; it can issue wakeups */ + if (alarm) { + if (menelaus_add_irq_work(MENELAUS_RTCALM_IRQ, + menelaus_rtc_alarm_work) < 0) { + dev_err(&m->client->dev, "can't handle RTC alarm\n"); + return; + } + device_init_wakeup(&m->client->dev, 1); + } + + /* be sure RTC is enabled; allow 1/sec irqs; leave 12hr mode alone */ + m->rtc_control = menelaus_read_reg(MENELAUS_RTC_CTRL); + if (!(m->rtc_control & RTC_CTRL_RTC_EN) + || (m->rtc_control & RTC_CTRL_AL_EN) + || (m->rtc_control & RTC_CTRL_EVERY_MASK)) { + if (!(m->rtc_control & RTC_CTRL_RTC_EN)) { + dev_warn(&m->client->dev, "rtc clock needs setting\n"); + m->rtc_control |= RTC_CTRL_RTC_EN; + } + m->rtc_control &= ~RTC_CTRL_EVERY_MASK; + m->rtc_control &= ~RTC_CTRL_AL_EN; + menelaus_write_reg(MENELAUS_RTC_CTRL, m->rtc_control); + } + + m->rtc = rtc_device_register(DRIVER_NAME, + &m->client->dev, + &menelaus_rtc_ops, THIS_MODULE); + if (IS_ERR(m->rtc)) { + if (alarm) { + menelaus_remove_irq_work(MENELAUS_RTCALM_IRQ); + device_init_wakeup(&m->client->dev, 0); + } + dev_err(&m->client->dev, "can't register RTC: %d\n", + (int) PTR_ERR(m->rtc)); + the_menelaus->rtc = NULL; + } +} + +#else + +static inline void menelaus_rtc_init(struct menelaus_chip *m) +{ + /* nothing */ +} + +#endif + +/*-----------------------------------------------------------------------*/ + +static struct i2c_driver menelaus_i2c_driver; + +static int menelaus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct menelaus_chip *menelaus; + int rev = 0; + int err = 0; + struct menelaus_platform_data *menelaus_pdata = + dev_get_platdata(&client->dev); + + if (the_menelaus) { + dev_dbg(&client->dev, "only one %s for now\n", + DRIVER_NAME); + return -ENODEV; + } + + menelaus = devm_kzalloc(&client->dev, sizeof(*menelaus), GFP_KERNEL); + if (!menelaus) + return -ENOMEM; + + i2c_set_clientdata(client, menelaus); + + the_menelaus = menelaus; + menelaus->client = client; + + /* If a true probe check the device */ + rev = menelaus_read_reg(MENELAUS_REV); + if (rev < 0) { + pr_err(DRIVER_NAME ": device not found"); + return -ENODEV; + } + + /* Ack and disable all Menelaus interrupts */ + menelaus_write_reg(MENELAUS_INT_ACK1, 0xff); + menelaus_write_reg(MENELAUS_INT_ACK2, 0xff); + menelaus_write_reg(MENELAUS_INT_MASK1, 0xff); + menelaus_write_reg(MENELAUS_INT_MASK2, 0xff); + menelaus->mask1 = 0xff; + menelaus->mask2 = 0xff; + + /* Set output buffer strengths */ + menelaus_write_reg(MENELAUS_MCT_CTRL1, 0x73); + + if (client->irq > 0) { + err = request_irq(client->irq, menelaus_irq, 0, + DRIVER_NAME, menelaus); + if (err) { + dev_dbg(&client->dev, "can't get IRQ %d, err %d\n", + client->irq, err); + return err; + } + } + + mutex_init(&menelaus->lock); + INIT_WORK(&menelaus->work, menelaus_work); + + pr_info("Menelaus rev %d.%d\n", rev >> 4, rev & 0x0f); + + err = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + if (err < 0) + goto fail; + if (err & VCORE_CTRL1_HW_NSW) + menelaus->vcore_hw_mode = 1; + else + menelaus->vcore_hw_mode = 0; + + if (menelaus_pdata != NULL && menelaus_pdata->late_init != NULL) { + err = menelaus_pdata->late_init(&client->dev); + if (err < 0) + goto fail; + } + + menelaus_rtc_init(menelaus); + + return 0; +fail: + free_irq(client->irq, menelaus); + flush_work(&menelaus->work); + return err; +} + +static int menelaus_remove(struct i2c_client *client) +{ + struct menelaus_chip *menelaus = i2c_get_clientdata(client); + + free_irq(client->irq, menelaus); + flush_work(&menelaus->work); + the_menelaus = NULL; + return 0; +} + +static const struct i2c_device_id menelaus_id[] = { + { "menelaus", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, menelaus_id); + +static struct i2c_driver menelaus_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = menelaus_probe, + .remove = menelaus_remove, + .id_table = menelaus_id, +}; + +module_i2c_driver(menelaus_i2c_driver); + +MODULE_AUTHOR("Texas Instruments, Inc. (and others)"); +MODULE_DESCRIPTION("I2C interface for Menelaus."); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/menf21bmc.c b/kernel/drivers/mfd/menf21bmc.c new file mode 100644 index 000000000..1c2743458 --- /dev/null +++ b/kernel/drivers/mfd/menf21bmc.c @@ -0,0 +1,132 @@ +/* + * MEN 14F021P00 Board Management Controller (BMC) MFD Core Driver. + * + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH + * + * 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/device.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> + +#define BMC_CMD_WDT_EXIT_PROD 0x18 +#define BMC_CMD_WDT_PROD_STAT 0x19 +#define BMC_CMD_REV_MAJOR 0x80 +#define BMC_CMD_REV_MINOR 0x81 +#define BMC_CMD_REV_MAIN 0x82 + +static struct mfd_cell menf21bmc_cell[] = { + { .name = "menf21bmc_wdt", }, + { .name = "menf21bmc_led", }, + { .name = "menf21bmc_hwmon", } +}; + +static int menf21bmc_wdt_exit_prod_mode(struct i2c_client *client) +{ + int val, ret; + + val = i2c_smbus_read_byte_data(client, BMC_CMD_WDT_PROD_STAT); + if (val < 0) + return val; + + /* + * Production mode should be not active after delivery of the Board. + * To be sure we check it, inform the user and exit the mode + * if active. + */ + if (val == 0x00) { + dev_info(&client->dev, + "BMC in production mode. Exit production mode\n"); + + ret = i2c_smbus_write_byte(client, BMC_CMD_WDT_EXIT_PROD); + if (ret < 0) + return ret; + } + + return 0; +} + +static int +menf21bmc_probe(struct i2c_client *client, const struct i2c_device_id *ids) +{ + int rev_major, rev_minor, rev_main; + int ret; + + ret = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BYTE); + if (!ret) + return -ENODEV; + + rev_major = i2c_smbus_read_word_data(client, BMC_CMD_REV_MAJOR); + if (rev_major < 0) { + dev_err(&client->dev, "failed to get BMC major revision\n"); + return rev_major; + } + + rev_minor = i2c_smbus_read_word_data(client, BMC_CMD_REV_MINOR); + if (rev_minor < 0) { + dev_err(&client->dev, "failed to get BMC minor revision\n"); + return rev_minor; + } + + rev_main = i2c_smbus_read_word_data(client, BMC_CMD_REV_MAIN); + if (rev_main < 0) { + dev_err(&client->dev, "failed to get BMC main revision\n"); + return rev_main; + } + + dev_info(&client->dev, "FW Revision: %02d.%02d.%02d\n", + rev_major, rev_minor, rev_main); + + /* + * We have to exit the Production Mode of the BMC to activate the + * Watchdog functionality and the BIOS life sign monitoring. + */ + ret = menf21bmc_wdt_exit_prod_mode(client); + if (ret < 0) { + dev_err(&client->dev, "failed to leave production mode\n"); + return ret; + } + + ret = mfd_add_devices(&client->dev, 0, menf21bmc_cell, + ARRAY_SIZE(menf21bmc_cell), NULL, 0, NULL); + if (ret < 0) { + dev_err(&client->dev, "failed to add BMC sub-devices\n"); + return ret; + } + + return 0; +} + +static int menf21bmc_remove(struct i2c_client *client) +{ + mfd_remove_devices(&client->dev); + return 0; +} + +static const struct i2c_device_id menf21bmc_id_table[] = { + { "menf21bmc" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, menf21bmc_id_table); + +static struct i2c_driver menf21bmc_driver = { + .driver.name = "menf21bmc", + .id_table = menf21bmc_id_table, + .probe = menf21bmc_probe, + .remove = menf21bmc_remove, +}; + +module_i2c_driver(menf21bmc_driver); + +MODULE_DESCRIPTION("MEN 14F021P00 BMC mfd core driver"); +MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/mfd-core.c b/kernel/drivers/mfd/mfd-core.c new file mode 100644 index 000000000..1aed3b7b8 --- /dev/null +++ b/kernel/drivers/mfd/mfd-core.c @@ -0,0 +1,340 @@ +/* + * drivers/mfd/mfd-core.c + * + * core MFD support + * Copyright (c) 2006 Ian Molton + * Copyright (c) 2007,2008 Dmitry Baryshkov + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> +#include <linux/mfd/core.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +static struct device_type mfd_dev_type = { + .name = "mfd_device", +}; + +int mfd_cell_enable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + int err = 0; + + /* only call enable hook if the cell wasn't previously enabled */ + if (atomic_inc_return(cell->usage_count) == 1) + err = cell->enable(pdev); + + /* if the enable hook failed, decrement counter to allow retries */ + if (err) + atomic_dec(cell->usage_count); + + return err; +} +EXPORT_SYMBOL(mfd_cell_enable); + +int mfd_cell_disable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + int err = 0; + + /* only disable if no other clients are using it */ + if (atomic_dec_return(cell->usage_count) == 0) + err = cell->disable(pdev); + + /* if the disable hook failed, increment to allow retries */ + if (err) + atomic_inc(cell->usage_count); + + /* sanity check; did someone call disable too many times? */ + WARN_ON(atomic_read(cell->usage_count) < 0); + + return err; +} +EXPORT_SYMBOL(mfd_cell_disable); + +static int mfd_platform_add_cell(struct platform_device *pdev, + const struct mfd_cell *cell, + atomic_t *usage_count) +{ + if (!cell) + return 0; + + pdev->mfd_cell = kmemdup(cell, sizeof(*cell), GFP_KERNEL); + if (!pdev->mfd_cell) + return -ENOMEM; + + pdev->mfd_cell->usage_count = usage_count; + return 0; +} + +#if IS_ENABLED(CONFIG_ACPI) +static void mfd_acpi_add_device(const struct mfd_cell *cell, + struct platform_device *pdev) +{ + struct acpi_device *parent_adev; + struct acpi_device *adev; + + parent_adev = ACPI_COMPANION(pdev->dev.parent); + if (!parent_adev) + return; + + /* + * MFD child device gets its ACPI handle either from the ACPI + * device directly under the parent that matches the acpi_pnpid or + * it will use the parent handle if is no acpi_pnpid is given. + */ + adev = parent_adev; + if (cell->acpi_pnpid) { + struct acpi_device_id ids[2] = {}; + struct acpi_device *child_adev; + + strlcpy(ids[0].id, cell->acpi_pnpid, sizeof(ids[0].id)); + list_for_each_entry(child_adev, &parent_adev->children, node) + if (acpi_match_device_ids(child_adev, ids)) { + adev = child_adev; + break; + } + } + + ACPI_COMPANION_SET(&pdev->dev, adev); +} +#else +static inline void mfd_acpi_add_device(const struct mfd_cell *cell, + struct platform_device *pdev) +{ +} +#endif + +static int mfd_add_device(struct device *parent, int id, + const struct mfd_cell *cell, atomic_t *usage_count, + struct resource *mem_base, + int irq_base, struct irq_domain *domain) +{ + struct resource *res; + struct platform_device *pdev; + struct device_node *np = NULL; + int ret = -ENOMEM; + int platform_id; + int r; + + if (id == PLATFORM_DEVID_AUTO) + platform_id = id; + else + platform_id = id + cell->id; + + pdev = platform_device_alloc(cell->name, platform_id); + if (!pdev) + goto fail_alloc; + + res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL); + if (!res) + goto fail_device; + + pdev->dev.parent = parent; + pdev->dev.type = &mfd_dev_type; + pdev->dev.dma_mask = parent->dma_mask; + pdev->dev.dma_parms = parent->dma_parms; + pdev->dev.coherent_dma_mask = parent->coherent_dma_mask; + + ret = regulator_bulk_register_supply_alias( + &pdev->dev, cell->parent_supplies, + parent, cell->parent_supplies, + cell->num_parent_supplies); + if (ret < 0) + goto fail_res; + + if (parent->of_node && cell->of_compatible) { + for_each_child_of_node(parent->of_node, np) { + if (of_device_is_compatible(np, cell->of_compatible)) { + pdev->dev.of_node = np; + break; + } + } + } + + mfd_acpi_add_device(cell, pdev); + + if (cell->pdata_size) { + ret = platform_device_add_data(pdev, + cell->platform_data, cell->pdata_size); + if (ret) + goto fail_alias; + } + + ret = mfd_platform_add_cell(pdev, cell, usage_count); + if (ret) + goto fail_alias; + + for (r = 0; r < cell->num_resources; r++) { + res[r].name = cell->resources[r].name; + res[r].flags = cell->resources[r].flags; + + /* Find out base to use */ + if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) { + res[r].parent = mem_base; + res[r].start = mem_base->start + + cell->resources[r].start; + res[r].end = mem_base->start + + cell->resources[r].end; + } else if (cell->resources[r].flags & IORESOURCE_IRQ) { + if (domain) { + /* Unable to create mappings for IRQ ranges. */ + WARN_ON(cell->resources[r].start != + cell->resources[r].end); + res[r].start = res[r].end = irq_create_mapping( + domain, cell->resources[r].start); + } else { + res[r].start = irq_base + + cell->resources[r].start; + res[r].end = irq_base + + cell->resources[r].end; + } + } else { + res[r].parent = cell->resources[r].parent; + res[r].start = cell->resources[r].start; + res[r].end = cell->resources[r].end; + } + + if (!cell->ignore_resource_conflicts) { + ret = acpi_check_resource_conflict(&res[r]); + if (ret) + goto fail_alias; + } + } + + ret = platform_device_add_resources(pdev, res, cell->num_resources); + if (ret) + goto fail_alias; + + ret = platform_device_add(pdev); + if (ret) + goto fail_alias; + + if (cell->pm_runtime_no_callbacks) + pm_runtime_no_callbacks(&pdev->dev); + + kfree(res); + + return 0; + +fail_alias: + regulator_bulk_unregister_supply_alias(&pdev->dev, + cell->parent_supplies, + cell->num_parent_supplies); +fail_res: + kfree(res); +fail_device: + platform_device_put(pdev); +fail_alloc: + return ret; +} + +int mfd_add_devices(struct device *parent, int id, + const struct mfd_cell *cells, int n_devs, + struct resource *mem_base, + int irq_base, struct irq_domain *domain) +{ + int i; + int ret; + atomic_t *cnts; + + /* initialize reference counting for all cells */ + cnts = kcalloc(n_devs, sizeof(*cnts), GFP_KERNEL); + if (!cnts) + return -ENOMEM; + + for (i = 0; i < n_devs; i++) { + atomic_set(&cnts[i], 0); + ret = mfd_add_device(parent, id, cells + i, cnts + i, mem_base, + irq_base, domain); + if (ret) + goto fail; + } + + return 0; + +fail: + if (i) + mfd_remove_devices(parent); + else + kfree(cnts); + return ret; +} +EXPORT_SYMBOL(mfd_add_devices); + +static int mfd_remove_devices_fn(struct device *dev, void *c) +{ + struct platform_device *pdev; + const struct mfd_cell *cell; + atomic_t **usage_count = c; + + if (dev->type != &mfd_dev_type) + return 0; + + pdev = to_platform_device(dev); + cell = mfd_get_cell(pdev); + + regulator_bulk_unregister_supply_alias(dev, cell->parent_supplies, + cell->num_parent_supplies); + + /* find the base address of usage_count pointers (for freeing) */ + if (!*usage_count || (cell->usage_count < *usage_count)) + *usage_count = cell->usage_count; + + platform_device_unregister(pdev); + return 0; +} + +void mfd_remove_devices(struct device *parent) +{ + atomic_t *cnts = NULL; + + device_for_each_child(parent, &cnts, mfd_remove_devices_fn); + kfree(cnts); +} +EXPORT_SYMBOL(mfd_remove_devices); + +int mfd_clone_cell(const char *cell, const char **clones, size_t n_clones) +{ + struct mfd_cell cell_entry; + struct device *dev; + struct platform_device *pdev; + int i; + + /* fetch the parent cell's device (should already be registered!) */ + dev = bus_find_device_by_name(&platform_bus_type, NULL, cell); + if (!dev) { + printk(KERN_ERR "failed to find device for cell %s\n", cell); + return -ENODEV; + } + pdev = to_platform_device(dev); + memcpy(&cell_entry, mfd_get_cell(pdev), sizeof(cell_entry)); + + WARN_ON(!cell_entry.enable); + + for (i = 0; i < n_clones; i++) { + cell_entry.name = clones[i]; + /* don't give up if a single call fails; just report error */ + if (mfd_add_device(pdev->dev.parent, -1, &cell_entry, + cell_entry.usage_count, NULL, 0, NULL)) + dev_err(dev, "failed to create platform device '%s'\n", + clones[i]); + } + + return 0; +} +EXPORT_SYMBOL(mfd_clone_cell); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov"); diff --git a/kernel/drivers/mfd/mt6397-core.c b/kernel/drivers/mfd/mt6397-core.c new file mode 100644 index 000000000..09bc78049 --- /dev/null +++ b/kernel/drivers/mfd/mt6397-core.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Flora Fu, MediaTek + * + * 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. + */ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> +#include <linux/mfd/core.h> +#include <linux/mfd/mt6397/core.h> +#include <linux/mfd/mt6397/registers.h> + +static const struct mfd_cell mt6397_devs[] = { + { + .name = "mt6397-rtc", + .of_compatible = "mediatek,mt6397-rtc", + }, { + .name = "mt6397-regulator", + .of_compatible = "mediatek,mt6397-regulator", + }, { + .name = "mt6397-codec", + .of_compatible = "mediatek,mt6397-codec", + }, { + .name = "mt6397-clk", + .of_compatible = "mediatek,mt6397-clk", + }, +}; + +static void mt6397_irq_lock(struct irq_data *data) +{ + struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); + + mutex_lock(&mt6397->irqlock); +} + +static void mt6397_irq_sync_unlock(struct irq_data *data) +{ + struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); + + regmap_write(mt6397->regmap, MT6397_INT_CON0, mt6397->irq_masks_cur[0]); + regmap_write(mt6397->regmap, MT6397_INT_CON1, mt6397->irq_masks_cur[1]); + + mutex_unlock(&mt6397->irqlock); +} + +static void mt6397_irq_disable(struct irq_data *data) +{ + struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); + int shift = data->hwirq & 0xf; + int reg = data->hwirq >> 4; + + mt6397->irq_masks_cur[reg] &= ~BIT(shift); +} + +static void mt6397_irq_enable(struct irq_data *data) +{ + struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); + int shift = data->hwirq & 0xf; + int reg = data->hwirq >> 4; + + mt6397->irq_masks_cur[reg] |= BIT(shift); +} + +static struct irq_chip mt6397_irq_chip = { + .name = "mt6397-irq", + .irq_bus_lock = mt6397_irq_lock, + .irq_bus_sync_unlock = mt6397_irq_sync_unlock, + .irq_enable = mt6397_irq_enable, + .irq_disable = mt6397_irq_disable, +}; + +static void mt6397_irq_handle_reg(struct mt6397_chip *mt6397, int reg, + int irqbase) +{ + unsigned int status; + int i, irq, ret; + + ret = regmap_read(mt6397->regmap, reg, &status); + if (ret) { + dev_err(mt6397->dev, "Failed to read irq status: %d\n", ret); + return; + } + + for (i = 0; i < 16; i++) { + if (status & BIT(i)) { + irq = irq_find_mapping(mt6397->irq_domain, irqbase + i); + if (irq) + handle_nested_irq(irq); + } + } + + regmap_write(mt6397->regmap, reg, status); +} + +static irqreturn_t mt6397_irq_thread(int irq, void *data) +{ + struct mt6397_chip *mt6397 = data; + + mt6397_irq_handle_reg(mt6397, MT6397_INT_STATUS0, 0); + mt6397_irq_handle_reg(mt6397, MT6397_INT_STATUS1, 16); + + return IRQ_HANDLED; +} + +static int mt6397_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct mt6397_chip *mt6397 = d->host_data; + + irq_set_chip_data(irq, mt6397); + irq_set_chip_and_handler(irq, &mt6397_irq_chip, handle_level_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + + return 0; +} + +static struct irq_domain_ops mt6397_irq_domain_ops = { + .map = mt6397_irq_domain_map, +}; + +static int mt6397_irq_init(struct mt6397_chip *mt6397) +{ + int ret; + + mutex_init(&mt6397->irqlock); + + /* Mask all interrupt sources */ + regmap_write(mt6397->regmap, MT6397_INT_CON0, 0x0); + regmap_write(mt6397->regmap, MT6397_INT_CON1, 0x0); + + mt6397->irq_domain = irq_domain_add_linear(mt6397->dev->of_node, + MT6397_IRQ_NR, &mt6397_irq_domain_ops, mt6397); + if (!mt6397->irq_domain) { + dev_err(mt6397->dev, "could not create irq domain\n"); + return -ENOMEM; + } + + ret = devm_request_threaded_irq(mt6397->dev, mt6397->irq, NULL, + mt6397_irq_thread, IRQF_ONESHOT, "mt6397-pmic", mt6397); + if (ret) { + dev_err(mt6397->dev, "failed to register irq=%d; err: %d\n", + mt6397->irq, ret); + return ret; + } + + return 0; +} + +static int mt6397_probe(struct platform_device *pdev) +{ + int ret; + struct mt6397_chip *mt6397; + + mt6397 = devm_kzalloc(&pdev->dev, sizeof(*mt6397), GFP_KERNEL); + if (!mt6397) + return -ENOMEM; + + mt6397->dev = &pdev->dev; + /* + * mt6397 MFD is child device of soc pmic wrapper. + * Regmap is set from its parent. + */ + mt6397->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!mt6397->regmap) + return -ENODEV; + + platform_set_drvdata(pdev, mt6397); + + mt6397->irq = platform_get_irq(pdev, 0); + if (mt6397->irq > 0) { + ret = mt6397_irq_init(mt6397); + if (ret) + return ret; + } + + ret = mfd_add_devices(&pdev->dev, -1, mt6397_devs, + ARRAY_SIZE(mt6397_devs), NULL, 0, NULL); + if (ret) + dev_err(&pdev->dev, "failed to add child devices: %d\n", ret); + + return ret; +} + +static int mt6397_remove(struct platform_device *pdev) +{ + mfd_remove_devices(&pdev->dev); + + return 0; +} + +static const struct of_device_id mt6397_of_match[] = { + { .compatible = "mediatek,mt6397" }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6397_of_match); + +static struct platform_driver mt6397_driver = { + .probe = mt6397_probe, + .remove = mt6397_remove, + .driver = { + .name = "mt6397", + .of_match_table = of_match_ptr(mt6397_of_match), + }, +}; + +module_platform_driver(mt6397_driver); + +MODULE_AUTHOR("Flora Fu, MediaTek"); +MODULE_DESCRIPTION("Driver for MediaTek MT6397 PMIC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mt6397"); diff --git a/kernel/drivers/mfd/omap-usb-host.c b/kernel/drivers/mfd/omap-usb-host.c new file mode 100644 index 000000000..1d924d153 --- /dev/null +++ b/kernel/drivers/mfd/omap-usb-host.c @@ -0,0 +1,905 @@ +/** + * omap-usb-host.c - The USBHS core driver for OMAP EHCI & OHCI + * + * Copyright (C) 2011-2013 Texas Instruments Incorporated - http://www.ti.com + * Author: Keshava Munegowda <keshava_mgowda@ti.com> + * Author: Roger Quadros <rogerq@ti.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 of + * the License 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/platform_data/usb-omap.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/err.h> + +#include "omap-usb.h" + +#define USBHS_DRIVER_NAME "usbhs_omap" +#define OMAP_EHCI_DEVICE "ehci-omap" +#define OMAP_OHCI_DEVICE "ohci-omap3" + +/* OMAP USBHOST Register addresses */ + +/* UHH Register Set */ +#define OMAP_UHH_REVISION (0x00) +#define OMAP_UHH_SYSCONFIG (0x10) +#define OMAP_UHH_SYSCONFIG_MIDLEMODE (1 << 12) +#define OMAP_UHH_SYSCONFIG_CACTIVITY (1 << 8) +#define OMAP_UHH_SYSCONFIG_SIDLEMODE (1 << 3) +#define OMAP_UHH_SYSCONFIG_ENAWAKEUP (1 << 2) +#define OMAP_UHH_SYSCONFIG_SOFTRESET (1 << 1) +#define OMAP_UHH_SYSCONFIG_AUTOIDLE (1 << 0) + +#define OMAP_UHH_SYSSTATUS (0x14) +#define OMAP_UHH_HOSTCONFIG (0x40) +#define OMAP_UHH_HOSTCONFIG_ULPI_BYPASS (1 << 0) +#define OMAP_UHH_HOSTCONFIG_ULPI_P1_BYPASS (1 << 0) +#define OMAP_UHH_HOSTCONFIG_ULPI_P2_BYPASS (1 << 11) +#define OMAP_UHH_HOSTCONFIG_ULPI_P3_BYPASS (1 << 12) +#define OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN (1 << 2) +#define OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN (1 << 3) +#define OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN (1 << 4) +#define OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN (1 << 5) +#define OMAP_UHH_HOSTCONFIG_P1_CONNECT_STATUS (1 << 8) +#define OMAP_UHH_HOSTCONFIG_P2_CONNECT_STATUS (1 << 9) +#define OMAP_UHH_HOSTCONFIG_P3_CONNECT_STATUS (1 << 10) +#define OMAP4_UHH_HOSTCONFIG_APP_START_CLK (1 << 31) + +/* OMAP4-specific defines */ +#define OMAP4_UHH_SYSCONFIG_IDLEMODE_CLEAR (3 << 2) +#define OMAP4_UHH_SYSCONFIG_NOIDLE (1 << 2) +#define OMAP4_UHH_SYSCONFIG_STDBYMODE_CLEAR (3 << 4) +#define OMAP4_UHH_SYSCONFIG_NOSTDBY (1 << 4) +#define OMAP4_UHH_SYSCONFIG_SOFTRESET (1 << 0) + +#define OMAP4_P1_MODE_CLEAR (3 << 16) +#define OMAP4_P1_MODE_TLL (1 << 16) +#define OMAP4_P1_MODE_HSIC (3 << 16) +#define OMAP4_P2_MODE_CLEAR (3 << 18) +#define OMAP4_P2_MODE_TLL (1 << 18) +#define OMAP4_P2_MODE_HSIC (3 << 18) + +#define OMAP_UHH_DEBUG_CSR (0x44) + +/* Values of UHH_REVISION - Note: these are not given in the TRM */ +#define OMAP_USBHS_REV1 0x00000010 /* OMAP3 */ +#define OMAP_USBHS_REV2 0x50700100 /* OMAP4 */ + +#define is_omap_usbhs_rev1(x) (x->usbhs_rev == OMAP_USBHS_REV1) +#define is_omap_usbhs_rev2(x) (x->usbhs_rev == OMAP_USBHS_REV2) + +#define is_ehci_phy_mode(x) (x == OMAP_EHCI_PORT_MODE_PHY) +#define is_ehci_tll_mode(x) (x == OMAP_EHCI_PORT_MODE_TLL) +#define is_ehci_hsic_mode(x) (x == OMAP_EHCI_PORT_MODE_HSIC) + + +struct usbhs_hcd_omap { + int nports; + struct clk **utmi_clk; + struct clk **hsic60m_clk; + struct clk **hsic480m_clk; + + struct clk *xclk60mhsp1_ck; + struct clk *xclk60mhsp2_ck; + struct clk *utmi_p1_gfclk; + struct clk *utmi_p2_gfclk; + struct clk *init_60m_fclk; + struct clk *ehci_logic_fck; + + void __iomem *uhh_base; + + struct usbhs_omap_platform_data *pdata; + + u32 usbhs_rev; +}; +/*-------------------------------------------------------------------------*/ + +static const char usbhs_driver_name[] = USBHS_DRIVER_NAME; +static u64 usbhs_dmamask = DMA_BIT_MASK(32); + +/*-------------------------------------------------------------------------*/ + +static inline void usbhs_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline u32 usbhs_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +/*-------------------------------------------------------------------------*/ + +/** + * Map 'enum usbhs_omap_port_mode' found in <linux/platform_data/usb-omap.h> + * to the device tree binding portN-mode found in + * 'Documentation/devicetree/bindings/mfd/omap-usb-host.txt' + */ +static const char * const port_modes[] = { + [OMAP_USBHS_PORT_MODE_UNUSED] = "", + [OMAP_EHCI_PORT_MODE_PHY] = "ehci-phy", + [OMAP_EHCI_PORT_MODE_TLL] = "ehci-tll", + [OMAP_EHCI_PORT_MODE_HSIC] = "ehci-hsic", + [OMAP_OHCI_PORT_MODE_PHY_6PIN_DATSE0] = "ohci-phy-6pin-datse0", + [OMAP_OHCI_PORT_MODE_PHY_6PIN_DPDM] = "ohci-phy-6pin-dpdm", + [OMAP_OHCI_PORT_MODE_PHY_3PIN_DATSE0] = "ohci-phy-3pin-datse0", + [OMAP_OHCI_PORT_MODE_PHY_4PIN_DPDM] = "ohci-phy-4pin-dpdm", + [OMAP_OHCI_PORT_MODE_TLL_6PIN_DATSE0] = "ohci-tll-6pin-datse0", + [OMAP_OHCI_PORT_MODE_TLL_6PIN_DPDM] = "ohci-tll-6pin-dpdm", + [OMAP_OHCI_PORT_MODE_TLL_3PIN_DATSE0] = "ohci-tll-3pin-datse0", + [OMAP_OHCI_PORT_MODE_TLL_4PIN_DPDM] = "ohci-tll-4pin-dpdm", + [OMAP_OHCI_PORT_MODE_TLL_2PIN_DATSE0] = "ohci-tll-2pin-datse0", + [OMAP_OHCI_PORT_MODE_TLL_2PIN_DPDM] = "ohci-tll-2pin-dpdm", +}; + +/** + * omap_usbhs_get_dt_port_mode - Get the 'enum usbhs_omap_port_mode' + * from the port mode string. + * @mode: The port mode string, usually obtained from device tree. + * + * The function returns the 'enum usbhs_omap_port_mode' that matches the + * provided port mode string as per the port_modes table. + * If no match is found it returns -ENODEV + */ +static const int omap_usbhs_get_dt_port_mode(const char *mode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port_modes); i++) { + if (!strcmp(mode, port_modes[i])) + return i; + } + + return -ENODEV; +} + +static struct platform_device *omap_usbhs_alloc_child(const char *name, + struct resource *res, int num_resources, void *pdata, + size_t pdata_size, struct device *dev) +{ + struct platform_device *child; + int ret; + + child = platform_device_alloc(name, 0); + + if (!child) { + dev_err(dev, "platform_device_alloc %s failed\n", name); + goto err_end; + } + + ret = platform_device_add_resources(child, res, num_resources); + if (ret) { + dev_err(dev, "platform_device_add_resources failed\n"); + goto err_alloc; + } + + ret = platform_device_add_data(child, pdata, pdata_size); + if (ret) { + dev_err(dev, "platform_device_add_data failed\n"); + goto err_alloc; + } + + child->dev.dma_mask = &usbhs_dmamask; + dma_set_coherent_mask(&child->dev, DMA_BIT_MASK(32)); + child->dev.parent = dev; + + ret = platform_device_add(child); + if (ret) { + dev_err(dev, "platform_device_add failed\n"); + goto err_alloc; + } + + return child; + +err_alloc: + platform_device_put(child); + +err_end: + return NULL; +} + +static int omap_usbhs_alloc_children(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usbhs_omap_platform_data *pdata = dev_get_platdata(dev); + struct platform_device *ehci; + struct platform_device *ohci; + struct resource *res; + struct resource resources[2]; + int ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ehci"); + if (!res) { + dev_err(dev, "EHCI get resource IORESOURCE_MEM failed\n"); + ret = -ENODEV; + goto err_end; + } + resources[0] = *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ehci-irq"); + if (!res) { + dev_err(dev, " EHCI get resource IORESOURCE_IRQ failed\n"); + ret = -ENODEV; + goto err_end; + } + resources[1] = *res; + + ehci = omap_usbhs_alloc_child(OMAP_EHCI_DEVICE, resources, 2, pdata, + sizeof(*pdata), dev); + + if (!ehci) { + dev_err(dev, "omap_usbhs_alloc_child failed\n"); + ret = -ENOMEM; + goto err_end; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ohci"); + if (!res) { + dev_err(dev, "OHCI get resource IORESOURCE_MEM failed\n"); + ret = -ENODEV; + goto err_ehci; + } + resources[0] = *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ohci-irq"); + if (!res) { + dev_err(dev, "OHCI get resource IORESOURCE_IRQ failed\n"); + ret = -ENODEV; + goto err_ehci; + } + resources[1] = *res; + + ohci = omap_usbhs_alloc_child(OMAP_OHCI_DEVICE, resources, 2, pdata, + sizeof(*pdata), dev); + if (!ohci) { + dev_err(dev, "omap_usbhs_alloc_child failed\n"); + ret = -ENOMEM; + goto err_ehci; + } + + return 0; + +err_ehci: + platform_device_unregister(ehci); + +err_end: + return ret; +} + +static bool is_ohci_port(enum usbhs_omap_port_mode pmode) +{ + switch (pmode) { + case OMAP_OHCI_PORT_MODE_PHY_6PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_PHY_6PIN_DPDM: + case OMAP_OHCI_PORT_MODE_PHY_3PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_PHY_4PIN_DPDM: + case OMAP_OHCI_PORT_MODE_TLL_6PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_TLL_6PIN_DPDM: + case OMAP_OHCI_PORT_MODE_TLL_3PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_TLL_4PIN_DPDM: + case OMAP_OHCI_PORT_MODE_TLL_2PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_TLL_2PIN_DPDM: + return true; + + default: + return false; + } +} + +static int usbhs_runtime_resume(struct device *dev) +{ + struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); + struct usbhs_omap_platform_data *pdata = omap->pdata; + int i, r; + + dev_dbg(dev, "usbhs_runtime_resume\n"); + + omap_tll_enable(pdata); + + if (!IS_ERR(omap->ehci_logic_fck)) + clk_prepare_enable(omap->ehci_logic_fck); + + for (i = 0; i < omap->nports; i++) { + switch (pdata->port_mode[i]) { + case OMAP_EHCI_PORT_MODE_HSIC: + if (!IS_ERR(omap->hsic60m_clk[i])) { + r = clk_prepare_enable(omap->hsic60m_clk[i]); + if (r) { + dev_err(dev, + "Can't enable port %d hsic60m clk:%d\n", + i, r); + } + } + + if (!IS_ERR(omap->hsic480m_clk[i])) { + r = clk_prepare_enable(omap->hsic480m_clk[i]); + if (r) { + dev_err(dev, + "Can't enable port %d hsic480m clk:%d\n", + i, r); + } + } + /* Fall through as HSIC mode needs utmi_clk */ + + case OMAP_EHCI_PORT_MODE_TLL: + if (!IS_ERR(omap->utmi_clk[i])) { + r = clk_prepare_enable(omap->utmi_clk[i]); + if (r) { + dev_err(dev, + "Can't enable port %d clk : %d\n", + i, r); + } + } + break; + default: + break; + } + } + + return 0; +} + +static int usbhs_runtime_suspend(struct device *dev) +{ + struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); + struct usbhs_omap_platform_data *pdata = omap->pdata; + int i; + + dev_dbg(dev, "usbhs_runtime_suspend\n"); + + for (i = 0; i < omap->nports; i++) { + switch (pdata->port_mode[i]) { + case OMAP_EHCI_PORT_MODE_HSIC: + if (!IS_ERR(omap->hsic60m_clk[i])) + clk_disable_unprepare(omap->hsic60m_clk[i]); + + if (!IS_ERR(omap->hsic480m_clk[i])) + clk_disable_unprepare(omap->hsic480m_clk[i]); + /* Fall through as utmi_clks were used in HSIC mode */ + + case OMAP_EHCI_PORT_MODE_TLL: + if (!IS_ERR(omap->utmi_clk[i])) + clk_disable_unprepare(omap->utmi_clk[i]); + break; + default: + break; + } + } + + if (!IS_ERR(omap->ehci_logic_fck)) + clk_disable_unprepare(omap->ehci_logic_fck); + + omap_tll_disable(pdata); + + return 0; +} + +static unsigned omap_usbhs_rev1_hostconfig(struct usbhs_hcd_omap *omap, + unsigned reg) +{ + struct usbhs_omap_platform_data *pdata = omap->pdata; + int i; + + for (i = 0; i < omap->nports; i++) { + switch (pdata->port_mode[i]) { + case OMAP_USBHS_PORT_MODE_UNUSED: + reg &= ~(OMAP_UHH_HOSTCONFIG_P1_CONNECT_STATUS << i); + break; + case OMAP_EHCI_PORT_MODE_PHY: + if (pdata->single_ulpi_bypass) + break; + + if (i == 0) + reg &= ~OMAP_UHH_HOSTCONFIG_ULPI_P1_BYPASS; + else + reg &= ~(OMAP_UHH_HOSTCONFIG_ULPI_P2_BYPASS + << (i-1)); + break; + default: + if (pdata->single_ulpi_bypass) + break; + + if (i == 0) + reg |= OMAP_UHH_HOSTCONFIG_ULPI_P1_BYPASS; + else + reg |= OMAP_UHH_HOSTCONFIG_ULPI_P2_BYPASS + << (i-1); + break; + } + } + + if (pdata->single_ulpi_bypass) { + /* bypass ULPI only if none of the ports use PHY mode */ + reg |= OMAP_UHH_HOSTCONFIG_ULPI_BYPASS; + + for (i = 0; i < omap->nports; i++) { + if (is_ehci_phy_mode(pdata->port_mode[i])) { + reg &= ~OMAP_UHH_HOSTCONFIG_ULPI_BYPASS; + break; + } + } + } + + return reg; +} + +static unsigned omap_usbhs_rev2_hostconfig(struct usbhs_hcd_omap *omap, + unsigned reg) +{ + struct usbhs_omap_platform_data *pdata = omap->pdata; + int i; + + for (i = 0; i < omap->nports; i++) { + /* Clear port mode fields for PHY mode */ + reg &= ~(OMAP4_P1_MODE_CLEAR << 2 * i); + + if (is_ehci_tll_mode(pdata->port_mode[i]) || + (is_ohci_port(pdata->port_mode[i]))) + reg |= OMAP4_P1_MODE_TLL << 2 * i; + else if (is_ehci_hsic_mode(pdata->port_mode[i])) + reg |= OMAP4_P1_MODE_HSIC << 2 * i; + } + + return reg; +} + +static void omap_usbhs_init(struct device *dev) +{ + struct usbhs_hcd_omap *omap = dev_get_drvdata(dev); + unsigned reg; + + dev_dbg(dev, "starting TI HSUSB Controller\n"); + + pm_runtime_get_sync(dev); + + reg = usbhs_read(omap->uhh_base, OMAP_UHH_HOSTCONFIG); + /* setup ULPI bypass and burst configurations */ + reg |= (OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN + | OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN + | OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN); + reg |= OMAP4_UHH_HOSTCONFIG_APP_START_CLK; + reg &= ~OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN; + + switch (omap->usbhs_rev) { + case OMAP_USBHS_REV1: + reg = omap_usbhs_rev1_hostconfig(omap, reg); + break; + + case OMAP_USBHS_REV2: + reg = omap_usbhs_rev2_hostconfig(omap, reg); + break; + + default: /* newer revisions */ + reg = omap_usbhs_rev2_hostconfig(omap, reg); + break; + } + + usbhs_write(omap->uhh_base, OMAP_UHH_HOSTCONFIG, reg); + dev_dbg(dev, "UHH setup done, uhh_hostconfig=%x\n", reg); + + pm_runtime_put_sync(dev); +} + +static int usbhs_omap_get_dt_pdata(struct device *dev, + struct usbhs_omap_platform_data *pdata) +{ + int ret, i; + struct device_node *node = dev->of_node; + + ret = of_property_read_u32(node, "num-ports", &pdata->nports); + if (ret) + pdata->nports = 0; + + if (pdata->nports > OMAP3_HS_USB_PORTS) { + dev_warn(dev, "Too many num_ports <%d> in device tree. Max %d\n", + pdata->nports, OMAP3_HS_USB_PORTS); + return -ENODEV; + } + + /* get port modes */ + for (i = 0; i < OMAP3_HS_USB_PORTS; i++) { + char prop[11]; + const char *mode; + + pdata->port_mode[i] = OMAP_USBHS_PORT_MODE_UNUSED; + + snprintf(prop, sizeof(prop), "port%d-mode", i + 1); + ret = of_property_read_string(node, prop, &mode); + if (ret < 0) + continue; + + ret = omap_usbhs_get_dt_port_mode(mode); + if (ret < 0) { + dev_warn(dev, "Invalid port%d-mode \"%s\" in device tree\n", + i, mode); + return -ENODEV; + } + + dev_dbg(dev, "port%d-mode: %s -> %d\n", i, mode, ret); + pdata->port_mode[i] = ret; + } + + /* get flags */ + pdata->single_ulpi_bypass = of_property_read_bool(node, + "single-ulpi-bypass"); + + return 0; +} + +static const struct of_device_id usbhs_child_match_table[] = { + { .compatible = "ti,omap-ehci", }, + { .compatible = "ti,omap-ohci", }, + { } +}; + +/** + * usbhs_omap_probe - initialize TI-based HCDs + * + * Allocates basic resources for this USB host controller. + */ +static int usbhs_omap_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usbhs_omap_platform_data *pdata = dev_get_platdata(dev); + struct usbhs_hcd_omap *omap; + struct resource *res; + int ret = 0; + int i; + bool need_logic_fck; + + if (dev->of_node) { + /* For DT boot we populate platform data from OF node */ + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = usbhs_omap_get_dt_pdata(dev, pdata); + if (ret) + return ret; + + dev->platform_data = pdata; + } + + if (!pdata) { + dev_err(dev, "Missing platform data\n"); + return -ENODEV; + } + + if (pdata->nports > OMAP3_HS_USB_PORTS) { + dev_info(dev, "Too many num_ports <%d> in platform_data. Max %d\n", + pdata->nports, OMAP3_HS_USB_PORTS); + return -ENODEV; + } + + omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); + if (!omap) { + dev_err(dev, "Memory allocation failed\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + omap->uhh_base = devm_ioremap_resource(dev, res); + if (IS_ERR(omap->uhh_base)) + return PTR_ERR(omap->uhh_base); + + omap->pdata = pdata; + + /* Initialize the TLL subsystem */ + omap_tll_init(pdata); + + pm_runtime_enable(dev); + + platform_set_drvdata(pdev, omap); + pm_runtime_get_sync(dev); + + omap->usbhs_rev = usbhs_read(omap->uhh_base, OMAP_UHH_REVISION); + + /* we need to call runtime suspend before we update omap->nports + * to prevent unbalanced clk_disable() + */ + pm_runtime_put_sync(dev); + + /* + * If platform data contains nports then use that + * else make out number of ports from USBHS revision + */ + if (pdata->nports) { + omap->nports = pdata->nports; + } else { + switch (omap->usbhs_rev) { + case OMAP_USBHS_REV1: + omap->nports = 3; + break; + case OMAP_USBHS_REV2: + omap->nports = 2; + break; + default: + omap->nports = OMAP3_HS_USB_PORTS; + dev_dbg(dev, + "USB HOST Rev:0x%x not recognized, assuming %d ports\n", + omap->usbhs_rev, omap->nports); + break; + } + pdata->nports = omap->nports; + } + + i = sizeof(struct clk *) * omap->nports; + omap->utmi_clk = devm_kzalloc(dev, i, GFP_KERNEL); + omap->hsic480m_clk = devm_kzalloc(dev, i, GFP_KERNEL); + omap->hsic60m_clk = devm_kzalloc(dev, i, GFP_KERNEL); + + if (!omap->utmi_clk || !omap->hsic480m_clk || !omap->hsic60m_clk) { + dev_err(dev, "Memory allocation failed\n"); + ret = -ENOMEM; + goto err_mem; + } + + /* Set all clocks as invalid to begin with */ + omap->ehci_logic_fck = ERR_PTR(-ENODEV); + omap->init_60m_fclk = ERR_PTR(-ENODEV); + omap->utmi_p1_gfclk = ERR_PTR(-ENODEV); + omap->utmi_p2_gfclk = ERR_PTR(-ENODEV); + omap->xclk60mhsp1_ck = ERR_PTR(-ENODEV); + omap->xclk60mhsp2_ck = ERR_PTR(-ENODEV); + + for (i = 0; i < omap->nports; i++) { + omap->utmi_clk[i] = ERR_PTR(-ENODEV); + omap->hsic480m_clk[i] = ERR_PTR(-ENODEV); + omap->hsic60m_clk[i] = ERR_PTR(-ENODEV); + } + + /* for OMAP3 i.e. USBHS REV1 */ + if (omap->usbhs_rev == OMAP_USBHS_REV1) { + need_logic_fck = false; + for (i = 0; i < omap->nports; i++) { + if (is_ehci_phy_mode(pdata->port_mode[i]) || + is_ehci_tll_mode(pdata->port_mode[i]) || + is_ehci_hsic_mode(pdata->port_mode[i])) + + need_logic_fck |= true; + } + + if (need_logic_fck) { + omap->ehci_logic_fck = devm_clk_get(dev, + "usbhost_120m_fck"); + if (IS_ERR(omap->ehci_logic_fck)) { + ret = PTR_ERR(omap->ehci_logic_fck); + dev_err(dev, "usbhost_120m_fck failed:%d\n", + ret); + goto err_mem; + } + } + goto initialize; + } + + /* for OMAP4+ i.e. USBHS REV2+ */ + omap->utmi_p1_gfclk = devm_clk_get(dev, "utmi_p1_gfclk"); + if (IS_ERR(omap->utmi_p1_gfclk)) { + ret = PTR_ERR(omap->utmi_p1_gfclk); + dev_err(dev, "utmi_p1_gfclk failed error:%d\n", ret); + goto err_mem; + } + + omap->utmi_p2_gfclk = devm_clk_get(dev, "utmi_p2_gfclk"); + if (IS_ERR(omap->utmi_p2_gfclk)) { + ret = PTR_ERR(omap->utmi_p2_gfclk); + dev_err(dev, "utmi_p2_gfclk failed error:%d\n", ret); + goto err_mem; + } + + omap->xclk60mhsp1_ck = devm_clk_get(dev, "refclk_60m_ext_p1"); + if (IS_ERR(omap->xclk60mhsp1_ck)) { + ret = PTR_ERR(omap->xclk60mhsp1_ck); + dev_err(dev, "refclk_60m_ext_p1 failed error:%d\n", ret); + goto err_mem; + } + + omap->xclk60mhsp2_ck = devm_clk_get(dev, "refclk_60m_ext_p2"); + if (IS_ERR(omap->xclk60mhsp2_ck)) { + ret = PTR_ERR(omap->xclk60mhsp2_ck); + dev_err(dev, "refclk_60m_ext_p2 failed error:%d\n", ret); + goto err_mem; + } + + omap->init_60m_fclk = devm_clk_get(dev, "refclk_60m_int"); + if (IS_ERR(omap->init_60m_fclk)) { + ret = PTR_ERR(omap->init_60m_fclk); + dev_err(dev, "refclk_60m_int failed error:%d\n", ret); + goto err_mem; + } + + for (i = 0; i < omap->nports; i++) { + char clkname[30]; + + /* clock names are indexed from 1*/ + snprintf(clkname, sizeof(clkname), + "usb_host_hs_utmi_p%d_clk", i + 1); + + /* If a clock is not found we won't bail out as not all + * platforms have all clocks and we can function without + * them + */ + omap->utmi_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(omap->utmi_clk[i])) { + ret = PTR_ERR(omap->utmi_clk[i]); + dev_err(dev, "Failed to get clock : %s : %d\n", + clkname, ret); + goto err_mem; + } + + snprintf(clkname, sizeof(clkname), + "usb_host_hs_hsic480m_p%d_clk", i + 1); + omap->hsic480m_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(omap->hsic480m_clk[i])) { + ret = PTR_ERR(omap->hsic480m_clk[i]); + dev_err(dev, "Failed to get clock : %s : %d\n", + clkname, ret); + goto err_mem; + } + + snprintf(clkname, sizeof(clkname), + "usb_host_hs_hsic60m_p%d_clk", i + 1); + omap->hsic60m_clk[i] = devm_clk_get(dev, clkname); + if (IS_ERR(omap->hsic60m_clk[i])) { + ret = PTR_ERR(omap->hsic60m_clk[i]); + dev_err(dev, "Failed to get clock : %s : %d\n", + clkname, ret); + goto err_mem; + } + } + + if (is_ehci_phy_mode(pdata->port_mode[0])) { + ret = clk_set_parent(omap->utmi_p1_gfclk, + omap->xclk60mhsp1_ck); + if (ret != 0) { + dev_err(dev, "xclk60mhsp1_ck set parent failed: %d\n", + ret); + goto err_mem; + } + } else if (is_ehci_tll_mode(pdata->port_mode[0])) { + ret = clk_set_parent(omap->utmi_p1_gfclk, + omap->init_60m_fclk); + if (ret != 0) { + dev_err(dev, "P0 init_60m_fclk set parent failed: %d\n", + ret); + goto err_mem; + } + } + + if (is_ehci_phy_mode(pdata->port_mode[1])) { + ret = clk_set_parent(omap->utmi_p2_gfclk, + omap->xclk60mhsp2_ck); + if (ret != 0) { + dev_err(dev, "xclk60mhsp2_ck set parent failed: %d\n", + ret); + goto err_mem; + } + } else if (is_ehci_tll_mode(pdata->port_mode[1])) { + ret = clk_set_parent(omap->utmi_p2_gfclk, + omap->init_60m_fclk); + if (ret != 0) { + dev_err(dev, "P1 init_60m_fclk set parent failed: %d\n", + ret); + goto err_mem; + } + } + +initialize: + omap_usbhs_init(dev); + + if (dev->of_node) { + ret = of_platform_populate(dev->of_node, + usbhs_child_match_table, NULL, dev); + + if (ret) { + dev_err(dev, "Failed to create DT children: %d\n", ret); + goto err_mem; + } + + } else { + ret = omap_usbhs_alloc_children(pdev); + if (ret) { + dev_err(dev, "omap_usbhs_alloc_children failed: %d\n", + ret); + goto err_mem; + } + } + + return 0; + +err_mem: + pm_runtime_disable(dev); + + return ret; +} + +static int usbhs_omap_remove_child(struct device *dev, void *data) +{ + dev_info(dev, "unregistering\n"); + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +/** + * usbhs_omap_remove - shutdown processing for UHH & TLL HCDs + * @pdev: USB Host Controller being removed + * + * Reverses the effect of usbhs_omap_probe(). + */ +static int usbhs_omap_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + /* remove children */ + device_for_each_child(&pdev->dev, NULL, usbhs_omap_remove_child); + return 0; +} + +static const struct dev_pm_ops usbhsomap_dev_pm_ops = { + .runtime_suspend = usbhs_runtime_suspend, + .runtime_resume = usbhs_runtime_resume, +}; + +static const struct of_device_id usbhs_omap_dt_ids[] = { + { .compatible = "ti,usbhs-host" }, + { } +}; + +MODULE_DEVICE_TABLE(of, usbhs_omap_dt_ids); + + +static struct platform_driver usbhs_omap_driver = { + .driver = { + .name = (char *)usbhs_driver_name, + .pm = &usbhsomap_dev_pm_ops, + .of_match_table = usbhs_omap_dt_ids, + }, + .remove = usbhs_omap_remove, +}; + +MODULE_AUTHOR("Keshava Munegowda <keshava_mgowda@ti.com>"); +MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); +MODULE_ALIAS("platform:" USBHS_DRIVER_NAME); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("usb host common core driver for omap EHCI and OHCI"); + +static int __init omap_usbhs_drvinit(void) +{ + return platform_driver_probe(&usbhs_omap_driver, usbhs_omap_probe); +} + +/* + * init before ehci and ohci drivers; + * The usbhs core driver should be initialized much before + * the omap ehci and ohci probe functions are called. + * This usbhs core driver should be initialized after + * usb tll driver + */ +fs_initcall_sync(omap_usbhs_drvinit); + +static void __exit omap_usbhs_drvexit(void) +{ + platform_driver_unregister(&usbhs_omap_driver); +} +module_exit(omap_usbhs_drvexit); diff --git a/kernel/drivers/mfd/omap-usb-tll.c b/kernel/drivers/mfd/omap-usb-tll.c new file mode 100644 index 000000000..b7b3e8ee6 --- /dev/null +++ b/kernel/drivers/mfd/omap-usb-tll.c @@ -0,0 +1,484 @@ +/** + * omap-usb-tll.c - The USB TLL driver for OMAP EHCI & OHCI + * + * Copyright (C) 2012-2013 Texas Instruments Incorporated - http://www.ti.com + * Author: Keshava Munegowda <keshava_mgowda@ti.com> + * Author: Roger Quadros <rogerq@ti.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 of + * the License 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/platform_data/usb-omap.h> +#include <linux/of.h> + +#define USBTLL_DRIVER_NAME "usbhs_tll" + +/* TLL Register Set */ +#define OMAP_USBTLL_REVISION (0x00) +#define OMAP_USBTLL_SYSCONFIG (0x10) +#define OMAP_USBTLL_SYSCONFIG_CACTIVITY (1 << 8) +#define OMAP_USBTLL_SYSCONFIG_SIDLEMODE (1 << 3) +#define OMAP_USBTLL_SYSCONFIG_ENAWAKEUP (1 << 2) +#define OMAP_USBTLL_SYSCONFIG_SOFTRESET (1 << 1) +#define OMAP_USBTLL_SYSCONFIG_AUTOIDLE (1 << 0) + +#define OMAP_USBTLL_SYSSTATUS (0x14) +#define OMAP_USBTLL_SYSSTATUS_RESETDONE (1 << 0) + +#define OMAP_USBTLL_IRQSTATUS (0x18) +#define OMAP_USBTLL_IRQENABLE (0x1C) + +#define OMAP_TLL_SHARED_CONF (0x30) +#define OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN (1 << 6) +#define OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN (1 << 5) +#define OMAP_TLL_SHARED_CONF_USB_DIVRATION (1 << 2) +#define OMAP_TLL_SHARED_CONF_FCLK_REQ (1 << 1) +#define OMAP_TLL_SHARED_CONF_FCLK_IS_ON (1 << 0) + +#define OMAP_TLL_CHANNEL_CONF(num) (0x040 + 0x004 * num) +#define OMAP_TLL_CHANNEL_CONF_FSLSMODE_SHIFT 24 +#define OMAP_TLL_CHANNEL_CONF_DRVVBUS (1 << 16) +#define OMAP_TLL_CHANNEL_CONF_CHRGVBUS (1 << 15) +#define OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF (1 << 11) +#define OMAP_TLL_CHANNEL_CONF_ULPI_ULPIAUTOIDLE (1 << 10) +#define OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE (1 << 9) +#define OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE (1 << 8) +#define OMAP_TLL_CHANNEL_CONF_MODE_TRANSPARENT_UTMI (2 << 1) +#define OMAP_TLL_CHANNEL_CONF_CHANMODE_FSLS (1 << 1) +#define OMAP_TLL_CHANNEL_CONF_CHANEN (1 << 0) + +#define OMAP_TLL_FSLSMODE_6PIN_PHY_DAT_SE0 0x0 +#define OMAP_TLL_FSLSMODE_6PIN_PHY_DP_DM 0x1 +#define OMAP_TLL_FSLSMODE_3PIN_PHY 0x2 +#define OMAP_TLL_FSLSMODE_4PIN_PHY 0x3 +#define OMAP_TLL_FSLSMODE_6PIN_TLL_DAT_SE0 0x4 +#define OMAP_TLL_FSLSMODE_6PIN_TLL_DP_DM 0x5 +#define OMAP_TLL_FSLSMODE_3PIN_TLL 0x6 +#define OMAP_TLL_FSLSMODE_4PIN_TLL 0x7 +#define OMAP_TLL_FSLSMODE_2PIN_TLL_DAT_SE0 0xA +#define OMAP_TLL_FSLSMODE_2PIN_DAT_DP_DM 0xB + +#define OMAP_TLL_ULPI_FUNCTION_CTRL(num) (0x804 + 0x100 * num) +#define OMAP_TLL_ULPI_INTERFACE_CTRL(num) (0x807 + 0x100 * num) +#define OMAP_TLL_ULPI_OTG_CTRL(num) (0x80A + 0x100 * num) +#define OMAP_TLL_ULPI_INT_EN_RISE(num) (0x80D + 0x100 * num) +#define OMAP_TLL_ULPI_INT_EN_FALL(num) (0x810 + 0x100 * num) +#define OMAP_TLL_ULPI_INT_STATUS(num) (0x813 + 0x100 * num) +#define OMAP_TLL_ULPI_INT_LATCH(num) (0x814 + 0x100 * num) +#define OMAP_TLL_ULPI_DEBUG(num) (0x815 + 0x100 * num) +#define OMAP_TLL_ULPI_SCRATCH_REGISTER(num) (0x816 + 0x100 * num) + +#define OMAP_REV2_TLL_CHANNEL_COUNT 2 +#define OMAP_TLL_CHANNEL_COUNT 3 +#define OMAP_TLL_CHANNEL_1_EN_MASK (1 << 0) +#define OMAP_TLL_CHANNEL_2_EN_MASK (1 << 1) +#define OMAP_TLL_CHANNEL_3_EN_MASK (1 << 2) + +/* Values of USBTLL_REVISION - Note: these are not given in the TRM */ +#define OMAP_USBTLL_REV1 0x00000015 /* OMAP3 */ +#define OMAP_USBTLL_REV2 0x00000018 /* OMAP 3630 */ +#define OMAP_USBTLL_REV3 0x00000004 /* OMAP4 */ +#define OMAP_USBTLL_REV4 0x00000006 /* OMAP5 */ + +#define is_ehci_tll_mode(x) (x == OMAP_EHCI_PORT_MODE_TLL) + +/* only PHY and UNUSED modes don't need TLL */ +#define omap_usb_mode_needs_tll(x) ((x) != OMAP_USBHS_PORT_MODE_UNUSED &&\ + (x) != OMAP_EHCI_PORT_MODE_PHY) + +struct usbtll_omap { + int nch; /* num. of channels */ + struct clk **ch_clk; + void __iomem *base; +}; + +/*-------------------------------------------------------------------------*/ + +static const char usbtll_driver_name[] = USBTLL_DRIVER_NAME; +static struct device *tll_dev; +static DEFINE_SPINLOCK(tll_lock); /* serialize access to tll_dev */ + +/*-------------------------------------------------------------------------*/ + +static inline void usbtll_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline u32 usbtll_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +static inline void usbtll_writeb(void __iomem *base, u8 reg, u8 val) +{ + writeb_relaxed(val, base + reg); +} + +static inline u8 usbtll_readb(void __iomem *base, u8 reg) +{ + return readb_relaxed(base + reg); +} + +/*-------------------------------------------------------------------------*/ + +static bool is_ohci_port(enum usbhs_omap_port_mode pmode) +{ + switch (pmode) { + case OMAP_OHCI_PORT_MODE_PHY_6PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_PHY_6PIN_DPDM: + case OMAP_OHCI_PORT_MODE_PHY_3PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_PHY_4PIN_DPDM: + case OMAP_OHCI_PORT_MODE_TLL_6PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_TLL_6PIN_DPDM: + case OMAP_OHCI_PORT_MODE_TLL_3PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_TLL_4PIN_DPDM: + case OMAP_OHCI_PORT_MODE_TLL_2PIN_DATSE0: + case OMAP_OHCI_PORT_MODE_TLL_2PIN_DPDM: + return true; + + default: + return false; + } +} + +/* + * convert the port-mode enum to a value we can use in the FSLSMODE + * field of USBTLL_CHANNEL_CONF + */ +static unsigned ohci_omap3_fslsmode(enum usbhs_omap_port_mode mode) +{ + switch (mode) { + case OMAP_USBHS_PORT_MODE_UNUSED: + case OMAP_OHCI_PORT_MODE_PHY_6PIN_DATSE0: + return OMAP_TLL_FSLSMODE_6PIN_PHY_DAT_SE0; + + case OMAP_OHCI_PORT_MODE_PHY_6PIN_DPDM: + return OMAP_TLL_FSLSMODE_6PIN_PHY_DP_DM; + + case OMAP_OHCI_PORT_MODE_PHY_3PIN_DATSE0: + return OMAP_TLL_FSLSMODE_3PIN_PHY; + + case OMAP_OHCI_PORT_MODE_PHY_4PIN_DPDM: + return OMAP_TLL_FSLSMODE_4PIN_PHY; + + case OMAP_OHCI_PORT_MODE_TLL_6PIN_DATSE0: + return OMAP_TLL_FSLSMODE_6PIN_TLL_DAT_SE0; + + case OMAP_OHCI_PORT_MODE_TLL_6PIN_DPDM: + return OMAP_TLL_FSLSMODE_6PIN_TLL_DP_DM; + + case OMAP_OHCI_PORT_MODE_TLL_3PIN_DATSE0: + return OMAP_TLL_FSLSMODE_3PIN_TLL; + + case OMAP_OHCI_PORT_MODE_TLL_4PIN_DPDM: + return OMAP_TLL_FSLSMODE_4PIN_TLL; + + case OMAP_OHCI_PORT_MODE_TLL_2PIN_DATSE0: + return OMAP_TLL_FSLSMODE_2PIN_TLL_DAT_SE0; + + case OMAP_OHCI_PORT_MODE_TLL_2PIN_DPDM: + return OMAP_TLL_FSLSMODE_2PIN_DAT_DP_DM; + default: + pr_warn("Invalid port mode, using default\n"); + return OMAP_TLL_FSLSMODE_6PIN_PHY_DAT_SE0; + } +} + +/** + * usbtll_omap_probe - initialize TI-based HCDs + * + * Allocates basic resources for this USB host controller. + */ +static int usbtll_omap_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct usbtll_omap *tll; + int ret = 0; + int i, ver; + + dev_dbg(dev, "starting TI HSUSB TLL Controller\n"); + + tll = devm_kzalloc(dev, sizeof(struct usbtll_omap), GFP_KERNEL); + if (!tll) { + dev_err(dev, "Memory allocation failed\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tll->base = devm_ioremap_resource(dev, res); + if (IS_ERR(tll->base)) + return PTR_ERR(tll->base); + + platform_set_drvdata(pdev, tll); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + ver = usbtll_read(tll->base, OMAP_USBTLL_REVISION); + switch (ver) { + case OMAP_USBTLL_REV1: + case OMAP_USBTLL_REV4: + tll->nch = OMAP_TLL_CHANNEL_COUNT; + break; + case OMAP_USBTLL_REV2: + case OMAP_USBTLL_REV3: + tll->nch = OMAP_REV2_TLL_CHANNEL_COUNT; + break; + default: + tll->nch = OMAP_TLL_CHANNEL_COUNT; + dev_dbg(dev, + "USB TLL Rev : 0x%x not recognized, assuming %d channels\n", + ver, tll->nch); + break; + } + + tll->ch_clk = devm_kzalloc(dev, sizeof(struct clk *) * tll->nch, + GFP_KERNEL); + if (!tll->ch_clk) { + ret = -ENOMEM; + dev_err(dev, "Couldn't allocate memory for channel clocks\n"); + goto err_clk_alloc; + } + + for (i = 0; i < tll->nch; i++) { + char clkname[] = "usb_tll_hs_usb_chx_clk"; + + snprintf(clkname, sizeof(clkname), + "usb_tll_hs_usb_ch%d_clk", i); + tll->ch_clk[i] = clk_get(dev, clkname); + + if (IS_ERR(tll->ch_clk[i])) + dev_dbg(dev, "can't get clock : %s\n", clkname); + } + + pm_runtime_put_sync(dev); + /* only after this can omap_tll_enable/disable work */ + spin_lock(&tll_lock); + tll_dev = dev; + spin_unlock(&tll_lock); + + return 0; + +err_clk_alloc: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return ret; +} + +/** + * usbtll_omap_remove - shutdown processing for UHH & TLL HCDs + * @pdev: USB Host Controller being removed + * + * Reverses the effect of usbtll_omap_probe(). + */ +static int usbtll_omap_remove(struct platform_device *pdev) +{ + struct usbtll_omap *tll = platform_get_drvdata(pdev); + int i; + + spin_lock(&tll_lock); + tll_dev = NULL; + spin_unlock(&tll_lock); + + for (i = 0; i < tll->nch; i++) + if (!IS_ERR(tll->ch_clk[i])) + clk_put(tll->ch_clk[i]); + + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id usbtll_omap_dt_ids[] = { + { .compatible = "ti,usbhs-tll" }, + { } +}; + +MODULE_DEVICE_TABLE(of, usbtll_omap_dt_ids); + +static struct platform_driver usbtll_omap_driver = { + .driver = { + .name = (char *)usbtll_driver_name, + .of_match_table = usbtll_omap_dt_ids, + }, + .probe = usbtll_omap_probe, + .remove = usbtll_omap_remove, +}; + +int omap_tll_init(struct usbhs_omap_platform_data *pdata) +{ + int i; + bool needs_tll; + unsigned reg; + struct usbtll_omap *tll; + + if (!tll_dev) + return -ENODEV; + + pm_runtime_get_sync(tll_dev); + + spin_lock(&tll_lock); + tll = dev_get_drvdata(tll_dev); + needs_tll = false; + for (i = 0; i < tll->nch; i++) + needs_tll |= omap_usb_mode_needs_tll(pdata->port_mode[i]); + + if (needs_tll) { + void __iomem *base = tll->base; + + /* Program Common TLL register */ + reg = usbtll_read(base, OMAP_TLL_SHARED_CONF); + reg |= (OMAP_TLL_SHARED_CONF_FCLK_IS_ON + | OMAP_TLL_SHARED_CONF_USB_DIVRATION); + reg &= ~OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN; + reg &= ~OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN; + + usbtll_write(base, OMAP_TLL_SHARED_CONF, reg); + + /* Enable channels now */ + for (i = 0; i < tll->nch; i++) { + reg = usbtll_read(base, OMAP_TLL_CHANNEL_CONF(i)); + + if (is_ohci_port(pdata->port_mode[i])) { + reg |= ohci_omap3_fslsmode(pdata->port_mode[i]) + << OMAP_TLL_CHANNEL_CONF_FSLSMODE_SHIFT; + reg |= OMAP_TLL_CHANNEL_CONF_CHANMODE_FSLS; + } else if (pdata->port_mode[i] == + OMAP_EHCI_PORT_MODE_TLL) { + /* + * Disable AutoIdle, BitStuffing + * and use SDR Mode + */ + reg &= ~(OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE + | OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF + | OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE); + } else if (pdata->port_mode[i] == + OMAP_EHCI_PORT_MODE_HSIC) { + /* + * HSIC Mode requires UTMI port configurations + */ + reg |= OMAP_TLL_CHANNEL_CONF_DRVVBUS + | OMAP_TLL_CHANNEL_CONF_CHRGVBUS + | OMAP_TLL_CHANNEL_CONF_MODE_TRANSPARENT_UTMI + | OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF; + } else { + continue; + } + reg |= OMAP_TLL_CHANNEL_CONF_CHANEN; + usbtll_write(base, OMAP_TLL_CHANNEL_CONF(i), reg); + + usbtll_writeb(base, + OMAP_TLL_ULPI_SCRATCH_REGISTER(i), + 0xbe); + } + } + + spin_unlock(&tll_lock); + pm_runtime_put_sync(tll_dev); + + return 0; +} +EXPORT_SYMBOL_GPL(omap_tll_init); + +int omap_tll_enable(struct usbhs_omap_platform_data *pdata) +{ + int i; + struct usbtll_omap *tll; + + if (!tll_dev) + return -ENODEV; + + pm_runtime_get_sync(tll_dev); + + spin_lock(&tll_lock); + tll = dev_get_drvdata(tll_dev); + + for (i = 0; i < tll->nch; i++) { + if (omap_usb_mode_needs_tll(pdata->port_mode[i])) { + int r; + + if (IS_ERR(tll->ch_clk[i])) + continue; + + r = clk_prepare_enable(tll->ch_clk[i]); + if (r) { + dev_err(tll_dev, + "Error enabling ch %d clock: %d\n", i, r); + } + } + } + + spin_unlock(&tll_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(omap_tll_enable); + +int omap_tll_disable(struct usbhs_omap_platform_data *pdata) +{ + int i; + struct usbtll_omap *tll; + + if (!tll_dev) + return -ENODEV; + + spin_lock(&tll_lock); + tll = dev_get_drvdata(tll_dev); + + for (i = 0; i < tll->nch; i++) { + if (omap_usb_mode_needs_tll(pdata->port_mode[i])) { + if (!IS_ERR(tll->ch_clk[i])) + clk_disable_unprepare(tll->ch_clk[i]); + } + } + + spin_unlock(&tll_lock); + pm_runtime_put_sync(tll_dev); + + return 0; +} +EXPORT_SYMBOL_GPL(omap_tll_disable); + +MODULE_AUTHOR("Keshava Munegowda <keshava_mgowda@ti.com>"); +MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); +MODULE_ALIAS("platform:" USBHS_DRIVER_NAME); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("usb tll driver for TI OMAP EHCI and OHCI controllers"); + +static int __init omap_usbtll_drvinit(void) +{ + return platform_driver_register(&usbtll_omap_driver); +} + +/* + * init before usbhs core driver; + * The usbtll driver should be initialized before + * the usbhs core driver probe function is called. + */ +fs_initcall(omap_usbtll_drvinit); + +static void __exit omap_usbtll_drvexit(void) +{ + platform_driver_unregister(&usbtll_omap_driver); +} +module_exit(omap_usbtll_drvexit); diff --git a/kernel/drivers/mfd/omap-usb.h b/kernel/drivers/mfd/omap-usb.h new file mode 100644 index 000000000..2a508b6ae --- /dev/null +++ b/kernel/drivers/mfd/omap-usb.h @@ -0,0 +1,3 @@ +extern int omap_tll_init(struct usbhs_omap_platform_data *pdata); +extern int omap_tll_enable(struct usbhs_omap_platform_data *pdata); +extern int omap_tll_disable(struct usbhs_omap_platform_data *pdata); diff --git a/kernel/drivers/mfd/palmas.c b/kernel/drivers/mfd/palmas.c new file mode 100644 index 000000000..28cb048f4 --- /dev/null +++ b/kernel/drivers/mfd/palmas.c @@ -0,0 +1,744 @@ +/* + * TI Palmas MFD Driver + * + * Copyright 2011-2012 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/regmap.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/mfd/palmas.h> +#include <linux/of_device.h> + +static const struct regmap_config palmas_regmap_config[PALMAS_NUM_CLIENTS] = { + { + .reg_bits = 8, + .val_bits = 8, + .max_register = PALMAS_BASE_TO_REG(PALMAS_PU_PD_OD_BASE, + PALMAS_PRIMARY_SECONDARY_PAD3), + }, + { + .reg_bits = 8, + .val_bits = 8, + .max_register = PALMAS_BASE_TO_REG(PALMAS_GPADC_BASE, + PALMAS_GPADC_SMPS_VSEL_MONITORING), + }, + { + .reg_bits = 8, + .val_bits = 8, + .max_register = PALMAS_BASE_TO_REG(PALMAS_TRIM_GPADC_BASE, + PALMAS_GPADC_TRIM16), + }, +}; + +static const struct regmap_irq tps65917_irqs[] = { + /* INT1 IRQs */ + [TPS65917_RESERVED1] = { + .mask = TPS65917_RESERVED, + }, + [TPS65917_PWRON_IRQ] = { + .mask = TPS65917_INT1_STATUS_PWRON, + }, + [TPS65917_LONG_PRESS_KEY_IRQ] = { + .mask = TPS65917_INT1_STATUS_LONG_PRESS_KEY, + }, + [TPS65917_RESERVED2] = { + .mask = TPS65917_RESERVED, + }, + [TPS65917_PWRDOWN_IRQ] = { + .mask = TPS65917_INT1_STATUS_PWRDOWN, + }, + [TPS65917_HOTDIE_IRQ] = { + .mask = TPS65917_INT1_STATUS_HOTDIE, + }, + [TPS65917_VSYS_MON_IRQ] = { + .mask = TPS65917_INT1_STATUS_VSYS_MON, + }, + [TPS65917_RESERVED3] = { + .mask = TPS65917_RESERVED, + }, + /* INT2 IRQs*/ + [TPS65917_RESERVED4] = { + .mask = TPS65917_RESERVED, + .reg_offset = 1, + }, + [TPS65917_OTP_ERROR_IRQ] = { + .mask = TPS65917_INT2_STATUS_OTP_ERROR, + .reg_offset = 1, + }, + [TPS65917_WDT_IRQ] = { + .mask = TPS65917_INT2_STATUS_WDT, + .reg_offset = 1, + }, + [TPS65917_RESERVED5] = { + .mask = TPS65917_RESERVED, + .reg_offset = 1, + }, + [TPS65917_RESET_IN_IRQ] = { + .mask = TPS65917_INT2_STATUS_RESET_IN, + .reg_offset = 1, + }, + [TPS65917_FSD_IRQ] = { + .mask = TPS65917_INT2_STATUS_FSD, + .reg_offset = 1, + }, + [TPS65917_SHORT_IRQ] = { + .mask = TPS65917_INT2_STATUS_SHORT, + .reg_offset = 1, + }, + [TPS65917_RESERVED6] = { + .mask = TPS65917_RESERVED, + .reg_offset = 1, + }, + /* INT3 IRQs */ + [TPS65917_GPADC_AUTO_0_IRQ] = { + .mask = TPS65917_INT3_STATUS_GPADC_AUTO_0, + .reg_offset = 2, + }, + [TPS65917_GPADC_AUTO_1_IRQ] = { + .mask = TPS65917_INT3_STATUS_GPADC_AUTO_1, + .reg_offset = 2, + }, + [TPS65917_GPADC_EOC_SW_IRQ] = { + .mask = TPS65917_INT3_STATUS_GPADC_EOC_SW, + .reg_offset = 2, + }, + [TPS65917_RESREVED6] = { + .mask = TPS65917_RESERVED6, + .reg_offset = 2, + }, + [TPS65917_RESERVED7] = { + .mask = TPS65917_RESERVED, + .reg_offset = 2, + }, + [TPS65917_RESERVED8] = { + .mask = TPS65917_RESERVED, + .reg_offset = 2, + }, + [TPS65917_RESERVED9] = { + .mask = TPS65917_RESERVED, + .reg_offset = 2, + }, + [TPS65917_VBUS_IRQ] = { + .mask = TPS65917_INT3_STATUS_VBUS, + .reg_offset = 2, + }, + /* INT4 IRQs */ + [TPS65917_GPIO_0_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_0, + .reg_offset = 3, + }, + [TPS65917_GPIO_1_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_1, + .reg_offset = 3, + }, + [TPS65917_GPIO_2_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_2, + .reg_offset = 3, + }, + [TPS65917_GPIO_3_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_3, + .reg_offset = 3, + }, + [TPS65917_GPIO_4_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_4, + .reg_offset = 3, + }, + [TPS65917_GPIO_5_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_5, + .reg_offset = 3, + }, + [TPS65917_GPIO_6_IRQ] = { + .mask = TPS65917_INT4_STATUS_GPIO_6, + .reg_offset = 3, + }, + [TPS65917_RESERVED10] = { + .mask = TPS65917_RESERVED10, + .reg_offset = 3, + }, +}; + +static const struct regmap_irq palmas_irqs[] = { + /* INT1 IRQs */ + [PALMAS_CHARG_DET_N_VBUS_OVV_IRQ] = { + .mask = PALMAS_INT1_STATUS_CHARG_DET_N_VBUS_OVV, + }, + [PALMAS_PWRON_IRQ] = { + .mask = PALMAS_INT1_STATUS_PWRON, + }, + [PALMAS_LONG_PRESS_KEY_IRQ] = { + .mask = PALMAS_INT1_STATUS_LONG_PRESS_KEY, + }, + [PALMAS_RPWRON_IRQ] = { + .mask = PALMAS_INT1_STATUS_RPWRON, + }, + [PALMAS_PWRDOWN_IRQ] = { + .mask = PALMAS_INT1_STATUS_PWRDOWN, + }, + [PALMAS_HOTDIE_IRQ] = { + .mask = PALMAS_INT1_STATUS_HOTDIE, + }, + [PALMAS_VSYS_MON_IRQ] = { + .mask = PALMAS_INT1_STATUS_VSYS_MON, + }, + [PALMAS_VBAT_MON_IRQ] = { + .mask = PALMAS_INT1_STATUS_VBAT_MON, + }, + /* INT2 IRQs*/ + [PALMAS_RTC_ALARM_IRQ] = { + .mask = PALMAS_INT2_STATUS_RTC_ALARM, + .reg_offset = 1, + }, + [PALMAS_RTC_TIMER_IRQ] = { + .mask = PALMAS_INT2_STATUS_RTC_TIMER, + .reg_offset = 1, + }, + [PALMAS_WDT_IRQ] = { + .mask = PALMAS_INT2_STATUS_WDT, + .reg_offset = 1, + }, + [PALMAS_BATREMOVAL_IRQ] = { + .mask = PALMAS_INT2_STATUS_BATREMOVAL, + .reg_offset = 1, + }, + [PALMAS_RESET_IN_IRQ] = { + .mask = PALMAS_INT2_STATUS_RESET_IN, + .reg_offset = 1, + }, + [PALMAS_FBI_BB_IRQ] = { + .mask = PALMAS_INT2_STATUS_FBI_BB, + .reg_offset = 1, + }, + [PALMAS_SHORT_IRQ] = { + .mask = PALMAS_INT2_STATUS_SHORT, + .reg_offset = 1, + }, + [PALMAS_VAC_ACOK_IRQ] = { + .mask = PALMAS_INT2_STATUS_VAC_ACOK, + .reg_offset = 1, + }, + /* INT3 IRQs */ + [PALMAS_GPADC_AUTO_0_IRQ] = { + .mask = PALMAS_INT3_STATUS_GPADC_AUTO_0, + .reg_offset = 2, + }, + [PALMAS_GPADC_AUTO_1_IRQ] = { + .mask = PALMAS_INT3_STATUS_GPADC_AUTO_1, + .reg_offset = 2, + }, + [PALMAS_GPADC_EOC_SW_IRQ] = { + .mask = PALMAS_INT3_STATUS_GPADC_EOC_SW, + .reg_offset = 2, + }, + [PALMAS_GPADC_EOC_RT_IRQ] = { + .mask = PALMAS_INT3_STATUS_GPADC_EOC_RT, + .reg_offset = 2, + }, + [PALMAS_ID_OTG_IRQ] = { + .mask = PALMAS_INT3_STATUS_ID_OTG, + .reg_offset = 2, + }, + [PALMAS_ID_IRQ] = { + .mask = PALMAS_INT3_STATUS_ID, + .reg_offset = 2, + }, + [PALMAS_VBUS_OTG_IRQ] = { + .mask = PALMAS_INT3_STATUS_VBUS_OTG, + .reg_offset = 2, + }, + [PALMAS_VBUS_IRQ] = { + .mask = PALMAS_INT3_STATUS_VBUS, + .reg_offset = 2, + }, + /* INT4 IRQs */ + [PALMAS_GPIO_0_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_0, + .reg_offset = 3, + }, + [PALMAS_GPIO_1_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_1, + .reg_offset = 3, + }, + [PALMAS_GPIO_2_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_2, + .reg_offset = 3, + }, + [PALMAS_GPIO_3_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_3, + .reg_offset = 3, + }, + [PALMAS_GPIO_4_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_4, + .reg_offset = 3, + }, + [PALMAS_GPIO_5_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_5, + .reg_offset = 3, + }, + [PALMAS_GPIO_6_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_6, + .reg_offset = 3, + }, + [PALMAS_GPIO_7_IRQ] = { + .mask = PALMAS_INT4_STATUS_GPIO_7, + .reg_offset = 3, + }, +}; + +static struct regmap_irq_chip palmas_irq_chip = { + .name = "palmas", + .irqs = palmas_irqs, + .num_irqs = ARRAY_SIZE(palmas_irqs), + + .num_regs = 4, + .irq_reg_stride = 5, + .status_base = PALMAS_BASE_TO_REG(PALMAS_INTERRUPT_BASE, + PALMAS_INT1_STATUS), + .mask_base = PALMAS_BASE_TO_REG(PALMAS_INTERRUPT_BASE, + PALMAS_INT1_MASK), +}; + +static struct regmap_irq_chip tps65917_irq_chip = { + .name = "tps65917", + .irqs = tps65917_irqs, + .num_irqs = ARRAY_SIZE(tps65917_irqs), + + .num_regs = 4, + .irq_reg_stride = 5, + .status_base = PALMAS_BASE_TO_REG(PALMAS_INTERRUPT_BASE, + PALMAS_INT1_STATUS), + .mask_base = PALMAS_BASE_TO_REG(PALMAS_INTERRUPT_BASE, + PALMAS_INT1_MASK), +}; + +int palmas_ext_control_req_config(struct palmas *palmas, + enum palmas_external_requestor_id id, int ext_ctrl, bool enable) +{ + struct palmas_pmic_driver_data *pmic_ddata = palmas->pmic_ddata; + int preq_mask_bit = 0; + int reg_add = 0; + int bit_pos, ret; + + if (!(ext_ctrl & PALMAS_EXT_REQ)) + return 0; + + if (id >= PALMAS_EXTERNAL_REQSTR_ID_MAX) + return 0; + + if (ext_ctrl & PALMAS_EXT_CONTROL_NSLEEP) { + reg_add = PALMAS_NSLEEP_RES_ASSIGN; + preq_mask_bit = 0; + } else if (ext_ctrl & PALMAS_EXT_CONTROL_ENABLE1) { + reg_add = PALMAS_ENABLE1_RES_ASSIGN; + preq_mask_bit = 1; + } else if (ext_ctrl & PALMAS_EXT_CONTROL_ENABLE2) { + reg_add = PALMAS_ENABLE2_RES_ASSIGN; + preq_mask_bit = 2; + } + + bit_pos = pmic_ddata->sleep_req_info[id].bit_pos; + reg_add += pmic_ddata->sleep_req_info[id].reg_offset; + if (enable) + ret = palmas_update_bits(palmas, PALMAS_RESOURCE_BASE, + reg_add, BIT(bit_pos), BIT(bit_pos)); + else + ret = palmas_update_bits(palmas, PALMAS_RESOURCE_BASE, + reg_add, BIT(bit_pos), 0); + if (ret < 0) { + dev_err(palmas->dev, "Resource reg 0x%02x update failed %d\n", + reg_add, ret); + return ret; + } + + /* Unmask the PREQ */ + ret = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE, + PALMAS_POWER_CTRL, BIT(preq_mask_bit), 0); + if (ret < 0) { + dev_err(palmas->dev, "POWER_CTRL register update failed %d\n", + ret); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(palmas_ext_control_req_config); + +static int palmas_set_pdata_irq_flag(struct i2c_client *i2c, + struct palmas_platform_data *pdata) +{ + struct irq_data *irq_data = irq_get_irq_data(i2c->irq); + if (!irq_data) { + dev_err(&i2c->dev, "Invalid IRQ: %d\n", i2c->irq); + return -EINVAL; + } + + pdata->irq_flags = irqd_get_trigger_type(irq_data); + dev_info(&i2c->dev, "Irq flag is 0x%08x\n", pdata->irq_flags); + return 0; +} + +static void palmas_dt_to_pdata(struct i2c_client *i2c, + struct palmas_platform_data *pdata) +{ + struct device_node *node = i2c->dev.of_node; + int ret; + u32 prop; + + ret = of_property_read_u32(node, "ti,mux-pad1", &prop); + if (!ret) { + pdata->mux_from_pdata = 1; + pdata->pad1 = prop; + } + + ret = of_property_read_u32(node, "ti,mux-pad2", &prop); + if (!ret) { + pdata->mux_from_pdata = 1; + pdata->pad2 = prop; + } + + /* The default for this register is all masked */ + ret = of_property_read_u32(node, "ti,power-ctrl", &prop); + if (!ret) + pdata->power_ctrl = prop; + else + pdata->power_ctrl = PALMAS_POWER_CTRL_NSLEEP_MASK | + PALMAS_POWER_CTRL_ENABLE1_MASK | + PALMAS_POWER_CTRL_ENABLE2_MASK; + if (i2c->irq) + palmas_set_pdata_irq_flag(i2c, pdata); + + pdata->pm_off = of_property_read_bool(node, + "ti,system-power-controller"); +} + +static struct palmas *palmas_dev; +static void palmas_power_off(void) +{ + unsigned int addr; + int ret, slave; + + if (!palmas_dev) + return; + + slave = PALMAS_BASE_TO_SLAVE(PALMAS_PMU_CONTROL_BASE); + addr = PALMAS_BASE_TO_REG(PALMAS_PMU_CONTROL_BASE, PALMAS_DEV_CTRL); + + ret = regmap_update_bits( + palmas_dev->regmap[slave], + addr, + PALMAS_DEV_CTRL_DEV_ON, + 0); + + if (ret) + pr_err("%s: Unable to write to DEV_CTRL_DEV_ON: %d\n", + __func__, ret); +} + +static unsigned int palmas_features = PALMAS_PMIC_FEATURE_SMPS10_BOOST; +static unsigned int tps659038_features; + +struct palmas_driver_data { + unsigned int *features; + struct regmap_irq_chip *irq_chip; +}; + +static struct palmas_driver_data palmas_data = { + .features = &palmas_features, + .irq_chip = &palmas_irq_chip, +}; + +static struct palmas_driver_data tps659038_data = { + .features = &tps659038_features, + .irq_chip = &palmas_irq_chip, +}; + +static struct palmas_driver_data tps65917_data = { + .features = &tps659038_features, + .irq_chip = &tps65917_irq_chip, +}; + +static const struct of_device_id of_palmas_match_tbl[] = { + { + .compatible = "ti,palmas", + .data = &palmas_data, + }, + { + .compatible = "ti,tps659038", + .data = &tps659038_data, + }, + { + .compatible = "ti,tps65917", + .data = &tps65917_data, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_palmas_match_tbl); + +static int palmas_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct palmas *palmas; + struct palmas_platform_data *pdata; + struct palmas_driver_data *driver_data; + struct device_node *node = i2c->dev.of_node; + int ret = 0, i; + unsigned int reg, addr; + int slave; + const struct of_device_id *match; + + pdata = dev_get_platdata(&i2c->dev); + + if (node && !pdata) { + pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); + + if (!pdata) + return -ENOMEM; + + palmas_dt_to_pdata(i2c, pdata); + } + + if (!pdata) + return -EINVAL; + + palmas = devm_kzalloc(&i2c->dev, sizeof(struct palmas), GFP_KERNEL); + if (palmas == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, palmas); + palmas->dev = &i2c->dev; + palmas->irq = i2c->irq; + + match = of_match_device(of_palmas_match_tbl, &i2c->dev); + + if (!match) + return -ENODATA; + + driver_data = (struct palmas_driver_data *)match->data; + palmas->features = *driver_data->features; + + for (i = 0; i < PALMAS_NUM_CLIENTS; i++) { + if (i == 0) + palmas->i2c_clients[i] = i2c; + else { + palmas->i2c_clients[i] = + i2c_new_dummy(i2c->adapter, + i2c->addr + i); + if (!palmas->i2c_clients[i]) { + dev_err(palmas->dev, + "can't attach client %d\n", i); + ret = -ENOMEM; + goto err_i2c; + } + palmas->i2c_clients[i]->dev.of_node = of_node_get(node); + } + palmas->regmap[i] = devm_regmap_init_i2c(palmas->i2c_clients[i], + &palmas_regmap_config[i]); + if (IS_ERR(palmas->regmap[i])) { + ret = PTR_ERR(palmas->regmap[i]); + dev_err(palmas->dev, + "Failed to allocate regmap %d, err: %d\n", + i, ret); + goto err_i2c; + } + } + + if (!palmas->irq) { + dev_warn(palmas->dev, "IRQ missing: skipping irq request\n"); + goto no_irq; + } + + /* Change interrupt line output polarity */ + if (pdata->irq_flags & IRQ_TYPE_LEVEL_HIGH) + reg = PALMAS_POLARITY_CTRL_INT_POLARITY; + else + reg = 0; + ret = palmas_update_bits(palmas, PALMAS_PU_PD_OD_BASE, + PALMAS_POLARITY_CTRL, PALMAS_POLARITY_CTRL_INT_POLARITY, + reg); + if (ret < 0) { + dev_err(palmas->dev, "POLARITY_CTRL updat failed: %d\n", ret); + goto err_i2c; + } + + /* Change IRQ into clear on read mode for efficiency */ + slave = PALMAS_BASE_TO_SLAVE(PALMAS_INTERRUPT_BASE); + addr = PALMAS_BASE_TO_REG(PALMAS_INTERRUPT_BASE, PALMAS_INT_CTRL); + reg = PALMAS_INT_CTRL_INT_CLEAR; + + regmap_write(palmas->regmap[slave], addr, reg); + + ret = regmap_add_irq_chip(palmas->regmap[slave], palmas->irq, + IRQF_ONESHOT | pdata->irq_flags, 0, + driver_data->irq_chip, &palmas->irq_data); + if (ret < 0) + goto err_i2c; + +no_irq: + slave = PALMAS_BASE_TO_SLAVE(PALMAS_PU_PD_OD_BASE); + addr = PALMAS_BASE_TO_REG(PALMAS_PU_PD_OD_BASE, + PALMAS_PRIMARY_SECONDARY_PAD1); + + if (pdata->mux_from_pdata) { + reg = pdata->pad1; + ret = regmap_write(palmas->regmap[slave], addr, reg); + if (ret) + goto err_irq; + } else { + ret = regmap_read(palmas->regmap[slave], addr, ®); + if (ret) + goto err_irq; + } + + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_0)) + palmas->gpio_muxed |= PALMAS_GPIO_0_MUXED; + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK)) + palmas->gpio_muxed |= PALMAS_GPIO_1_MUXED; + else if ((reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK) == + (2 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_SHIFT)) + palmas->led_muxed |= PALMAS_LED1_MUXED; + else if ((reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK) == + (3 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_SHIFT)) + palmas->pwm_muxed |= PALMAS_PWM1_MUXED; + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_2_MASK)) + palmas->gpio_muxed |= PALMAS_GPIO_2_MUXED; + else if ((reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_2_MASK) == + (2 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_2_SHIFT)) + palmas->led_muxed |= PALMAS_LED2_MUXED; + else if ((reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_2_MASK) == + (3 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_2_SHIFT)) + palmas->pwm_muxed |= PALMAS_PWM2_MUXED; + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_3)) + palmas->gpio_muxed |= PALMAS_GPIO_3_MUXED; + + addr = PALMAS_BASE_TO_REG(PALMAS_PU_PD_OD_BASE, + PALMAS_PRIMARY_SECONDARY_PAD2); + + if (pdata->mux_from_pdata) { + reg = pdata->pad2; + ret = regmap_write(palmas->regmap[slave], addr, reg); + if (ret) + goto err_irq; + } else { + ret = regmap_read(palmas->regmap[slave], addr, ®); + if (ret) + goto err_irq; + } + + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD2_GPIO_4)) + palmas->gpio_muxed |= PALMAS_GPIO_4_MUXED; + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD2_GPIO_5_MASK)) + palmas->gpio_muxed |= PALMAS_GPIO_5_MUXED; + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD2_GPIO_6)) + palmas->gpio_muxed |= PALMAS_GPIO_6_MUXED; + if (!(reg & PALMAS_PRIMARY_SECONDARY_PAD2_GPIO_7_MASK)) + palmas->gpio_muxed |= PALMAS_GPIO_7_MUXED; + + dev_info(palmas->dev, "Muxing GPIO %x, PWM %x, LED %x\n", + palmas->gpio_muxed, palmas->pwm_muxed, + palmas->led_muxed); + + reg = pdata->power_ctrl; + + slave = PALMAS_BASE_TO_SLAVE(PALMAS_PMU_CONTROL_BASE); + addr = PALMAS_BASE_TO_REG(PALMAS_PMU_CONTROL_BASE, PALMAS_POWER_CTRL); + + ret = regmap_write(palmas->regmap[slave], addr, reg); + if (ret) + goto err_irq; + + /* + * If we are probing with DT do this the DT way and return here + * otherwise continue and add devices using mfd helpers. + */ + if (node) { + ret = of_platform_populate(node, NULL, NULL, &i2c->dev); + if (ret < 0) { + goto err_irq; + } else if (pdata->pm_off && !pm_power_off) { + palmas_dev = palmas; + pm_power_off = palmas_power_off; + } + } + + return ret; + +err_irq: + regmap_del_irq_chip(palmas->irq, palmas->irq_data); +err_i2c: + for (i = 1; i < PALMAS_NUM_CLIENTS; i++) { + if (palmas->i2c_clients[i]) + i2c_unregister_device(palmas->i2c_clients[i]); + } + return ret; +} + +static int palmas_i2c_remove(struct i2c_client *i2c) +{ + struct palmas *palmas = i2c_get_clientdata(i2c); + int i; + + regmap_del_irq_chip(palmas->irq, palmas->irq_data); + + for (i = 1; i < PALMAS_NUM_CLIENTS; i++) { + if (palmas->i2c_clients[i]) + i2c_unregister_device(palmas->i2c_clients[i]); + } + + if (palmas == palmas_dev) { + pm_power_off = NULL; + palmas_dev = NULL; + } + + return 0; +} + +static const struct i2c_device_id palmas_i2c_id[] = { + { "palmas", }, + { "twl6035", }, + { "twl6037", }, + { "tps65913", }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(i2c, palmas_i2c_id); + +static struct i2c_driver palmas_i2c_driver = { + .driver = { + .name = "palmas", + .of_match_table = of_palmas_match_tbl, + .owner = THIS_MODULE, + }, + .probe = palmas_i2c_probe, + .remove = palmas_i2c_remove, + .id_table = palmas_i2c_id, +}; + +static int __init palmas_i2c_init(void) +{ + return i2c_add_driver(&palmas_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(palmas_i2c_init); + +static void __exit palmas_i2c_exit(void) +{ + i2c_del_driver(&palmas_i2c_driver); +} +module_exit(palmas_i2c_exit); + +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("Palmas chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/pcf50633-adc.c b/kernel/drivers/mfd/pcf50633-adc.c new file mode 100644 index 000000000..c1984b0d1 --- /dev/null +++ b/kernel/drivers/mfd/pcf50633-adc.c @@ -0,0 +1,256 @@ +/* NXP PCF50633 ADC Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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. + * + * NOTE: This driver does not yet support subtractive ADC mode, which means + * you can do only one measurement per read request. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/completion.h> + +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/adc.h> + +struct pcf50633_adc_request { + int mux; + int avg; + void (*callback)(struct pcf50633 *, void *, int); + void *callback_param; +}; + +struct pcf50633_adc_sync_request { + int result; + struct completion completion; +}; + +#define PCF50633_MAX_ADC_FIFO_DEPTH 8 + +struct pcf50633_adc { + struct pcf50633 *pcf; + + /* Private stuff */ + struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH]; + int queue_head; + int queue_tail; + struct mutex queue_mutex; +}; + +static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf) +{ + return platform_get_drvdata(pcf->adc_pdev); +} + +static void adc_setup(struct pcf50633 *pcf, int channel, int avg) +{ + channel &= PCF50633_ADCC1_ADCMUX_MASK; + + /* kill ratiometric, but enable ACCSW biasing */ + pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01); + + /* start ADC conversion on selected channel */ + pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg | + PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT); +} + +static void trigger_next_adc_job_if_any(struct pcf50633 *pcf) +{ + struct pcf50633_adc *adc = __to_adc(pcf); + int head; + + head = adc->queue_head; + + if (!adc->queue[head]) + return; + + adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg); +} + +static int +adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) +{ + struct pcf50633_adc *adc = __to_adc(pcf); + int head, tail; + + mutex_lock(&adc->queue_mutex); + + head = adc->queue_head; + tail = adc->queue_tail; + + if (adc->queue[tail]) { + mutex_unlock(&adc->queue_mutex); + dev_err(pcf->dev, "ADC queue is full, dropping request\n"); + return -EBUSY; + } + + adc->queue[tail] = req; + if (head == tail) + trigger_next_adc_job_if_any(pcf); + adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); + + mutex_unlock(&adc->queue_mutex); + + return 0; +} + +static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, + int result) +{ + struct pcf50633_adc_sync_request *req = param; + + req->result = result; + complete(&req->completion); +} + +int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) +{ + struct pcf50633_adc_sync_request req; + int ret; + + init_completion(&req.completion); + + ret = pcf50633_adc_async_read(pcf, mux, avg, + pcf50633_adc_sync_read_callback, &req); + if (ret) + return ret; + + wait_for_completion(&req.completion); + + return req.result; +} +EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); + +int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg, + void (*callback)(struct pcf50633 *, void *, int), + void *callback_param) +{ + struct pcf50633_adc_request *req; + + /* req is freed when the result is ready, in interrupt handler */ + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->mux = mux; + req->avg = avg; + req->callback = callback; + req->callback_param = callback_param; + + return adc_enqueue_request(pcf, req); +} +EXPORT_SYMBOL_GPL(pcf50633_adc_async_read); + +static int adc_result(struct pcf50633 *pcf) +{ + u8 adcs1, adcs3; + u16 result; + + adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1); + adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3); + result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK); + + dev_dbg(pcf->dev, "adc result = %d\n", result); + + return result; +} + +static void pcf50633_adc_irq(int irq, void *data) +{ + struct pcf50633_adc *adc = data; + struct pcf50633 *pcf = adc->pcf; + struct pcf50633_adc_request *req; + int head, res; + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + req = adc->queue[head]; + if (WARN_ON(!req)) { + dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty!\n"); + mutex_unlock(&adc->queue_mutex); + return; + } + adc->queue[head] = NULL; + adc->queue_head = (head + 1) & + (PCF50633_MAX_ADC_FIFO_DEPTH - 1); + + res = adc_result(pcf); + trigger_next_adc_job_if_any(pcf); + + mutex_unlock(&adc->queue_mutex); + + req->callback(pcf, req->callback_param, res); + kfree(req); +} + +static int pcf50633_adc_probe(struct platform_device *pdev) +{ + struct pcf50633_adc *adc; + + adc = devm_kzalloc(&pdev->dev, sizeof(*adc), GFP_KERNEL); + if (!adc) + return -ENOMEM; + + adc->pcf = dev_to_pcf50633(pdev->dev.parent); + platform_set_drvdata(pdev, adc); + + pcf50633_register_irq(adc->pcf, PCF50633_IRQ_ADCRDY, + pcf50633_adc_irq, adc); + + mutex_init(&adc->queue_mutex); + + return 0; +} + +static int pcf50633_adc_remove(struct platform_device *pdev) +{ + struct pcf50633_adc *adc = platform_get_drvdata(pdev); + int i, head; + + pcf50633_free_irq(adc->pcf, PCF50633_IRQ_ADCRDY); + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + if (WARN_ON(adc->queue[head])) + dev_err(adc->pcf->dev, + "adc driver removed with request pending\n"); + + for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++) + kfree(adc->queue[i]); + + mutex_unlock(&adc->queue_mutex); + + return 0; +} + +static struct platform_driver pcf50633_adc_driver = { + .driver = { + .name = "pcf50633-adc", + }, + .probe = pcf50633_adc_probe, + .remove = pcf50633_adc_remove, +}; + +module_platform_driver(pcf50633_adc_driver); + +MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); +MODULE_DESCRIPTION("PCF50633 adc driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-adc"); + diff --git a/kernel/drivers/mfd/pcf50633-core.c b/kernel/drivers/mfd/pcf50633-core.c new file mode 100644 index 000000000..6155d123a --- /dev/null +++ b/kernel/drivers/mfd/pcf50633-core.c @@ -0,0 +1,329 @@ +/* NXP PCF50633 Power Management Unit (PMU) driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * 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/device.h> +#include <linux/sysfs.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/err.h> + +#include <linux/mfd/pcf50633/core.h> + +/* Read a block of up to 32 regs */ +int pcf50633_read_block(struct pcf50633 *pcf, u8 reg, + int nr_regs, u8 *data) +{ + int ret; + + ret = regmap_raw_read(pcf->regmap, reg, data, nr_regs); + if (ret != 0) + return ret; + + return nr_regs; +} +EXPORT_SYMBOL_GPL(pcf50633_read_block); + +/* Write a block of up to 32 regs */ +int pcf50633_write_block(struct pcf50633 *pcf , u8 reg, + int nr_regs, u8 *data) +{ + return regmap_raw_write(pcf->regmap, reg, data, nr_regs); +} +EXPORT_SYMBOL_GPL(pcf50633_write_block); + +u8 pcf50633_reg_read(struct pcf50633 *pcf, u8 reg) +{ + unsigned int val; + int ret; + + ret = regmap_read(pcf->regmap, reg, &val); + if (ret < 0) + return -1; + + return val; +} +EXPORT_SYMBOL_GPL(pcf50633_reg_read); + +int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val) +{ + return regmap_write(pcf->regmap, reg, val); +} +EXPORT_SYMBOL_GPL(pcf50633_reg_write); + +int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val) +{ + return regmap_update_bits(pcf->regmap, reg, mask, val); +} +EXPORT_SYMBOL_GPL(pcf50633_reg_set_bit_mask); + +int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 val) +{ + return regmap_update_bits(pcf->regmap, reg, val, 0); +} +EXPORT_SYMBOL_GPL(pcf50633_reg_clear_bits); + +/* sysfs attributes */ +static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pcf50633 *pcf = dev_get_drvdata(dev); + u8 dump[16]; + int n, n1, idx = 0; + char *buf1 = buf; + static u8 address_no_read[] = { /* must be ascending */ + PCF50633_REG_INT1, + PCF50633_REG_INT2, + PCF50633_REG_INT3, + PCF50633_REG_INT4, + PCF50633_REG_INT5, + 0 /* terminator */ + }; + + for (n = 0; n < 256; n += sizeof(dump)) { + for (n1 = 0; n1 < sizeof(dump); n1++) + if (n == address_no_read[idx]) { + idx++; + dump[n1] = 0x00; + } else + dump[n1] = pcf50633_reg_read(pcf, n + n1); + + buf1 += sprintf(buf1, "%*ph\n", (int)sizeof(dump), dump); + } + + return buf1 - buf; +} +static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL); + +static ssize_t show_resume_reason(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pcf50633 *pcf = dev_get_drvdata(dev); + int n; + + n = sprintf(buf, "%02x%02x%02x%02x%02x\n", + pcf->resume_reason[0], + pcf->resume_reason[1], + pcf->resume_reason[2], + pcf->resume_reason[3], + pcf->resume_reason[4]); + + return n; +} +static DEVICE_ATTR(resume_reason, 0400, show_resume_reason, NULL); + +static struct attribute *pcf_sysfs_entries[] = { + &dev_attr_dump_regs.attr, + &dev_attr_resume_reason.attr, + NULL, +}; + +static struct attribute_group pcf_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pcf_sysfs_entries, +}; + +static void +pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name, + struct platform_device **pdev) +{ + int ret; + + *pdev = platform_device_alloc(name, -1); + if (!*pdev) { + dev_err(pcf->dev, "Falied to allocate %s\n", name); + return; + } + + (*pdev)->dev.parent = pcf->dev; + + ret = platform_device_add(*pdev); + if (ret) { + dev_err(pcf->dev, "Failed to register %s: %d\n", name, ret); + platform_device_put(*pdev); + *pdev = NULL; + } +} + +#ifdef CONFIG_PM_SLEEP +static int pcf50633_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633 *pcf = i2c_get_clientdata(client); + + return pcf50633_irq_suspend(pcf); +} + +static int pcf50633_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf50633 *pcf = i2c_get_clientdata(client); + + return pcf50633_irq_resume(pcf); +} +#endif + +static SIMPLE_DEV_PM_OPS(pcf50633_pm, pcf50633_suspend, pcf50633_resume); + +static const struct regmap_config pcf50633_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int pcf50633_probe(struct i2c_client *client, + const struct i2c_device_id *ids) +{ + struct pcf50633 *pcf; + struct platform_device *pdev; + struct pcf50633_platform_data *pdata = dev_get_platdata(&client->dev); + int i, j, ret; + int version, variant; + + if (!client->irq) { + dev_err(&client->dev, "Missing IRQ\n"); + return -ENOENT; + } + + pcf = devm_kzalloc(&client->dev, sizeof(*pcf), GFP_KERNEL); + if (!pcf) + return -ENOMEM; + + i2c_set_clientdata(client, pcf); + pcf->dev = &client->dev; + pcf->pdata = pdata; + + mutex_init(&pcf->lock); + + pcf->regmap = devm_regmap_init_i2c(client, &pcf50633_regmap_config); + if (IS_ERR(pcf->regmap)) { + ret = PTR_ERR(pcf->regmap); + dev_err(pcf->dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + version = pcf50633_reg_read(pcf, 0); + variant = pcf50633_reg_read(pcf, 1); + if (version < 0 || variant < 0) { + dev_err(pcf->dev, "Unable to probe pcf50633\n"); + ret = -ENODEV; + return ret; + } + + dev_info(pcf->dev, "Probed device version %d variant %d\n", + version, variant); + + pcf50633_irq_init(pcf, client->irq); + + /* Create sub devices */ + pcf50633_client_dev_register(pcf, "pcf50633-input", &pcf->input_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-rtc", &pcf->rtc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-mbc", &pcf->mbc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-adc", &pcf->adc_pdev); + pcf50633_client_dev_register(pcf, "pcf50633-backlight", &pcf->bl_pdev); + + + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { + pdev = platform_device_alloc("pcf50633-regulator", i); + if (!pdev) + return -ENOMEM; + + pdev->dev.parent = pcf->dev; + ret = platform_device_add_data(pdev, &pdata->reg_init_data[i], + sizeof(pdata->reg_init_data[i])); + if (ret) + goto err; + + ret = platform_device_add(pdev); + if (ret) + goto err; + + pcf->regulator_pdev[i] = pdev; + } + + ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group); + if (ret) + dev_warn(pcf->dev, "error creating sysfs entries\n"); + + if (pdata->probe_done) + pdata->probe_done(pcf); + + return 0; + +err: + platform_device_put(pdev); + for (j = 0; j < i; j++) + platform_device_put(pcf->regulator_pdev[j]); + + return ret; +} + +static int pcf50633_remove(struct i2c_client *client) +{ + struct pcf50633 *pcf = i2c_get_clientdata(client); + int i; + + sysfs_remove_group(&client->dev.kobj, &pcf_attr_group); + pcf50633_irq_free(pcf); + + platform_device_unregister(pcf->input_pdev); + platform_device_unregister(pcf->rtc_pdev); + platform_device_unregister(pcf->mbc_pdev); + platform_device_unregister(pcf->adc_pdev); + platform_device_unregister(pcf->bl_pdev); + + for (i = 0; i < PCF50633_NUM_REGULATORS; i++) + platform_device_unregister(pcf->regulator_pdev[i]); + + return 0; +} + +static const struct i2c_device_id pcf50633_id_table[] = { + {"pcf50633", 0x73}, + {/* end of list */} +}; +MODULE_DEVICE_TABLE(i2c, pcf50633_id_table); + +static struct i2c_driver pcf50633_driver = { + .driver = { + .name = "pcf50633", + .pm = &pcf50633_pm, + }, + .id_table = pcf50633_id_table, + .probe = pcf50633_probe, + .remove = pcf50633_remove, +}; + +static int __init pcf50633_init(void) +{ + return i2c_add_driver(&pcf50633_driver); +} + +static void __exit pcf50633_exit(void) +{ + i2c_del_driver(&pcf50633_driver); +} + +MODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 PMU"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_LICENSE("GPL"); + +subsys_initcall(pcf50633_init); +module_exit(pcf50633_exit); diff --git a/kernel/drivers/mfd/pcf50633-gpio.c b/kernel/drivers/mfd/pcf50633-gpio.c new file mode 100644 index 000000000..d02ddf2eb --- /dev/null +++ b/kernel/drivers/mfd/pcf50633-gpio.c @@ -0,0 +1,96 @@ +/* NXP PCF50633 GPIO Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/gpio.h> +#include <linux/mfd/pcf50633/pmic.h> + +static const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = { + [PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT, + [PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT, + [PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT, + [PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT, + [PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT, + [PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT, + [PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT, + [PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT, + [PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT, + [PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT, + [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT, +}; + +int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val) +{ + u8 reg; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + + return pcf50633_reg_set_bit_mask(pcf, reg, 0x07, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_set); + +u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio) +{ + u8 reg, val; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + val = pcf50633_reg_read(pcf, reg) & 0x07; + + return val; +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_get); + +int pcf50633_gpio_invert_set(struct pcf50633 *pcf, int gpio, int invert) +{ + u8 val, reg; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + val = !!invert << 3; + + return pcf50633_reg_set_bit_mask(pcf, reg, 1 << 3, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_set); + +int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio) +{ + u8 reg, val; + + reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG; + val = pcf50633_reg_read(pcf, reg); + + return val & (1 << 3); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_get); + +int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf, + int gpio, int regulator, int on) +{ + u8 reg, val, mask; + + /* the *ENA register is always one after the *OUT register */ + reg = pcf50633_regulator_registers[regulator] + 1; + + val = !!on << (gpio - PCF50633_GPIO1); + mask = 1 << (gpio - PCF50633_GPIO1); + + return pcf50633_reg_set_bit_mask(pcf, reg, mask, val); +} +EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set); + +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/pcf50633-irq.c b/kernel/drivers/mfd/pcf50633-irq.c new file mode 100644 index 000000000..498286cbb --- /dev/null +++ b/kernel/drivers/mfd/pcf50633-irq.c @@ -0,0 +1,314 @@ +/* NXP PCF50633 Power Management Unit (PMU) driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * 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/interrupt.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/export.h> +#include <linux/slab.h> + +#include <linux/mfd/pcf50633/core.h> +#include <linux/mfd/pcf50633/mbc.h> + +int pcf50633_register_irq(struct pcf50633 *pcf, int irq, + void (*handler) (int, void *), void *data) +{ + if (irq < 0 || irq >= PCF50633_NUM_IRQ || !handler) + return -EINVAL; + + if (WARN_ON(pcf->irq_handler[irq].handler)) + return -EBUSY; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = handler; + pcf->irq_handler[irq].data = data; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_register_irq); + +int pcf50633_free_irq(struct pcf50633 *pcf, int irq) +{ + if (irq < 0 || irq >= PCF50633_NUM_IRQ) + return -EINVAL; + + mutex_lock(&pcf->lock); + pcf->irq_handler[irq].handler = NULL; + mutex_unlock(&pcf->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pcf50633_free_irq); + +static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask) +{ + u8 reg, bit; + int ret = 0, idx; + + idx = irq >> 3; + reg = PCF50633_REG_INT1M + idx; + bit = 1 << (irq & 0x07); + + pcf50633_reg_set_bit_mask(pcf, reg, bit, mask ? bit : 0); + + mutex_lock(&pcf->lock); + + if (mask) + pcf->mask_regs[idx] |= bit; + else + pcf->mask_regs[idx] &= ~bit; + + mutex_unlock(&pcf->lock); + + return ret; +} + +int pcf50633_irq_mask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Masking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 1); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask); + +int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq) +{ + dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq); + + return __pcf50633_irq_mask_set(pcf, irq, 0); +} +EXPORT_SYMBOL_GPL(pcf50633_irq_unmask); + +int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq) +{ + u8 reg, bits; + + reg = irq >> 3; + bits = 1 << (irq & 0x07); + + return pcf->mask_regs[reg] & bits; +} +EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get); + +static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq) +{ + if (pcf->irq_handler[irq].handler) + pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data); +} + +/* Maximum amount of time ONKEY is held before emergency action is taken */ +#define PCF50633_ONKEY1S_TIMEOUT 8 + +static irqreturn_t pcf50633_irq(int irq, void *data) +{ + struct pcf50633 *pcf = data; + int ret, i, j; + u8 pcf_int[5], chgstat; + + /* Read the 5 INT regs in one transaction */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1, + ARRAY_SIZE(pcf_int), pcf_int); + if (ret != ARRAY_SIZE(pcf_int)) { + dev_err(pcf->dev, "Error reading INT registers\n"); + + /* + * If this doesn't ACK the interrupt to the chip, we'll be + * called once again as we're level triggered. + */ + goto out; + } + + /* defeat 8s death from lowsys on A5 */ + pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN, 0x04); + + /* We immediately read the usb and adapter status. We thus make sure + * only of USBINS/USBREM IRQ handlers are called */ + if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) { + chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + if (chgstat & (0x3 << 4)) + pcf_int[0] &= ~PCF50633_INT1_USBREM; + else + pcf_int[0] &= ~PCF50633_INT1_USBINS; + } + + /* Make sure only one of ADPINS or ADPREM is set */ + if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) { + chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + if (chgstat & (0x3 << 4)) + pcf_int[0] &= ~PCF50633_INT1_ADPREM; + else + pcf_int[0] &= ~PCF50633_INT1_ADPINS; + } + + dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x " + "INT4=0x%02x INT5=0x%02x\n", pcf_int[0], + pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]); + + /* Some revisions of the chip don't have a 8s standby mode on + * ONKEY1S press. We try to manually do it in such cases. */ + if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) { + dev_info(pcf->dev, "ONKEY1S held for %d secs\n", + pcf->onkey1s_held); + if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT) + if (pcf->pdata->force_shutdown) + pcf->pdata->force_shutdown(pcf); + } + + if (pcf_int[2] & PCF50633_INT3_ONKEY1S) { + dev_info(pcf->dev, "ONKEY1S held\n"); + pcf->onkey1s_held = 1 ; + + /* Unmask IRQ_SECOND */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M, + PCF50633_INT1_SECOND); + + /* Unmask IRQ_ONKEYR */ + pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR); + } + + if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) { + pcf->onkey1s_held = 0; + + /* Mask SECOND and ONKEYR interrupts */ + if (pcf->mask_regs[0] & PCF50633_INT1_SECOND) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT1M, + PCF50633_INT1_SECOND, + PCF50633_INT1_SECOND); + + if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR) + pcf50633_reg_set_bit_mask(pcf, + PCF50633_REG_INT2M, + PCF50633_INT2_ONKEYR, + PCF50633_INT2_ONKEYR); + } + + /* Have we just resumed ? */ + if (pcf->is_suspended) { + pcf->is_suspended = 0; + + /* Set the resume reason filtering out non resumers */ + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) + pcf->resume_reason[i] = pcf_int[i] & + pcf->pdata->resumers[i]; + + /* Make sure we don't pass on any ONKEY events to + * userspace now */ + pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF); + } + + for (i = 0; i < ARRAY_SIZE(pcf_int); i++) { + /* Unset masked interrupts */ + pcf_int[i] &= ~pcf->mask_regs[i]; + + for (j = 0; j < 8 ; j++) + if (pcf_int[i] & (1 << j)) + pcf50633_irq_call_handler(pcf, (i * 8) + j); + } + +out: + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +int pcf50633_irq_suspend(struct pcf50633 *pcf) +{ + int ret; + int i; + u8 res[5]; + + + /* Make sure our interrupt handlers are not called + * henceforth */ + disable_irq(pcf->irq); + + /* Save the masks */ + ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) { + dev_err(pcf->dev, "error saving irq masks\n"); + goto out; + } + + /* Write wakeup irq masks */ + for (i = 0; i < ARRAY_SIZE(res); i++) + res[i] = ~pcf->pdata->resumers[i]; + + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(res), &res[0]); + if (ret < 0) { + dev_err(pcf->dev, "error writing wakeup irq masks\n"); + goto out; + } + + pcf->is_suspended = 1; + +out: + return ret; +} + +int pcf50633_irq_resume(struct pcf50633 *pcf) +{ + int ret; + + /* Write the saved mask registers */ + ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M, + ARRAY_SIZE(pcf->suspend_irq_masks), + pcf->suspend_irq_masks); + if (ret < 0) + dev_err(pcf->dev, "Error restoring saved suspend masks\n"); + + enable_irq(pcf->irq); + + return ret; +} + +#endif + +int pcf50633_irq_init(struct pcf50633 *pcf, int irq) +{ + int ret; + + pcf->irq = irq; + + /* Enable all interrupts except RTC SECOND */ + pcf->mask_regs[0] = 0x80; + pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]); + pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00); + pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00); + + ret = request_threaded_irq(irq, NULL, pcf50633_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "pcf50633", pcf); + + if (ret) + dev_err(pcf->dev, "Failed to request IRQ %d\n", ret); + + if (enable_irq_wake(irq) < 0) + dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source" + "in this hardware revision", irq); + + return ret; +} + +void pcf50633_irq_free(struct pcf50633 *pcf) +{ + free_irq(pcf->irq, pcf); +} diff --git a/kernel/drivers/mfd/pm8921-core.c b/kernel/drivers/mfd/pm8921-core.c new file mode 100644 index 000000000..5a92646a2 --- /dev/null +++ b/kernel/drivers/mfd/pm8921-core.c @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/ssbi.h> +#include <linux/regmap.h> +#include <linux/of_platform.h> +#include <linux/mfd/core.h> + +#define SSBI_REG_ADDR_IRQ_BASE 0x1BB + +#define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0) +#define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1) +#define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2) +#define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3) +#define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4) +#define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5) +#define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6) +#define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7) +#define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8) + +#define PM_IRQF_LVL_SEL 0x01 /* level select */ +#define PM_IRQF_MASK_FE 0x02 /* mask falling edge */ +#define PM_IRQF_MASK_RE 0x04 /* mask rising edge */ +#define PM_IRQF_CLR 0x08 /* clear interrupt */ +#define PM_IRQF_BITS_MASK 0x70 +#define PM_IRQF_BITS_SHIFT 4 +#define PM_IRQF_WRITE 0x80 + +#define PM_IRQF_MASK_ALL (PM_IRQF_MASK_FE | \ + PM_IRQF_MASK_RE) + +#define REG_HWREV 0x002 /* PMIC4 revision */ +#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ + +#define PM8921_NR_IRQS 256 + +struct pm_irq_chip { + struct regmap *regmap; + spinlock_t pm_irq_lock; + struct irq_domain *irqdomain; + unsigned int num_irqs; + unsigned int num_blocks; + unsigned int num_masters; + u8 config[0]; +}; + +static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, unsigned int bp, + unsigned int *ip) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + + rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_IT_STATUS, ip); + if (rc) + pr_err("Failed Reading Status rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int +pm8xxx_config_irq(struct pm_irq_chip *chip, unsigned int bp, unsigned int cp) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + + cp |= PM_IRQF_WRITE; + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_CONFIG, cp); + if (rc) + pr_err("Failed Configuring IRQ rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int pm8xxx_irq_block_handler(struct pm_irq_chip *chip, int block) +{ + int pmirq, irq, i, ret = 0; + unsigned int bits; + + ret = pm8xxx_read_block_irq(chip, block, &bits); + if (ret) { + pr_err("Failed reading %d block ret=%d", block, ret); + return ret; + } + if (!bits) { + pr_err("block bit set in master but no irqs: %d", block); + return 0; + } + + /* Check IRQ bits */ + for (i = 0; i < 8; i++) { + if (bits & (1 << i)) { + pmirq = block * 8 + i; + irq = irq_find_mapping(chip->irqdomain, pmirq); + generic_handle_irq(irq); + } + } + return 0; +} + +static int pm8xxx_irq_master_handler(struct pm_irq_chip *chip, int master) +{ + unsigned int blockbits; + int block_number, i, ret = 0; + + ret = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_M_STATUS1 + master, + &blockbits); + if (ret) { + pr_err("Failed to read master %d ret=%d\n", master, ret); + return ret; + } + if (!blockbits) { + pr_err("master bit set in root but no blocks: %d", master); + return 0; + } + + for (i = 0; i < 8; i++) + if (blockbits & (1 << i)) { + block_number = master * 8 + i; /* block # */ + ret |= pm8xxx_irq_block_handler(chip, block_number); + } + return ret; +} + +static void pm8xxx_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct pm_irq_chip *chip = irq_desc_get_handler_data(desc); + struct irq_chip *irq_chip = irq_desc_get_chip(desc); + unsigned int root; + int i, ret, masters = 0; + + chained_irq_enter(irq_chip, desc); + + ret = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_ROOT, &root); + if (ret) { + pr_err("Can't read root status ret=%d\n", ret); + return; + } + + /* on pm8xxx series masters start from bit 1 of the root */ + masters = root >> 1; + + /* Read allowed masters for blocks. */ + for (i = 0; i < chip->num_masters; i++) + if (masters & (1 << i)) + pm8xxx_irq_master_handler(chip, i); + + chained_irq_exit(irq_chip, desc); +} + +static void pm8xxx_irq_mask_ack(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + u8 block, config; + + block = pmirq / 8; + + config = chip->config[pmirq] | PM_IRQF_MASK_ALL | PM_IRQF_CLR; + pm8xxx_config_irq(chip, block, config); +} + +static void pm8xxx_irq_unmask(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + u8 block, config; + + block = pmirq / 8; + + config = chip->config[pmirq]; + pm8xxx_config_irq(chip, block, config); +} + +static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + int irq_bit; + u8 block, config; + + block = pmirq / 8; + irq_bit = pmirq % 8; + + chip->config[pmirq] = (irq_bit << PM_IRQF_BITS_SHIFT) + | PM_IRQF_MASK_ALL; + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + if (flow_type & IRQF_TRIGGER_RISING) + chip->config[pmirq] &= ~PM_IRQF_MASK_RE; + if (flow_type & IRQF_TRIGGER_FALLING) + chip->config[pmirq] &= ~PM_IRQF_MASK_FE; + } else { + chip->config[pmirq] |= PM_IRQF_LVL_SEL; + + if (flow_type & IRQF_TRIGGER_HIGH) + chip->config[pmirq] &= ~PM_IRQF_MASK_RE; + else + chip->config[pmirq] &= ~PM_IRQF_MASK_FE; + } + + config = chip->config[pmirq] | PM_IRQF_CLR; + return pm8xxx_config_irq(chip, block, config); +} + +static struct irq_chip pm8xxx_irq_chip = { + .name = "pm8xxx", + .irq_mask_ack = pm8xxx_irq_mask_ack, + .irq_unmask = pm8xxx_irq_unmask, + .irq_set_type = pm8xxx_irq_set_type, + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, +}; + +static int pm8xxx_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct pm_irq_chip *chip = d->host_data; + + irq_set_chip_and_handler(irq, &pm8xxx_irq_chip, handle_level_irq); + irq_set_chip_data(irq, chip); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + return 0; +} + +static const struct irq_domain_ops pm8xxx_irq_domain_ops = { + .xlate = irq_domain_xlate_twocell, + .map = pm8xxx_irq_domain_map, +}; + +static const struct regmap_config ssbi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x3ff, + .fast_io = true, + .reg_read = ssbi_reg_read, + .reg_write = ssbi_reg_write +}; + +static const struct of_device_id pm8921_id_table[] = { + { .compatible = "qcom,pm8058", }, + { .compatible = "qcom,pm8921", }, + { } +}; +MODULE_DEVICE_TABLE(of, pm8921_id_table); + +static int pm8921_probe(struct platform_device *pdev) +{ + struct regmap *regmap; + int irq, rc; + unsigned int val; + u32 rev; + struct pm_irq_chip *chip; + unsigned int nirqs = PM8921_NR_IRQS; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + regmap = devm_regmap_init(&pdev->dev, NULL, pdev->dev.parent, + &ssbi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Read PMIC chip revision */ + rc = regmap_read(regmap, REG_HWREV, &val); + if (rc) { + pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); + return rc; + } + pr_info("PMIC revision 1: %02X\n", val); + rev = val; + + /* Read PMIC chip revision 2 */ + rc = regmap_read(regmap, REG_HWREV_2, &val); + if (rc) { + pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", + REG_HWREV_2, rc); + return rc; + } + pr_info("PMIC revision 2: %02X\n", val); + rev |= val << BITS_PER_BYTE; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip) + + sizeof(chip->config[0]) * nirqs, + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + platform_set_drvdata(pdev, chip); + chip->regmap = regmap; + chip->num_irqs = nirqs; + chip->num_blocks = DIV_ROUND_UP(chip->num_irqs, 8); + chip->num_masters = DIV_ROUND_UP(chip->num_blocks, 8); + spin_lock_init(&chip->pm_irq_lock); + + chip->irqdomain = irq_domain_add_linear(pdev->dev.of_node, nirqs, + &pm8xxx_irq_domain_ops, + chip); + if (!chip->irqdomain) + return -ENODEV; + + irq_set_handler_data(irq, chip); + irq_set_chained_handler(irq, pm8xxx_irq_handler); + irq_set_irq_wake(irq, 1); + + rc = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + if (rc) { + irq_set_chained_handler(irq, NULL); + irq_set_handler_data(irq, NULL); + irq_domain_remove(chip->irqdomain); + } + + return rc; +} + +static int pm8921_remove_child(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int pm8921_remove(struct platform_device *pdev) +{ + int irq = platform_get_irq(pdev, 0); + struct pm_irq_chip *chip = platform_get_drvdata(pdev); + + device_for_each_child(&pdev->dev, NULL, pm8921_remove_child); + irq_set_chained_handler(irq, NULL); + irq_set_handler_data(irq, NULL); + irq_domain_remove(chip->irqdomain); + + return 0; +} + +static struct platform_driver pm8921_driver = { + .probe = pm8921_probe, + .remove = pm8921_remove, + .driver = { + .name = "pm8921-core", + .of_match_table = pm8921_id_table, + }, +}; + +static int __init pm8921_init(void) +{ + return platform_driver_register(&pm8921_driver); +} +subsys_initcall(pm8921_init); + +static void __exit pm8921_exit(void) +{ + platform_driver_unregister(&pm8921_driver); +} +module_exit(pm8921_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8921 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8921-core"); diff --git a/kernel/drivers/mfd/qcom-spmi-pmic.c b/kernel/drivers/mfd/qcom-spmi-pmic.c new file mode 100644 index 000000000..af6ac1c4b --- /dev/null +++ b/kernel/drivers/mfd/qcom-spmi-pmic.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spmi.h> +#include <linux/regmap.h> +#include <linux/of_platform.h> + +#define PMIC_REV2 0x101 +#define PMIC_REV3 0x102 +#define PMIC_REV4 0x103 +#define PMIC_TYPE 0x104 +#define PMIC_SUBTYPE 0x105 + +#define PMIC_TYPE_VALUE 0x51 + +#define COMMON_SUBTYPE 0x00 +#define PM8941_SUBTYPE 0x01 +#define PM8841_SUBTYPE 0x02 +#define PM8019_SUBTYPE 0x03 +#define PM8226_SUBTYPE 0x04 +#define PM8110_SUBTYPE 0x05 +#define PMA8084_SUBTYPE 0x06 +#define PMI8962_SUBTYPE 0x07 +#define PMD9635_SUBTYPE 0x08 +#define PM8994_SUBTYPE 0x09 +#define PMI8994_SUBTYPE 0x0a +#define PM8916_SUBTYPE 0x0b +#define PM8004_SUBTYPE 0x0c +#define PM8909_SUBTYPE 0x0d + +static const struct of_device_id pmic_spmi_id_table[] = { + { .compatible = "qcom,spmi-pmic", .data = (void *)COMMON_SUBTYPE }, + { .compatible = "qcom,pm8941", .data = (void *)PM8941_SUBTYPE }, + { .compatible = "qcom,pm8841", .data = (void *)PM8841_SUBTYPE }, + { .compatible = "qcom,pm8019", .data = (void *)PM8019_SUBTYPE }, + { .compatible = "qcom,pm8226", .data = (void *)PM8226_SUBTYPE }, + { .compatible = "qcom,pm8110", .data = (void *)PM8110_SUBTYPE }, + { .compatible = "qcom,pma8084", .data = (void *)PMA8084_SUBTYPE }, + { .compatible = "qcom,pmi8962", .data = (void *)PMI8962_SUBTYPE }, + { .compatible = "qcom,pmd9635", .data = (void *)PMD9635_SUBTYPE }, + { .compatible = "qcom,pm8994", .data = (void *)PM8994_SUBTYPE }, + { .compatible = "qcom,pmi8994", .data = (void *)PMI8994_SUBTYPE }, + { .compatible = "qcom,pm8916", .data = (void *)PM8916_SUBTYPE }, + { .compatible = "qcom,pm8004", .data = (void *)PM8004_SUBTYPE }, + { .compatible = "qcom,pm8909", .data = (void *)PM8909_SUBTYPE }, + { } +}; + +static void pmic_spmi_show_revid(struct regmap *map, struct device *dev) +{ + unsigned int rev2, minor, major, type, subtype; + const char *name = "unknown"; + int ret, i; + + ret = regmap_read(map, PMIC_TYPE, &type); + if (ret < 0) + return; + + if (type != PMIC_TYPE_VALUE) + return; + + ret = regmap_read(map, PMIC_SUBTYPE, &subtype); + if (ret < 0) + return; + + for (i = 0; i < ARRAY_SIZE(pmic_spmi_id_table); i++) { + if (subtype == (unsigned long)pmic_spmi_id_table[i].data) + break; + } + + if (i != ARRAY_SIZE(pmic_spmi_id_table)) + name = pmic_spmi_id_table[i].compatible; + + ret = regmap_read(map, PMIC_REV2, &rev2); + if (ret < 0) + return; + + ret = regmap_read(map, PMIC_REV3, &minor); + if (ret < 0) + return; + + ret = regmap_read(map, PMIC_REV4, &major); + if (ret < 0) + return; + + /* + * In early versions of PM8941 and PM8226, the major revision number + * started incrementing from 0 (eg 0 = v1.0, 1 = v2.0). + * Increment the major revision number here if the chip is an early + * version of PM8941 or PM8226. + */ + if ((subtype == PM8941_SUBTYPE || subtype == PM8226_SUBTYPE) && + major < 0x02) + major++; + + if (subtype == PM8110_SUBTYPE) + minor = rev2; + + dev_dbg(dev, "%x: %s v%d.%d\n", subtype, name, major, minor); +} + +static const struct regmap_config spmi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xffff, + .fast_io = true, +}; + +static int pmic_spmi_probe(struct spmi_device *sdev) +{ + struct device_node *root = sdev->dev.of_node; + struct regmap *regmap; + + regmap = devm_regmap_init_spmi_ext(sdev, &spmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + pmic_spmi_show_revid(regmap, &sdev->dev); + + return of_platform_populate(root, NULL, NULL, &sdev->dev); +} + +static void pmic_spmi_remove(struct spmi_device *sdev) +{ + of_platform_depopulate(&sdev->dev); +} + +MODULE_DEVICE_TABLE(of, pmic_spmi_id_table); + +static struct spmi_driver pmic_spmi_driver = { + .probe = pmic_spmi_probe, + .remove = pmic_spmi_remove, + .driver = { + .name = "pmic-spmi", + .of_match_table = pmic_spmi_id_table, + }, +}; +module_spmi_driver(pmic_spmi_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI PMIC driver"); +MODULE_ALIAS("spmi:spmi-pmic"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Josh Cartwright <joshc@codeaurora.org>"); +MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>"); diff --git a/kernel/drivers/mfd/qcom_rpm.c b/kernel/drivers/mfd/qcom_rpm.c new file mode 100644 index 000000000..12e324319 --- /dev/null +++ b/kernel/drivers/mfd/qcom_rpm.c @@ -0,0 +1,622 @@ +/* + * Copyright (c) 2014, Sony Mobile Communications AB. + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Author: Bjorn Andersson <bjorn.andersson@sonymobile.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/mfd/qcom_rpm.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> + +#include <dt-bindings/mfd/qcom-rpm.h> + +struct qcom_rpm_resource { + unsigned target_id; + unsigned status_id; + unsigned select_id; + unsigned size; +}; + +struct qcom_rpm_data { + u32 version; + const struct qcom_rpm_resource *resource_table; + unsigned n_resources; +}; + +struct qcom_rpm { + struct device *dev; + struct regmap *ipc_regmap; + unsigned ipc_offset; + unsigned ipc_bit; + + struct completion ack; + struct mutex lock; + + void __iomem *status_regs; + void __iomem *ctrl_regs; + void __iomem *req_regs; + + u32 ack_status; + + const struct qcom_rpm_data *data; +}; + +#define RPM_STATUS_REG(rpm, i) ((rpm)->status_regs + (i) * 4) +#define RPM_CTRL_REG(rpm, i) ((rpm)->ctrl_regs + (i) * 4) +#define RPM_REQ_REG(rpm, i) ((rpm)->req_regs + (i) * 4) + +#define RPM_REQUEST_TIMEOUT (5 * HZ) + +#define RPM_REQUEST_CONTEXT 3 +#define RPM_REQ_SELECT 11 +#define RPM_ACK_CONTEXT 15 +#define RPM_ACK_SELECTOR 23 +#define RPM_SELECT_SIZE 7 + +#define RPM_NOTIFICATION BIT(30) +#define RPM_REJECTED BIT(31) + +#define RPM_SIGNAL BIT(2) + +static const struct qcom_rpm_resource apq8064_rpm_resource_table[] = { + [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 }, + [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 }, + [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 }, + [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 }, + [QCOM_RPM_MM_FABRIC_CLK] = { 29, 13, 10, 1 }, + [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 }, + [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 }, + [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 }, + [QCOM_RPM_MMFPB_CLK] = { 33, 17, 14, 1 }, + [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 }, + [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 1 }, + [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 1 }, + [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 }, + [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 }, + [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 1 }, + [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 1 }, + [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 }, + [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 30 }, + [QCOM_RPM_MM_FABRIC_HALT] = { 89, 27, 26, 1 }, + [QCOM_RPM_MM_FABRIC_MODE] = { 91, 28, 27, 1 }, + [QCOM_RPM_MM_FABRIC_IOCTL] = { 94, 29, 28, 1 }, + [QCOM_RPM_MM_FABRIC_ARB] = { 95, 30, 29, 21 }, + [QCOM_RPM_PM8921_SMPS1] = { 116, 31, 30, 2 }, + [QCOM_RPM_PM8921_SMPS2] = { 118, 33, 31, 2 }, + [QCOM_RPM_PM8921_SMPS3] = { 120, 35, 32, 2 }, + [QCOM_RPM_PM8921_SMPS4] = { 122, 37, 33, 2 }, + [QCOM_RPM_PM8921_SMPS5] = { 124, 39, 34, 2 }, + [QCOM_RPM_PM8921_SMPS6] = { 126, 41, 35, 2 }, + [QCOM_RPM_PM8921_SMPS7] = { 128, 43, 36, 2 }, + [QCOM_RPM_PM8921_SMPS8] = { 130, 45, 37, 2 }, + [QCOM_RPM_PM8921_LDO1] = { 132, 47, 38, 2 }, + [QCOM_RPM_PM8921_LDO2] = { 134, 49, 39, 2 }, + [QCOM_RPM_PM8921_LDO3] = { 136, 51, 40, 2 }, + [QCOM_RPM_PM8921_LDO4] = { 138, 53, 41, 2 }, + [QCOM_RPM_PM8921_LDO5] = { 140, 55, 42, 2 }, + [QCOM_RPM_PM8921_LDO6] = { 142, 57, 43, 2 }, + [QCOM_RPM_PM8921_LDO7] = { 144, 59, 44, 2 }, + [QCOM_RPM_PM8921_LDO8] = { 146, 61, 45, 2 }, + [QCOM_RPM_PM8921_LDO9] = { 148, 63, 46, 2 }, + [QCOM_RPM_PM8921_LDO10] = { 150, 65, 47, 2 }, + [QCOM_RPM_PM8921_LDO11] = { 152, 67, 48, 2 }, + [QCOM_RPM_PM8921_LDO12] = { 154, 69, 49, 2 }, + [QCOM_RPM_PM8921_LDO13] = { 156, 71, 50, 2 }, + [QCOM_RPM_PM8921_LDO14] = { 158, 73, 51, 2 }, + [QCOM_RPM_PM8921_LDO15] = { 160, 75, 52, 2 }, + [QCOM_RPM_PM8921_LDO16] = { 162, 77, 53, 2 }, + [QCOM_RPM_PM8921_LDO17] = { 164, 79, 54, 2 }, + [QCOM_RPM_PM8921_LDO18] = { 166, 81, 55, 2 }, + [QCOM_RPM_PM8921_LDO19] = { 168, 83, 56, 2 }, + [QCOM_RPM_PM8921_LDO20] = { 170, 85, 57, 2 }, + [QCOM_RPM_PM8921_LDO21] = { 172, 87, 58, 2 }, + [QCOM_RPM_PM8921_LDO22] = { 174, 89, 59, 2 }, + [QCOM_RPM_PM8921_LDO23] = { 176, 91, 60, 2 }, + [QCOM_RPM_PM8921_LDO24] = { 178, 93, 61, 2 }, + [QCOM_RPM_PM8921_LDO25] = { 180, 95, 62, 2 }, + [QCOM_RPM_PM8921_LDO26] = { 182, 97, 63, 2 }, + [QCOM_RPM_PM8921_LDO27] = { 184, 99, 64, 2 }, + [QCOM_RPM_PM8921_LDO28] = { 186, 101, 65, 2 }, + [QCOM_RPM_PM8921_LDO29] = { 188, 103, 66, 2 }, + [QCOM_RPM_PM8921_CLK1] = { 190, 105, 67, 2 }, + [QCOM_RPM_PM8921_CLK2] = { 192, 107, 68, 2 }, + [QCOM_RPM_PM8921_LVS1] = { 194, 109, 69, 1 }, + [QCOM_RPM_PM8921_LVS2] = { 195, 110, 70, 1 }, + [QCOM_RPM_PM8921_LVS3] = { 196, 111, 71, 1 }, + [QCOM_RPM_PM8921_LVS4] = { 197, 112, 72, 1 }, + [QCOM_RPM_PM8921_LVS5] = { 198, 113, 73, 1 }, + [QCOM_RPM_PM8921_LVS6] = { 199, 114, 74, 1 }, + [QCOM_RPM_PM8921_LVS7] = { 200, 115, 75, 1 }, + [QCOM_RPM_PM8821_SMPS1] = { 201, 116, 76, 2 }, + [QCOM_RPM_PM8821_SMPS2] = { 203, 118, 77, 2 }, + [QCOM_RPM_PM8821_LDO1] = { 205, 120, 78, 2 }, + [QCOM_RPM_PM8921_NCP] = { 207, 122, 80, 2 }, + [QCOM_RPM_CXO_BUFFERS] = { 209, 124, 81, 1 }, + [QCOM_RPM_USB_OTG_SWITCH] = { 210, 125, 82, 1 }, + [QCOM_RPM_HDMI_SWITCH] = { 211, 126, 83, 1 }, + [QCOM_RPM_DDR_DMM] = { 212, 127, 84, 2 }, + [QCOM_RPM_VDDMIN_GPIO] = { 215, 131, 89, 1 }, +}; + +static const struct qcom_rpm_data apq8064_template = { + .version = 3, + .resource_table = apq8064_rpm_resource_table, + .n_resources = ARRAY_SIZE(apq8064_rpm_resource_table), +}; + +static const struct qcom_rpm_resource msm8660_rpm_resource_table[] = { + [QCOM_RPM_CXO_CLK] = { 32, 12, 5, 1 }, + [QCOM_RPM_PXO_CLK] = { 33, 13, 6, 1 }, + [QCOM_RPM_PLL_4] = { 34, 14, 7, 1 }, + [QCOM_RPM_APPS_FABRIC_CLK] = { 35, 15, 8, 1 }, + [QCOM_RPM_SYS_FABRIC_CLK] = { 36, 16, 9, 1 }, + [QCOM_RPM_MM_FABRIC_CLK] = { 37, 17, 10, 1 }, + [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 38, 18, 11, 1 }, + [QCOM_RPM_SFPB_CLK] = { 39, 19, 12, 1 }, + [QCOM_RPM_CFPB_CLK] = { 40, 20, 13, 1 }, + [QCOM_RPM_MMFPB_CLK] = { 41, 21, 14, 1 }, + [QCOM_RPM_SMI_CLK] = { 42, 22, 15, 1 }, + [QCOM_RPM_EBI1_CLK] = { 43, 23, 16, 1 }, + [QCOM_RPM_APPS_L2_CACHE_CTL] = { 44, 24, 17, 1 }, + [QCOM_RPM_APPS_FABRIC_HALT] = { 45, 25, 18, 2 }, + [QCOM_RPM_APPS_FABRIC_MODE] = { 47, 26, 19, 3 }, + [QCOM_RPM_APPS_FABRIC_ARB] = { 51, 28, 21, 6 }, + [QCOM_RPM_SYS_FABRIC_HALT] = { 63, 29, 22, 2 }, + [QCOM_RPM_SYS_FABRIC_MODE] = { 65, 30, 23, 3 }, + [QCOM_RPM_SYS_FABRIC_ARB] = { 69, 32, 25, 22 }, + [QCOM_RPM_MM_FABRIC_HALT] = { 105, 33, 26, 2 }, + [QCOM_RPM_MM_FABRIC_MODE] = { 107, 34, 27, 3 }, + [QCOM_RPM_MM_FABRIC_ARB] = { 111, 36, 29, 23 }, + [QCOM_RPM_PM8901_SMPS0] = { 134, 37, 30, 2 }, + [QCOM_RPM_PM8901_SMPS1] = { 136, 39, 31, 2 }, + [QCOM_RPM_PM8901_SMPS2] = { 138, 41, 32, 2 }, + [QCOM_RPM_PM8901_SMPS3] = { 140, 43, 33, 2 }, + [QCOM_RPM_PM8901_SMPS4] = { 142, 45, 34, 2 }, + [QCOM_RPM_PM8901_LDO0] = { 144, 47, 35, 2 }, + [QCOM_RPM_PM8901_LDO1] = { 146, 49, 36, 2 }, + [QCOM_RPM_PM8901_LDO2] = { 148, 51, 37, 2 }, + [QCOM_RPM_PM8901_LDO3] = { 150, 53, 38, 2 }, + [QCOM_RPM_PM8901_LDO4] = { 152, 55, 39, 2 }, + [QCOM_RPM_PM8901_LDO5] = { 154, 57, 40, 2 }, + [QCOM_RPM_PM8901_LDO6] = { 156, 59, 41, 2 }, + [QCOM_RPM_PM8901_LVS0] = { 158, 61, 42, 1 }, + [QCOM_RPM_PM8901_LVS1] = { 159, 62, 43, 1 }, + [QCOM_RPM_PM8901_LVS2] = { 160, 63, 44, 1 }, + [QCOM_RPM_PM8901_LVS3] = { 161, 64, 45, 1 }, + [QCOM_RPM_PM8901_MVS] = { 162, 65, 46, 1 }, + [QCOM_RPM_PM8058_SMPS0] = { 163, 66, 47, 2 }, + [QCOM_RPM_PM8058_SMPS1] = { 165, 68, 48, 2 }, + [QCOM_RPM_PM8058_SMPS2] = { 167, 70, 49, 2 }, + [QCOM_RPM_PM8058_SMPS3] = { 169, 72, 50, 2 }, + [QCOM_RPM_PM8058_SMPS4] = { 171, 74, 51, 2 }, + [QCOM_RPM_PM8058_LDO0] = { 173, 76, 52, 2 }, + [QCOM_RPM_PM8058_LDO1] = { 175, 78, 53, 2 }, + [QCOM_RPM_PM8058_LDO2] = { 177, 80, 54, 2 }, + [QCOM_RPM_PM8058_LDO3] = { 179, 82, 55, 2 }, + [QCOM_RPM_PM8058_LDO4] = { 181, 84, 56, 2 }, + [QCOM_RPM_PM8058_LDO5] = { 183, 86, 57, 2 }, + [QCOM_RPM_PM8058_LDO6] = { 185, 88, 58, 2 }, + [QCOM_RPM_PM8058_LDO7] = { 187, 90, 59, 2 }, + [QCOM_RPM_PM8058_LDO8] = { 189, 92, 60, 2 }, + [QCOM_RPM_PM8058_LDO9] = { 191, 94, 61, 2 }, + [QCOM_RPM_PM8058_LDO10] = { 193, 96, 62, 2 }, + [QCOM_RPM_PM8058_LDO11] = { 195, 98, 63, 2 }, + [QCOM_RPM_PM8058_LDO12] = { 197, 100, 64, 2 }, + [QCOM_RPM_PM8058_LDO13] = { 199, 102, 65, 2 }, + [QCOM_RPM_PM8058_LDO14] = { 201, 104, 66, 2 }, + [QCOM_RPM_PM8058_LDO15] = { 203, 106, 67, 2 }, + [QCOM_RPM_PM8058_LDO16] = { 205, 108, 68, 2 }, + [QCOM_RPM_PM8058_LDO17] = { 207, 110, 69, 2 }, + [QCOM_RPM_PM8058_LDO18] = { 209, 112, 70, 2 }, + [QCOM_RPM_PM8058_LDO19] = { 211, 114, 71, 2 }, + [QCOM_RPM_PM8058_LDO20] = { 213, 116, 72, 2 }, + [QCOM_RPM_PM8058_LDO21] = { 215, 118, 73, 2 }, + [QCOM_RPM_PM8058_LDO22] = { 217, 120, 74, 2 }, + [QCOM_RPM_PM8058_LDO23] = { 219, 122, 75, 2 }, + [QCOM_RPM_PM8058_LDO24] = { 221, 124, 76, 2 }, + [QCOM_RPM_PM8058_LDO25] = { 223, 126, 77, 2 }, + [QCOM_RPM_PM8058_LVS0] = { 225, 128, 78, 1 }, + [QCOM_RPM_PM8058_LVS1] = { 226, 129, 79, 1 }, + [QCOM_RPM_PM8058_NCP] = { 227, 130, 80, 2 }, + [QCOM_RPM_CXO_BUFFERS] = { 229, 132, 81, 1 }, +}; + +static const struct qcom_rpm_data msm8660_template = { + .version = 2, + .resource_table = msm8660_rpm_resource_table, + .n_resources = ARRAY_SIZE(msm8660_rpm_resource_table), +}; + +static const struct qcom_rpm_resource msm8960_rpm_resource_table[] = { + [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 }, + [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 }, + [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 }, + [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 }, + [QCOM_RPM_MM_FABRIC_CLK] = { 29, 13, 10, 1 }, + [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 }, + [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 }, + [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 }, + [QCOM_RPM_MMFPB_CLK] = { 33, 17, 14, 1 }, + [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 }, + [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 1 }, + [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 1 }, + [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 }, + [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 }, + [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 1 }, + [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 1 }, + [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 }, + [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 29 }, + [QCOM_RPM_MM_FABRIC_HALT] = { 88, 27, 26, 1 }, + [QCOM_RPM_MM_FABRIC_MODE] = { 90, 28, 27, 1 }, + [QCOM_RPM_MM_FABRIC_IOCTL] = { 93, 29, 28, 1 }, + [QCOM_RPM_MM_FABRIC_ARB] = { 94, 30, 29, 23 }, + [QCOM_RPM_PM8921_SMPS1] = { 117, 31, 30, 2 }, + [QCOM_RPM_PM8921_SMPS2] = { 119, 33, 31, 2 }, + [QCOM_RPM_PM8921_SMPS3] = { 121, 35, 32, 2 }, + [QCOM_RPM_PM8921_SMPS4] = { 123, 37, 33, 2 }, + [QCOM_RPM_PM8921_SMPS5] = { 125, 39, 34, 2 }, + [QCOM_RPM_PM8921_SMPS6] = { 127, 41, 35, 2 }, + [QCOM_RPM_PM8921_SMPS7] = { 129, 43, 36, 2 }, + [QCOM_RPM_PM8921_SMPS8] = { 131, 45, 37, 2 }, + [QCOM_RPM_PM8921_LDO1] = { 133, 47, 38, 2 }, + [QCOM_RPM_PM8921_LDO2] = { 135, 49, 39, 2 }, + [QCOM_RPM_PM8921_LDO3] = { 137, 51, 40, 2 }, + [QCOM_RPM_PM8921_LDO4] = { 139, 53, 41, 2 }, + [QCOM_RPM_PM8921_LDO5] = { 141, 55, 42, 2 }, + [QCOM_RPM_PM8921_LDO6] = { 143, 57, 43, 2 }, + [QCOM_RPM_PM8921_LDO7] = { 145, 59, 44, 2 }, + [QCOM_RPM_PM8921_LDO8] = { 147, 61, 45, 2 }, + [QCOM_RPM_PM8921_LDO9] = { 149, 63, 46, 2 }, + [QCOM_RPM_PM8921_LDO10] = { 151, 65, 47, 2 }, + [QCOM_RPM_PM8921_LDO11] = { 153, 67, 48, 2 }, + [QCOM_RPM_PM8921_LDO12] = { 155, 69, 49, 2 }, + [QCOM_RPM_PM8921_LDO13] = { 157, 71, 50, 2 }, + [QCOM_RPM_PM8921_LDO14] = { 159, 73, 51, 2 }, + [QCOM_RPM_PM8921_LDO15] = { 161, 75, 52, 2 }, + [QCOM_RPM_PM8921_LDO16] = { 163, 77, 53, 2 }, + [QCOM_RPM_PM8921_LDO17] = { 165, 79, 54, 2 }, + [QCOM_RPM_PM8921_LDO18] = { 167, 81, 55, 2 }, + [QCOM_RPM_PM8921_LDO19] = { 169, 83, 56, 2 }, + [QCOM_RPM_PM8921_LDO20] = { 171, 85, 57, 2 }, + [QCOM_RPM_PM8921_LDO21] = { 173, 87, 58, 2 }, + [QCOM_RPM_PM8921_LDO22] = { 175, 89, 59, 2 }, + [QCOM_RPM_PM8921_LDO23] = { 177, 91, 60, 2 }, + [QCOM_RPM_PM8921_LDO24] = { 179, 93, 61, 2 }, + [QCOM_RPM_PM8921_LDO25] = { 181, 95, 62, 2 }, + [QCOM_RPM_PM8921_LDO26] = { 183, 97, 63, 2 }, + [QCOM_RPM_PM8921_LDO27] = { 185, 99, 64, 2 }, + [QCOM_RPM_PM8921_LDO28] = { 187, 101, 65, 2 }, + [QCOM_RPM_PM8921_LDO29] = { 189, 103, 66, 2 }, + [QCOM_RPM_PM8921_CLK1] = { 191, 105, 67, 2 }, + [QCOM_RPM_PM8921_CLK2] = { 193, 107, 68, 2 }, + [QCOM_RPM_PM8921_LVS1] = { 195, 109, 69, 1 }, + [QCOM_RPM_PM8921_LVS2] = { 196, 110, 70, 1 }, + [QCOM_RPM_PM8921_LVS3] = { 197, 111, 71, 1 }, + [QCOM_RPM_PM8921_LVS4] = { 198, 112, 72, 1 }, + [QCOM_RPM_PM8921_LVS5] = { 199, 113, 73, 1 }, + [QCOM_RPM_PM8921_LVS6] = { 200, 114, 74, 1 }, + [QCOM_RPM_PM8921_LVS7] = { 201, 115, 75, 1 }, + [QCOM_RPM_PM8921_NCP] = { 202, 116, 80, 2 }, + [QCOM_RPM_CXO_BUFFERS] = { 204, 118, 81, 1 }, + [QCOM_RPM_USB_OTG_SWITCH] = { 205, 119, 82, 1 }, + [QCOM_RPM_HDMI_SWITCH] = { 206, 120, 83, 1 }, + [QCOM_RPM_DDR_DMM] = { 207, 121, 84, 2 }, +}; + +static const struct qcom_rpm_data msm8960_template = { + .version = 3, + .resource_table = msm8960_rpm_resource_table, + .n_resources = ARRAY_SIZE(msm8960_rpm_resource_table), +}; + +static const struct qcom_rpm_resource ipq806x_rpm_resource_table[] = { + [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 }, + [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 }, + [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 }, + [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 }, + [QCOM_RPM_NSS_FABRIC_0_CLK] = { 29, 13, 10, 1 }, + [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 }, + [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 }, + [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 }, + [QCOM_RPM_NSS_FABRIC_1_CLK] = { 33, 17, 14, 1 }, + [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 }, + [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 2 }, + [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 3 }, + [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 }, + [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 }, + [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 2 }, + [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 3 }, + [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 }, + [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 30 }, + [QCOM_RPM_MM_FABRIC_HALT] = { 89, 27, 26, 2 }, + [QCOM_RPM_MM_FABRIC_MODE] = { 91, 28, 27, 3 }, + [QCOM_RPM_MM_FABRIC_IOCTL] = { 94, 29, 28, 1 }, + [QCOM_RPM_MM_FABRIC_ARB] = { 95, 30, 29, 2 }, + [QCOM_RPM_CXO_BUFFERS] = { 209, 33, 31, 1 }, + [QCOM_RPM_USB_OTG_SWITCH] = { 210, 34, 32, 1 }, + [QCOM_RPM_HDMI_SWITCH] = { 211, 35, 33, 1 }, + [QCOM_RPM_DDR_DMM] = { 212, 36, 34, 2 }, + [QCOM_RPM_VDDMIN_GPIO] = { 215, 40, 39, 1 }, + [QCOM_RPM_SMB208_S1a] = { 216, 41, 90, 2 }, + [QCOM_RPM_SMB208_S1b] = { 218, 43, 91, 2 }, + [QCOM_RPM_SMB208_S2a] = { 220, 45, 92, 2 }, + [QCOM_RPM_SMB208_S2b] = { 222, 47, 93, 2 }, +}; + +static const struct qcom_rpm_data ipq806x_template = { + .version = 3, + .resource_table = ipq806x_rpm_resource_table, + .n_resources = ARRAY_SIZE(ipq806x_rpm_resource_table), +}; + +static const struct of_device_id qcom_rpm_of_match[] = { + { .compatible = "qcom,rpm-apq8064", .data = &apq8064_template }, + { .compatible = "qcom,rpm-msm8660", .data = &msm8660_template }, + { .compatible = "qcom,rpm-msm8960", .data = &msm8960_template }, + { .compatible = "qcom,rpm-ipq8064", .data = &ipq806x_template }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_rpm_of_match); + +int qcom_rpm_write(struct qcom_rpm *rpm, + int state, + int resource, + u32 *buf, size_t count) +{ + const struct qcom_rpm_resource *res; + const struct qcom_rpm_data *data = rpm->data; + u32 sel_mask[RPM_SELECT_SIZE] = { 0 }; + int left; + int ret = 0; + int i; + + if (WARN_ON(resource < 0 || resource >= data->n_resources)) + return -EINVAL; + + res = &data->resource_table[resource]; + if (WARN_ON(res->size != count)) + return -EINVAL; + + mutex_lock(&rpm->lock); + + for (i = 0; i < res->size; i++) + writel_relaxed(buf[i], RPM_REQ_REG(rpm, res->target_id + i)); + + bitmap_set((unsigned long *)sel_mask, res->select_id, 1); + for (i = 0; i < ARRAY_SIZE(sel_mask); i++) { + writel_relaxed(sel_mask[i], + RPM_CTRL_REG(rpm, RPM_REQ_SELECT + i)); + } + + writel_relaxed(BIT(state), RPM_CTRL_REG(rpm, RPM_REQUEST_CONTEXT)); + + reinit_completion(&rpm->ack); + regmap_write(rpm->ipc_regmap, rpm->ipc_offset, BIT(rpm->ipc_bit)); + + left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT); + if (!left) + ret = -ETIMEDOUT; + else if (rpm->ack_status & RPM_REJECTED) + ret = -EIO; + + mutex_unlock(&rpm->lock); + + return ret; +} +EXPORT_SYMBOL(qcom_rpm_write); + +static irqreturn_t qcom_rpm_ack_interrupt(int irq, void *dev) +{ + struct qcom_rpm *rpm = dev; + u32 ack; + int i; + + ack = readl_relaxed(RPM_CTRL_REG(rpm, RPM_ACK_CONTEXT)); + for (i = 0; i < RPM_SELECT_SIZE; i++) + writel_relaxed(0, RPM_CTRL_REG(rpm, RPM_ACK_SELECTOR + i)); + writel(0, RPM_CTRL_REG(rpm, RPM_ACK_CONTEXT)); + + if (ack & RPM_NOTIFICATION) { + dev_warn(rpm->dev, "ignoring notification!\n"); + } else { + rpm->ack_status = ack; + complete(&rpm->ack); + } + + return IRQ_HANDLED; +} + +static irqreturn_t qcom_rpm_err_interrupt(int irq, void *dev) +{ + struct qcom_rpm *rpm = dev; + + regmap_write(rpm->ipc_regmap, rpm->ipc_offset, BIT(rpm->ipc_bit)); + dev_err(rpm->dev, "RPM triggered fatal error\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t qcom_rpm_wakeup_interrupt(int irq, void *dev) +{ + return IRQ_HANDLED; +} + +static int qcom_rpm_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *syscon_np; + struct resource *res; + struct qcom_rpm *rpm; + u32 fw_version[3]; + int irq_wakeup; + int irq_ack; + int irq_err; + int ret; + + rpm = devm_kzalloc(&pdev->dev, sizeof(*rpm), GFP_KERNEL); + if (!rpm) + return -ENOMEM; + + rpm->dev = &pdev->dev; + mutex_init(&rpm->lock); + init_completion(&rpm->ack); + + irq_ack = platform_get_irq_byname(pdev, "ack"); + if (irq_ack < 0) { + dev_err(&pdev->dev, "required ack interrupt missing\n"); + return irq_ack; + } + + irq_err = platform_get_irq_byname(pdev, "err"); + if (irq_err < 0) { + dev_err(&pdev->dev, "required err interrupt missing\n"); + return irq_err; + } + + irq_wakeup = platform_get_irq_byname(pdev, "wakeup"); + if (irq_wakeup < 0) { + dev_err(&pdev->dev, "required wakeup interrupt missing\n"); + return irq_wakeup; + } + + match = of_match_device(qcom_rpm_of_match, &pdev->dev); + rpm->data = match->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rpm->status_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rpm->status_regs)) + return PTR_ERR(rpm->status_regs); + rpm->ctrl_regs = rpm->status_regs + 0x400; + rpm->req_regs = rpm->status_regs + 0x600; + + syscon_np = of_parse_phandle(pdev->dev.of_node, "qcom,ipc", 0); + if (!syscon_np) { + dev_err(&pdev->dev, "no qcom,ipc node\n"); + return -ENODEV; + } + + rpm->ipc_regmap = syscon_node_to_regmap(syscon_np); + if (IS_ERR(rpm->ipc_regmap)) + return PTR_ERR(rpm->ipc_regmap); + + ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,ipc", 1, + &rpm->ipc_offset); + if (ret < 0) { + dev_err(&pdev->dev, "no offset in qcom,ipc\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,ipc", 2, + &rpm->ipc_bit); + if (ret < 0) { + dev_err(&pdev->dev, "no bit in qcom,ipc\n"); + return -EINVAL; + } + + dev_set_drvdata(&pdev->dev, rpm); + + fw_version[0] = readl(RPM_STATUS_REG(rpm, 0)); + fw_version[1] = readl(RPM_STATUS_REG(rpm, 1)); + fw_version[2] = readl(RPM_STATUS_REG(rpm, 2)); + if (fw_version[0] != rpm->data->version) { + dev_err(&pdev->dev, + "RPM version %u.%u.%u incompatible with driver version %u", + fw_version[0], + fw_version[1], + fw_version[2], + rpm->data->version); + return -EFAULT; + } + + dev_info(&pdev->dev, "RPM firmware %u.%u.%u\n", fw_version[0], + fw_version[1], + fw_version[2]); + + ret = devm_request_irq(&pdev->dev, + irq_ack, + qcom_rpm_ack_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND, + "qcom_rpm_ack", + rpm); + if (ret) { + dev_err(&pdev->dev, "failed to request ack interrupt\n"); + return ret; + } + + ret = irq_set_irq_wake(irq_ack, 1); + if (ret) + dev_warn(&pdev->dev, "failed to mark ack irq as wakeup\n"); + + ret = devm_request_irq(&pdev->dev, + irq_err, + qcom_rpm_err_interrupt, + IRQF_TRIGGER_RISING, + "qcom_rpm_err", + rpm); + if (ret) { + dev_err(&pdev->dev, "failed to request err interrupt\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, + irq_wakeup, + qcom_rpm_wakeup_interrupt, + IRQF_TRIGGER_RISING, + "qcom_rpm_wakeup", + rpm); + if (ret) { + dev_err(&pdev->dev, "failed to request wakeup interrupt\n"); + return ret; + } + + ret = irq_set_irq_wake(irq_wakeup, 1); + if (ret) + dev_warn(&pdev->dev, "failed to mark wakeup irq as wakeup\n"); + + return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); +} + +static int qcom_rpm_remove(struct platform_device *pdev) +{ + of_platform_depopulate(&pdev->dev); + return 0; +} + +static struct platform_driver qcom_rpm_driver = { + .probe = qcom_rpm_probe, + .remove = qcom_rpm_remove, + .driver = { + .name = "qcom_rpm", + .of_match_table = qcom_rpm_of_match, + }, +}; + +static int __init qcom_rpm_init(void) +{ + return platform_driver_register(&qcom_rpm_driver); +} +arch_initcall(qcom_rpm_init); + +static void __exit qcom_rpm_exit(void) +{ + platform_driver_unregister(&qcom_rpm_driver); +} +module_exit(qcom_rpm_exit) + +MODULE_DESCRIPTION("Qualcomm Resource Power Manager driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); diff --git a/kernel/drivers/mfd/rc5t583-irq.c b/kernel/drivers/mfd/rc5t583-irq.c new file mode 100644 index 000000000..bb8502020 --- /dev/null +++ b/kernel/drivers/mfd/rc5t583-irq.c @@ -0,0 +1,407 @@ +/* + * Interrupt driver for RICOH583 power management chip. + * + * Copyright (c) 2011-2012, NVIDIA CORPORATION. All rights reserved. + * Author: Laxman dewangan <ldewangan@nvidia.com> + * + * based on code + * Copyright (C) 2011 RICOH COMPANY,LTD + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + * + */ +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/mfd/rc5t583.h> + +enum int_type { + SYS_INT = 0x1, + DCDC_INT = 0x2, + RTC_INT = 0x4, + ADC_INT = 0x8, + GPIO_INT = 0x10, +}; + +static int gpedge_add[] = { + RC5T583_GPIO_GPEDGE2, + RC5T583_GPIO_GPEDGE2 +}; + +static int irq_en_add[] = { + RC5T583_INT_EN_SYS1, + RC5T583_INT_EN_SYS2, + RC5T583_INT_EN_DCDC, + RC5T583_INT_EN_RTC, + RC5T583_INT_EN_ADC1, + RC5T583_INT_EN_ADC2, + RC5T583_INT_EN_ADC3, + RC5T583_GPIO_EN_INT +}; + +static int irq_mon_add[] = { + RC5T583_INT_MON_SYS1, + RC5T583_INT_MON_SYS2, + RC5T583_INT_MON_DCDC, + RC5T583_INT_MON_RTC, + RC5T583_INT_IR_ADCL, + RC5T583_INT_IR_ADCH, + RC5T583_INT_IR_ADCEND, + RC5T583_INT_IR_GPIOF, + RC5T583_INT_IR_GPIOR +}; + +static int irq_clr_add[] = { + RC5T583_INT_IR_SYS1, + RC5T583_INT_IR_SYS2, + RC5T583_INT_IR_DCDC, + RC5T583_INT_IR_RTC, + RC5T583_INT_IR_ADCL, + RC5T583_INT_IR_ADCH, + RC5T583_INT_IR_ADCEND, + RC5T583_INT_IR_GPIOF, + RC5T583_INT_IR_GPIOR +}; + +static int main_int_type[] = { + SYS_INT, + SYS_INT, + DCDC_INT, + RTC_INT, + ADC_INT, + ADC_INT, + ADC_INT, + GPIO_INT, + GPIO_INT, +}; + +struct rc5t583_irq_data { + u8 int_type; + u8 master_bit; + u8 int_en_bit; + u8 mask_reg_index; + int grp_index; +}; + +#define RC5T583_IRQ(_int_type, _master_bit, _grp_index, \ + _int_bit, _mask_ind) \ + { \ + .int_type = _int_type, \ + .master_bit = _master_bit, \ + .grp_index = _grp_index, \ + .int_en_bit = _int_bit, \ + .mask_reg_index = _mask_ind, \ + } + +static const struct rc5t583_irq_data rc5t583_irqs[RC5T583_MAX_IRQS] = { + [RC5T583_IRQ_ONKEY] = RC5T583_IRQ(SYS_INT, 0, 0, 0, 0), + [RC5T583_IRQ_ACOK] = RC5T583_IRQ(SYS_INT, 0, 1, 1, 0), + [RC5T583_IRQ_LIDOPEN] = RC5T583_IRQ(SYS_INT, 0, 2, 2, 0), + [RC5T583_IRQ_PREOT] = RC5T583_IRQ(SYS_INT, 0, 3, 3, 0), + [RC5T583_IRQ_CLKSTP] = RC5T583_IRQ(SYS_INT, 0, 4, 4, 0), + [RC5T583_IRQ_ONKEY_OFF] = RC5T583_IRQ(SYS_INT, 0, 5, 5, 0), + [RC5T583_IRQ_WD] = RC5T583_IRQ(SYS_INT, 0, 7, 7, 0), + [RC5T583_IRQ_EN_PWRREQ1] = RC5T583_IRQ(SYS_INT, 0, 8, 0, 1), + [RC5T583_IRQ_EN_PWRREQ2] = RC5T583_IRQ(SYS_INT, 0, 9, 1, 1), + [RC5T583_IRQ_PRE_VINDET] = RC5T583_IRQ(SYS_INT, 0, 10, 2, 1), + + [RC5T583_IRQ_DC0LIM] = RC5T583_IRQ(DCDC_INT, 1, 0, 0, 2), + [RC5T583_IRQ_DC1LIM] = RC5T583_IRQ(DCDC_INT, 1, 1, 1, 2), + [RC5T583_IRQ_DC2LIM] = RC5T583_IRQ(DCDC_INT, 1, 2, 2, 2), + [RC5T583_IRQ_DC3LIM] = RC5T583_IRQ(DCDC_INT, 1, 3, 3, 2), + + [RC5T583_IRQ_CTC] = RC5T583_IRQ(RTC_INT, 2, 0, 0, 3), + [RC5T583_IRQ_YALE] = RC5T583_IRQ(RTC_INT, 2, 5, 5, 3), + [RC5T583_IRQ_DALE] = RC5T583_IRQ(RTC_INT, 2, 6, 6, 3), + [RC5T583_IRQ_WALE] = RC5T583_IRQ(RTC_INT, 2, 7, 7, 3), + + [RC5T583_IRQ_AIN1L] = RC5T583_IRQ(ADC_INT, 3, 0, 0, 4), + [RC5T583_IRQ_AIN2L] = RC5T583_IRQ(ADC_INT, 3, 1, 1, 4), + [RC5T583_IRQ_AIN3L] = RC5T583_IRQ(ADC_INT, 3, 2, 2, 4), + [RC5T583_IRQ_VBATL] = RC5T583_IRQ(ADC_INT, 3, 3, 3, 4), + [RC5T583_IRQ_VIN3L] = RC5T583_IRQ(ADC_INT, 3, 4, 4, 4), + [RC5T583_IRQ_VIN8L] = RC5T583_IRQ(ADC_INT, 3, 5, 5, 4), + [RC5T583_IRQ_AIN1H] = RC5T583_IRQ(ADC_INT, 3, 6, 0, 5), + [RC5T583_IRQ_AIN2H] = RC5T583_IRQ(ADC_INT, 3, 7, 1, 5), + [RC5T583_IRQ_AIN3H] = RC5T583_IRQ(ADC_INT, 3, 8, 2, 5), + [RC5T583_IRQ_VBATH] = RC5T583_IRQ(ADC_INT, 3, 9, 3, 5), + [RC5T583_IRQ_VIN3H] = RC5T583_IRQ(ADC_INT, 3, 10, 4, 5), + [RC5T583_IRQ_VIN8H] = RC5T583_IRQ(ADC_INT, 3, 11, 5, 5), + [RC5T583_IRQ_ADCEND] = RC5T583_IRQ(ADC_INT, 3, 12, 0, 6), + + [RC5T583_IRQ_GPIO0] = RC5T583_IRQ(GPIO_INT, 4, 0, 0, 7), + [RC5T583_IRQ_GPIO1] = RC5T583_IRQ(GPIO_INT, 4, 1, 1, 7), + [RC5T583_IRQ_GPIO2] = RC5T583_IRQ(GPIO_INT, 4, 2, 2, 7), + [RC5T583_IRQ_GPIO3] = RC5T583_IRQ(GPIO_INT, 4, 3, 3, 7), + [RC5T583_IRQ_GPIO4] = RC5T583_IRQ(GPIO_INT, 4, 4, 4, 7), + [RC5T583_IRQ_GPIO5] = RC5T583_IRQ(GPIO_INT, 4, 5, 5, 7), + [RC5T583_IRQ_GPIO6] = RC5T583_IRQ(GPIO_INT, 4, 6, 6, 7), + [RC5T583_IRQ_GPIO7] = RC5T583_IRQ(GPIO_INT, 4, 7, 7, 7), +}; + +static void rc5t583_irq_lock(struct irq_data *irq_data) +{ + struct rc5t583 *rc5t583 = irq_data_get_irq_chip_data(irq_data); + mutex_lock(&rc5t583->irq_lock); +} + +static void rc5t583_irq_unmask(struct irq_data *irq_data) +{ + struct rc5t583 *rc5t583 = irq_data_get_irq_chip_data(irq_data); + unsigned int __irq = irq_data->irq - rc5t583->irq_base; + const struct rc5t583_irq_data *data = &rc5t583_irqs[__irq]; + + rc5t583->group_irq_en[data->grp_index] |= 1 << data->grp_index; + rc5t583->intc_inten_reg |= 1 << data->master_bit; + rc5t583->irq_en_reg[data->mask_reg_index] |= 1 << data->int_en_bit; +} + +static void rc5t583_irq_mask(struct irq_data *irq_data) +{ + struct rc5t583 *rc5t583 = irq_data_get_irq_chip_data(irq_data); + unsigned int __irq = irq_data->irq - rc5t583->irq_base; + const struct rc5t583_irq_data *data = &rc5t583_irqs[__irq]; + + rc5t583->group_irq_en[data->grp_index] &= ~(1 << data->grp_index); + if (!rc5t583->group_irq_en[data->grp_index]) + rc5t583->intc_inten_reg &= ~(1 << data->master_bit); + + rc5t583->irq_en_reg[data->mask_reg_index] &= ~(1 << data->int_en_bit); +} + +static int rc5t583_irq_set_type(struct irq_data *irq_data, unsigned int type) +{ + struct rc5t583 *rc5t583 = irq_data_get_irq_chip_data(irq_data); + unsigned int __irq = irq_data->irq - rc5t583->irq_base; + const struct rc5t583_irq_data *data = &rc5t583_irqs[__irq]; + int val = 0; + int gpedge_index; + int gpedge_bit_pos; + + /* Supporting only trigger level inetrrupt */ + if ((data->int_type & GPIO_INT) && (type & IRQ_TYPE_EDGE_BOTH)) { + gpedge_index = data->int_en_bit / 4; + gpedge_bit_pos = data->int_en_bit % 4; + + if (type & IRQ_TYPE_EDGE_FALLING) + val |= 0x2; + + if (type & IRQ_TYPE_EDGE_RISING) + val |= 0x1; + + rc5t583->gpedge_reg[gpedge_index] &= ~(3 << gpedge_bit_pos); + rc5t583->gpedge_reg[gpedge_index] |= (val << gpedge_bit_pos); + rc5t583_irq_unmask(irq_data); + return 0; + } + return -EINVAL; +} + +static void rc5t583_irq_sync_unlock(struct irq_data *irq_data) +{ + struct rc5t583 *rc5t583 = irq_data_get_irq_chip_data(irq_data); + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(rc5t583->gpedge_reg); i++) { + ret = rc5t583_write(rc5t583->dev, gpedge_add[i], + rc5t583->gpedge_reg[i]); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + gpedge_add[i], ret); + } + + for (i = 0; i < ARRAY_SIZE(rc5t583->irq_en_reg); i++) { + ret = rc5t583_write(rc5t583->dev, irq_en_add[i], + rc5t583->irq_en_reg[i]); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + irq_en_add[i], ret); + } + + ret = rc5t583_write(rc5t583->dev, RC5T583_INTC_INTEN, + rc5t583->intc_inten_reg); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + RC5T583_INTC_INTEN, ret); + + mutex_unlock(&rc5t583->irq_lock); +} +#ifdef CONFIG_PM_SLEEP +static int rc5t583_irq_set_wake(struct irq_data *irq_data, unsigned int on) +{ + struct rc5t583 *rc5t583 = irq_data_get_irq_chip_data(irq_data); + return irq_set_irq_wake(rc5t583->chip_irq, on); +} +#else +#define rc5t583_irq_set_wake NULL +#endif + +static irqreturn_t rc5t583_irq(int irq, void *data) +{ + struct rc5t583 *rc5t583 = data; + uint8_t int_sts[RC5T583_MAX_INTERRUPT_MASK_REGS]; + uint8_t master_int = 0; + int i; + int ret; + unsigned int rtc_int_sts = 0; + + /* Clear the status */ + for (i = 0; i < RC5T583_MAX_INTERRUPT_MASK_REGS; i++) + int_sts[i] = 0; + + ret = rc5t583_read(rc5t583->dev, RC5T583_INTC_INTMON, &master_int); + if (ret < 0) { + dev_err(rc5t583->dev, + "Error in reading reg 0x%02x error: %d\n", + RC5T583_INTC_INTMON, ret); + return IRQ_HANDLED; + } + + for (i = 0; i < RC5T583_MAX_INTERRUPT_MASK_REGS; ++i) { + if (!(master_int & main_int_type[i])) + continue; + + ret = rc5t583_read(rc5t583->dev, irq_mon_add[i], &int_sts[i]); + if (ret < 0) { + dev_warn(rc5t583->dev, + "Error in reading reg 0x%02x error: %d\n", + irq_mon_add[i], ret); + int_sts[i] = 0; + continue; + } + + if (main_int_type[i] & RTC_INT) { + rtc_int_sts = 0; + if (int_sts[i] & 0x1) + rtc_int_sts |= BIT(6); + if (int_sts[i] & 0x2) + rtc_int_sts |= BIT(7); + if (int_sts[i] & 0x4) + rtc_int_sts |= BIT(0); + if (int_sts[i] & 0x8) + rtc_int_sts |= BIT(5); + } + + ret = rc5t583_write(rc5t583->dev, irq_clr_add[i], + ~int_sts[i]); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in reading reg 0x%02x error: %d\n", + irq_clr_add[i], ret); + + if (main_int_type[i] & RTC_INT) + int_sts[i] = rtc_int_sts; + } + + /* Merge gpio interrupts for rising and falling case*/ + int_sts[7] |= int_sts[8]; + + /* Call interrupt handler if enabled */ + for (i = 0; i < RC5T583_MAX_IRQS; ++i) { + const struct rc5t583_irq_data *data = &rc5t583_irqs[i]; + if ((int_sts[data->mask_reg_index] & (1 << data->int_en_bit)) && + (rc5t583->group_irq_en[data->master_bit] & + (1 << data->grp_index))) + handle_nested_irq(rc5t583->irq_base + i); + } + + return IRQ_HANDLED; +} + +static struct irq_chip rc5t583_irq_chip = { + .name = "rc5t583-irq", + .irq_mask = rc5t583_irq_mask, + .irq_unmask = rc5t583_irq_unmask, + .irq_bus_lock = rc5t583_irq_lock, + .irq_bus_sync_unlock = rc5t583_irq_sync_unlock, + .irq_set_type = rc5t583_irq_set_type, + .irq_set_wake = rc5t583_irq_set_wake, +}; + +int rc5t583_irq_init(struct rc5t583 *rc5t583, int irq, int irq_base) +{ + int i, ret; + + if (!irq_base) { + dev_warn(rc5t583->dev, "No interrupt support on IRQ base\n"); + return -EINVAL; + } + + mutex_init(&rc5t583->irq_lock); + + /* Initailize all int register to 0 */ + for (i = 0; i < RC5T583_MAX_INTERRUPT_EN_REGS; i++) { + ret = rc5t583_write(rc5t583->dev, irq_en_add[i], + rc5t583->irq_en_reg[i]); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + irq_en_add[i], ret); + } + + for (i = 0; i < RC5T583_MAX_GPEDGE_REG; i++) { + ret = rc5t583_write(rc5t583->dev, gpedge_add[i], + rc5t583->gpedge_reg[i]); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + gpedge_add[i], ret); + } + + ret = rc5t583_write(rc5t583->dev, RC5T583_INTC_INTEN, 0x0); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + RC5T583_INTC_INTEN, ret); + + /* Clear all interrupts in case they woke up active. */ + for (i = 0; i < RC5T583_MAX_INTERRUPT_MASK_REGS; i++) { + ret = rc5t583_write(rc5t583->dev, irq_clr_add[i], 0); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + irq_clr_add[i], ret); + } + + rc5t583->irq_base = irq_base; + rc5t583->chip_irq = irq; + + for (i = 0; i < RC5T583_MAX_IRQS; i++) { + int __irq = i + rc5t583->irq_base; + irq_set_chip_data(__irq, rc5t583); + irq_set_chip_and_handler(__irq, &rc5t583_irq_chip, + handle_simple_irq); + irq_set_nested_thread(__irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(__irq, IRQF_VALID); +#endif + } + + ret = request_threaded_irq(irq, NULL, rc5t583_irq, IRQF_ONESHOT, + "rc5t583", rc5t583); + if (ret < 0) + dev_err(rc5t583->dev, + "Error in registering interrupt error: %d\n", ret); + return ret; +} + +int rc5t583_irq_exit(struct rc5t583 *rc5t583) +{ + if (rc5t583->chip_irq) + free_irq(rc5t583->chip_irq, rc5t583); + return 0; +} diff --git a/kernel/drivers/mfd/rc5t583.c b/kernel/drivers/mfd/rc5t583.c new file mode 100644 index 000000000..df276ad9f --- /dev/null +++ b/kernel/drivers/mfd/rc5t583.c @@ -0,0 +1,347 @@ +/* + * Core driver access RC5T583 power management chip. + * + * Copyright (c) 2011-2012, NVIDIA CORPORATION. All rights reserved. + * Author: Laxman dewangan <ldewangan@nvidia.com> + * + * Based on code + * Copyright (C) 2011 RICOH COMPANY,LTD + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + * + */ +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rc5t583.h> +#include <linux/regmap.h> + +#define RICOH_ONOFFSEL_REG 0x10 +#define RICOH_SWCTL_REG 0x5E + +struct deepsleep_control_data { + u8 reg_add; + u8 ds_pos_bit; +}; + +#define DEEPSLEEP_INIT(_id, _reg, _pos) \ + { \ + .reg_add = RC5T583_##_reg, \ + .ds_pos_bit = _pos, \ + } + +static struct deepsleep_control_data deepsleep_data[] = { + DEEPSLEEP_INIT(DC0, SLPSEQ1, 0), + DEEPSLEEP_INIT(DC1, SLPSEQ1, 4), + DEEPSLEEP_INIT(DC2, SLPSEQ2, 0), + DEEPSLEEP_INIT(DC3, SLPSEQ2, 4), + DEEPSLEEP_INIT(LDO0, SLPSEQ3, 0), + DEEPSLEEP_INIT(LDO1, SLPSEQ3, 4), + DEEPSLEEP_INIT(LDO2, SLPSEQ4, 0), + DEEPSLEEP_INIT(LDO3, SLPSEQ4, 4), + DEEPSLEEP_INIT(LDO4, SLPSEQ5, 0), + DEEPSLEEP_INIT(LDO5, SLPSEQ5, 4), + DEEPSLEEP_INIT(LDO6, SLPSEQ6, 0), + DEEPSLEEP_INIT(LDO7, SLPSEQ6, 4), + DEEPSLEEP_INIT(LDO8, SLPSEQ7, 0), + DEEPSLEEP_INIT(LDO9, SLPSEQ7, 4), + DEEPSLEEP_INIT(PSO0, SLPSEQ8, 0), + DEEPSLEEP_INIT(PSO1, SLPSEQ8, 4), + DEEPSLEEP_INIT(PSO2, SLPSEQ9, 0), + DEEPSLEEP_INIT(PSO3, SLPSEQ9, 4), + DEEPSLEEP_INIT(PSO4, SLPSEQ10, 0), + DEEPSLEEP_INIT(PSO5, SLPSEQ10, 4), + DEEPSLEEP_INIT(PSO6, SLPSEQ11, 0), + DEEPSLEEP_INIT(PSO7, SLPSEQ11, 4), +}; + +#define EXT_PWR_REQ \ + (RC5T583_EXT_PWRREQ1_CONTROL | RC5T583_EXT_PWRREQ2_CONTROL) + +static const struct mfd_cell rc5t583_subdevs[] = { + {.name = "rc5t583-gpio",}, + {.name = "rc5t583-regulator",}, + {.name = "rc5t583-rtc", }, + {.name = "rc5t583-key", } +}; + +static int __rc5t583_set_ext_pwrreq1_control(struct device *dev, + int id, int ext_pwr, int slots) +{ + int ret; + uint8_t sleepseq_val = 0; + unsigned int en_bit; + unsigned int slot_bit; + + if (id == RC5T583_DS_DC0) { + dev_err(dev, "PWRREQ1 is invalid control for rail %d\n", id); + return -EINVAL; + } + + en_bit = deepsleep_data[id].ds_pos_bit; + slot_bit = en_bit + 1; + ret = rc5t583_read(dev, deepsleep_data[id].reg_add, &sleepseq_val); + if (ret < 0) { + dev_err(dev, "Error in reading reg 0x%x\n", + deepsleep_data[id].reg_add); + return ret; + } + + sleepseq_val &= ~(0xF << en_bit); + sleepseq_val |= BIT(en_bit); + sleepseq_val |= ((slots & 0x7) << slot_bit); + ret = rc5t583_set_bits(dev, RICOH_ONOFFSEL_REG, BIT(1)); + if (ret < 0) { + dev_err(dev, "Error in updating the 0x%02x register\n", + RICOH_ONOFFSEL_REG); + return ret; + } + + ret = rc5t583_write(dev, deepsleep_data[id].reg_add, sleepseq_val); + if (ret < 0) { + dev_err(dev, "Error in writing reg 0x%x\n", + deepsleep_data[id].reg_add); + return ret; + } + + if (id == RC5T583_DS_LDO4) { + ret = rc5t583_write(dev, RICOH_SWCTL_REG, 0x1); + if (ret < 0) + dev_err(dev, "Error in writing reg 0x%x\n", + RICOH_SWCTL_REG); + } + return ret; +} + +static int __rc5t583_set_ext_pwrreq2_control(struct device *dev, + int id, int ext_pwr) +{ + int ret; + + if (id != RC5T583_DS_DC0) { + dev_err(dev, "PWRREQ2 is invalid control for rail %d\n", id); + return -EINVAL; + } + + ret = rc5t583_set_bits(dev, RICOH_ONOFFSEL_REG, BIT(2)); + if (ret < 0) + dev_err(dev, "Error in updating the ONOFFSEL 0x10 register\n"); + return ret; +} + +int rc5t583_ext_power_req_config(struct device *dev, int ds_id, + int ext_pwr_req, int deepsleep_slot_nr) +{ + if ((ext_pwr_req & EXT_PWR_REQ) == EXT_PWR_REQ) + return -EINVAL; + + if (ext_pwr_req & RC5T583_EXT_PWRREQ1_CONTROL) + return __rc5t583_set_ext_pwrreq1_control(dev, ds_id, + ext_pwr_req, deepsleep_slot_nr); + + if (ext_pwr_req & RC5T583_EXT_PWRREQ2_CONTROL) + return __rc5t583_set_ext_pwrreq2_control(dev, + ds_id, ext_pwr_req); + return 0; +} +EXPORT_SYMBOL(rc5t583_ext_power_req_config); + +static int rc5t583_clear_ext_power_req(struct rc5t583 *rc5t583, + struct rc5t583_platform_data *pdata) +{ + int ret; + int i; + uint8_t on_off_val = 0; + + /* Clear ONOFFSEL register */ + if (pdata->enable_shutdown) + on_off_val = 0x1; + + ret = rc5t583_write(rc5t583->dev, RICOH_ONOFFSEL_REG, on_off_val); + if (ret < 0) + dev_warn(rc5t583->dev, "Error in writing reg %d error: %d\n", + RICOH_ONOFFSEL_REG, ret); + + ret = rc5t583_write(rc5t583->dev, RICOH_SWCTL_REG, 0x0); + if (ret < 0) + dev_warn(rc5t583->dev, "Error in writing reg %d error: %d\n", + RICOH_SWCTL_REG, ret); + + /* Clear sleep sequence register */ + for (i = RC5T583_SLPSEQ1; i <= RC5T583_SLPSEQ11; ++i) { + ret = rc5t583_write(rc5t583->dev, i, 0x0); + if (ret < 0) + dev_warn(rc5t583->dev, + "Error in writing reg 0x%02x error: %d\n", + i, ret); + } + return 0; +} + +static bool volatile_reg(struct device *dev, unsigned int reg) +{ + /* Enable caching in interrupt registers */ + switch (reg) { + case RC5T583_INT_EN_SYS1: + case RC5T583_INT_EN_SYS2: + case RC5T583_INT_EN_DCDC: + case RC5T583_INT_EN_RTC: + case RC5T583_INT_EN_ADC1: + case RC5T583_INT_EN_ADC2: + case RC5T583_INT_EN_ADC3: + case RC5T583_GPIO_GPEDGE1: + case RC5T583_GPIO_GPEDGE2: + case RC5T583_GPIO_EN_INT: + return false; + + case RC5T583_GPIO_MON_IOIN: + /* This is gpio input register */ + return true; + + default: + /* Enable caching in gpio registers */ + if ((reg >= RC5T583_GPIO_IOSEL) && + (reg <= RC5T583_GPIO_GPOFUNC)) + return false; + + /* Enable caching in sleep seq registers */ + if ((reg >= RC5T583_SLPSEQ1) && (reg <= RC5T583_SLPSEQ11)) + return false; + + /* Enable caching of regulator registers */ + if ((reg >= RC5T583_REG_DC0CTL) && (reg <= RC5T583_REG_SR3CTL)) + return false; + if ((reg >= RC5T583_REG_LDOEN1) && + (reg <= RC5T583_REG_LDO9DAC_DS)) + return false; + + break; + } + + return true; +} + +static const struct regmap_config rc5t583_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = volatile_reg, + .max_register = RC5T583_MAX_REGS, + .num_reg_defaults_raw = RC5T583_MAX_REGS, + .cache_type = REGCACHE_RBTREE, +}; + +static int rc5t583_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rc5t583 *rc5t583; + struct rc5t583_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret; + bool irq_init_success = false; + + if (!pdata) { + dev_err(&i2c->dev, "Err: Platform data not found\n"); + return -EINVAL; + } + + rc5t583 = devm_kzalloc(&i2c->dev, sizeof(struct rc5t583), GFP_KERNEL); + if (!rc5t583) { + dev_err(&i2c->dev, "Memory allocation failed\n"); + return -ENOMEM; + } + + rc5t583->dev = &i2c->dev; + i2c_set_clientdata(i2c, rc5t583); + + rc5t583->regmap = devm_regmap_init_i2c(i2c, &rc5t583_regmap_config); + if (IS_ERR(rc5t583->regmap)) { + ret = PTR_ERR(rc5t583->regmap); + dev_err(&i2c->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + ret = rc5t583_clear_ext_power_req(rc5t583, pdata); + if (ret < 0) + return ret; + + if (i2c->irq) { + ret = rc5t583_irq_init(rc5t583, i2c->irq, pdata->irq_base); + /* Still continue with warning, if irq init fails */ + if (ret) + dev_warn(&i2c->dev, "IRQ init failed: %d\n", ret); + else + irq_init_success = true; + } + + ret = mfd_add_devices(rc5t583->dev, -1, rc5t583_subdevs, + ARRAY_SIZE(rc5t583_subdevs), NULL, 0, NULL); + if (ret) { + dev_err(&i2c->dev, "add mfd devices failed: %d\n", ret); + goto err_add_devs; + } + + return 0; + +err_add_devs: + if (irq_init_success) + rc5t583_irq_exit(rc5t583); + return ret; +} + +static int rc5t583_i2c_remove(struct i2c_client *i2c) +{ + struct rc5t583 *rc5t583 = i2c_get_clientdata(i2c); + + mfd_remove_devices(rc5t583->dev); + rc5t583_irq_exit(rc5t583); + return 0; +} + +static const struct i2c_device_id rc5t583_i2c_id[] = { + {.name = "rc5t583", .driver_data = 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, rc5t583_i2c_id); + +static struct i2c_driver rc5t583_i2c_driver = { + .driver = { + .name = "rc5t583", + .owner = THIS_MODULE, + }, + .probe = rc5t583_i2c_probe, + .remove = rc5t583_i2c_remove, + .id_table = rc5t583_i2c_id, +}; + +static int __init rc5t583_i2c_init(void) +{ + return i2c_add_driver(&rc5t583_i2c_driver); +} +subsys_initcall(rc5t583_i2c_init); + +static void __exit rc5t583_i2c_exit(void) +{ + i2c_del_driver(&rc5t583_i2c_driver); +} + +module_exit(rc5t583_i2c_exit); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("RICOH RC5T583 power management system device driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/rdc321x-southbridge.c b/kernel/drivers/mfd/rdc321x-southbridge.c new file mode 100644 index 000000000..6575585f1 --- /dev/null +++ b/kernel/drivers/mfd/rdc321x-southbridge.c @@ -0,0 +1,115 @@ +/* + * RDC321x MFD southbridge driver + * + * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org> + * Copyright (C) 2010 Bernhard Loos <bernhardloos@googlemail.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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rdc321x.h> + +static struct rdc321x_wdt_pdata rdc321x_wdt_pdata; + +static struct resource rdc321x_wdt_resource[] = { + { + .name = "wdt-reg", + .start = RDC321X_WDT_CTRL, + .end = RDC321X_WDT_CTRL + 0x3, + .flags = IORESOURCE_IO, + } +}; + +static struct rdc321x_gpio_pdata rdc321x_gpio_pdata = { + .max_gpios = RDC321X_NUM_GPIO, +}; + +static struct resource rdc321x_gpio_resources[] = { + { + .name = "gpio-reg1", + .start = RDC321X_GPIO_CTRL_REG1, + .end = RDC321X_GPIO_CTRL_REG1 + 0x7, + .flags = IORESOURCE_IO, + }, { + .name = "gpio-reg2", + .start = RDC321X_GPIO_CTRL_REG2, + .end = RDC321X_GPIO_CTRL_REG2 + 0x7, + .flags = IORESOURCE_IO, + } +}; + +static const struct mfd_cell rdc321x_sb_cells[] = { + { + .name = "rdc321x-wdt", + .resources = rdc321x_wdt_resource, + .num_resources = ARRAY_SIZE(rdc321x_wdt_resource), + .platform_data = &rdc321x_wdt_pdata, + .pdata_size = sizeof(rdc321x_wdt_pdata), + }, { + .name = "rdc321x-gpio", + .resources = rdc321x_gpio_resources, + .num_resources = ARRAY_SIZE(rdc321x_gpio_resources), + .platform_data = &rdc321x_gpio_pdata, + .pdata_size = sizeof(rdc321x_gpio_pdata), + }, +}; + +static int rdc321x_sb_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable device\n"); + return err; + } + + rdc321x_gpio_pdata.sb_pdev = pdev; + rdc321x_wdt_pdata.sb_pdev = pdev; + + return mfd_add_devices(&pdev->dev, -1, + rdc321x_sb_cells, ARRAY_SIZE(rdc321x_sb_cells), + NULL, 0, NULL); +} + +static void rdc321x_sb_remove(struct pci_dev *pdev) +{ + mfd_remove_devices(&pdev->dev); +} + +static const struct pci_device_id rdc321x_sb_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_RDC, PCI_DEVICE_ID_RDC_R6030) }, + {} +}; +MODULE_DEVICE_TABLE(pci, rdc321x_sb_table); + +static struct pci_driver rdc321x_sb_driver = { + .name = "RDC321x Southbridge", + .id_table = rdc321x_sb_table, + .probe = rdc321x_sb_probe, + .remove = rdc321x_sb_remove, +}; + +module_pci_driver(rdc321x_sb_driver); + +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RDC R-321x MFD southbridge driver"); diff --git a/kernel/drivers/mfd/retu-mfd.c b/kernel/drivers/mfd/retu-mfd.c new file mode 100644 index 000000000..2d64430c7 --- /dev/null +++ b/kernel/drivers/mfd/retu-mfd.c @@ -0,0 +1,327 @@ +/* + * Retu/Tahvo MFD driver + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Based on code written by Juha Yrjölä, David Weinehall and Mikko Ylinen. + * Rewritten by Aaro Koskinen. + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/mfd/core.h> +#include <linux/mfd/retu.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> + +/* Registers */ +#define RETU_REG_ASICR 0x00 /* ASIC ID and revision */ +#define RETU_REG_ASICR_VILMA (1 << 7) /* Bit indicating Vilma */ +#define RETU_REG_IDR 0x01 /* Interrupt ID */ +#define RETU_REG_IMR 0x02 /* Interrupt mask (Retu) */ +#define TAHVO_REG_IMR 0x03 /* Interrupt mask (Tahvo) */ + +/* Interrupt sources */ +#define RETU_INT_PWR 0 /* Power button */ + +struct retu_dev { + struct regmap *regmap; + struct device *dev; + struct mutex mutex; + struct regmap_irq_chip_data *irq_data; +}; + +static struct resource retu_pwrbutton_res[] = { + { + .name = "retu-pwrbutton", + .start = RETU_INT_PWR, + .end = RETU_INT_PWR, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell retu_devs[] = { + { + .name = "retu-wdt" + }, + { + .name = "retu-pwrbutton", + .resources = retu_pwrbutton_res, + .num_resources = ARRAY_SIZE(retu_pwrbutton_res), + } +}; + +static struct regmap_irq retu_irqs[] = { + [RETU_INT_PWR] = { + .mask = 1 << RETU_INT_PWR, + } +}; + +static struct regmap_irq_chip retu_irq_chip = { + .name = "RETU", + .irqs = retu_irqs, + .num_irqs = ARRAY_SIZE(retu_irqs), + .num_regs = 1, + .status_base = RETU_REG_IDR, + .mask_base = RETU_REG_IMR, + .ack_base = RETU_REG_IDR, +}; + +/* Retu device registered for the power off. */ +static struct retu_dev *retu_pm_power_off; + +static struct resource tahvo_usb_res[] = { + { + .name = "tahvo-usb", + .start = TAHVO_INT_VBUS, + .end = TAHVO_INT_VBUS, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell tahvo_devs[] = { + { + .name = "tahvo-usb", + .resources = tahvo_usb_res, + .num_resources = ARRAY_SIZE(tahvo_usb_res), + }, +}; + +static struct regmap_irq tahvo_irqs[] = { + [TAHVO_INT_VBUS] = { + .mask = 1 << TAHVO_INT_VBUS, + } +}; + +static struct regmap_irq_chip tahvo_irq_chip = { + .name = "TAHVO", + .irqs = tahvo_irqs, + .num_irqs = ARRAY_SIZE(tahvo_irqs), + .num_regs = 1, + .status_base = RETU_REG_IDR, + .mask_base = TAHVO_REG_IMR, + .ack_base = RETU_REG_IDR, +}; + +static const struct retu_data { + char *chip_name; + char *companion_name; + struct regmap_irq_chip *irq_chip; + const struct mfd_cell *children; + int nchildren; +} retu_data[] = { + [0] = { + .chip_name = "Retu", + .companion_name = "Vilma", + .irq_chip = &retu_irq_chip, + .children = retu_devs, + .nchildren = ARRAY_SIZE(retu_devs), + }, + [1] = { + .chip_name = "Tahvo", + .companion_name = "Betty", + .irq_chip = &tahvo_irq_chip, + .children = tahvo_devs, + .nchildren = ARRAY_SIZE(tahvo_devs), + } +}; + +int retu_read(struct retu_dev *rdev, u8 reg) +{ + int ret; + int value; + + mutex_lock(&rdev->mutex); + ret = regmap_read(rdev->regmap, reg, &value); + mutex_unlock(&rdev->mutex); + + return ret ? ret : value; +} +EXPORT_SYMBOL_GPL(retu_read); + +int retu_write(struct retu_dev *rdev, u8 reg, u16 data) +{ + int ret; + + mutex_lock(&rdev->mutex); + ret = regmap_write(rdev->regmap, reg, data); + mutex_unlock(&rdev->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(retu_write); + +static void retu_power_off(void) +{ + struct retu_dev *rdev = retu_pm_power_off; + int reg; + + mutex_lock(&retu_pm_power_off->mutex); + + /* Ignore power button state */ + regmap_read(rdev->regmap, RETU_REG_CC1, ®); + regmap_write(rdev->regmap, RETU_REG_CC1, reg | 2); + + /* Expire watchdog immediately */ + regmap_write(rdev->regmap, RETU_REG_WATCHDOG, 0); + + /* Wait for poweroff */ + for (;;) + cpu_relax(); + + mutex_unlock(&retu_pm_power_off->mutex); +} + +static int retu_regmap_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + int ret; + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + + BUG_ON(reg_size != 1 || val_size != 2); + + ret = i2c_smbus_read_word_data(i2c, *(u8 const *)reg); + if (ret < 0) + return ret; + + *(u16 *)val = ret; + return 0; +} + +static int retu_regmap_write(void *context, const void *data, size_t count) +{ + u8 reg; + u16 val; + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + + BUG_ON(count != sizeof(reg) + sizeof(val)); + memcpy(®, data, sizeof(reg)); + memcpy(&val, data + sizeof(reg), sizeof(val)); + return i2c_smbus_write_word_data(i2c, reg, val); +} + +static struct regmap_bus retu_bus = { + .read = retu_regmap_read, + .write = retu_regmap_write, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static const struct regmap_config retu_config = { + .reg_bits = 8, + .val_bits = 16, +}; + +static int retu_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct retu_data const *rdat; + struct retu_dev *rdev; + int ret; + + if (i2c->addr > ARRAY_SIZE(retu_data)) + return -ENODEV; + rdat = &retu_data[i2c->addr - 1]; + + rdev = devm_kzalloc(&i2c->dev, sizeof(*rdev), GFP_KERNEL); + if (rdev == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rdev); + rdev->dev = &i2c->dev; + mutex_init(&rdev->mutex); + rdev->regmap = devm_regmap_init(&i2c->dev, &retu_bus, &i2c->dev, + &retu_config); + if (IS_ERR(rdev->regmap)) + return PTR_ERR(rdev->regmap); + + ret = retu_read(rdev, RETU_REG_ASICR); + if (ret < 0) { + dev_err(rdev->dev, "could not read %s revision: %d\n", + rdat->chip_name, ret); + return ret; + } + + dev_info(rdev->dev, "%s%s%s v%d.%d found\n", rdat->chip_name, + (ret & RETU_REG_ASICR_VILMA) ? " & " : "", + (ret & RETU_REG_ASICR_VILMA) ? rdat->companion_name : "", + (ret >> 4) & 0x7, ret & 0xf); + + /* Mask all interrupts. */ + ret = retu_write(rdev, rdat->irq_chip->mask_base, 0xffff); + if (ret < 0) + return ret; + + ret = regmap_add_irq_chip(rdev->regmap, i2c->irq, IRQF_ONESHOT, -1, + rdat->irq_chip, &rdev->irq_data); + if (ret < 0) + return ret; + + ret = mfd_add_devices(rdev->dev, -1, rdat->children, rdat->nchildren, + NULL, regmap_irq_chip_get_base(rdev->irq_data), + NULL); + if (ret < 0) { + regmap_del_irq_chip(i2c->irq, rdev->irq_data); + return ret; + } + + if (i2c->addr == 1 && !pm_power_off) { + retu_pm_power_off = rdev; + pm_power_off = retu_power_off; + } + + return 0; +} + +static int retu_remove(struct i2c_client *i2c) +{ + struct retu_dev *rdev = i2c_get_clientdata(i2c); + + if (retu_pm_power_off == rdev) { + pm_power_off = NULL; + retu_pm_power_off = NULL; + } + mfd_remove_devices(rdev->dev); + regmap_del_irq_chip(i2c->irq, rdev->irq_data); + + return 0; +} + +static const struct i2c_device_id retu_id[] = { + { "retu-mfd", 0 }, + { "tahvo-mfd", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, retu_id); + +static struct i2c_driver retu_driver = { + .driver = { + .name = "retu-mfd", + .owner = THIS_MODULE, + }, + .probe = retu_probe, + .remove = retu_remove, + .id_table = retu_id, +}; +module_i2c_driver(retu_driver); + +MODULE_DESCRIPTION("Retu MFD driver"); +MODULE_AUTHOR("Juha Yrjölä"); +MODULE_AUTHOR("David Weinehall"); +MODULE_AUTHOR("Mikko Ylinen"); +MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/rk808.c b/kernel/drivers/mfd/rk808.c new file mode 100644 index 000000000..4b1e43997 --- /dev/null +++ b/kernel/drivers/mfd/rk808.c @@ -0,0 +1,276 @@ +/* + * MFD core driver for Rockchip RK808 + * + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * + * Author: Chris Zhong <zyw@rock-chips.com> + * Author: Zhang Qing <zhangqing@rock-chips.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/rk808.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/regmap.h> + +struct rk808_reg_data { + int addr; + int mask; + int value; +}; + +static bool rk808_is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* + * Notes: + * - Technically the ROUND_30s bit makes RTC_CTRL_REG volatile, but + * we don't use that feature. It's better to cache. + * - It's unlikely we care that RK808_DEVCTRL_REG is volatile since + * bits are cleared in case when we shutoff anyway, but better safe. + */ + + switch (reg) { + case RK808_SECONDS_REG ... RK808_WEEKS_REG: + case RK808_RTC_STATUS_REG: + case RK808_VB_MON_REG: + case RK808_THERMAL_REG: + case RK808_DCDC_UV_STS_REG: + case RK808_LDO_UV_STS_REG: + case RK808_DCDC_PG_REG: + case RK808_LDO_PG_REG: + case RK808_DEVCTRL_REG: + case RK808_INT_STS_REG1: + case RK808_INT_STS_REG2: + return true; + } + + return false; +} + +static const struct regmap_config rk808_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RK808_IO_POL_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = rk808_is_volatile_reg, +}; + +static struct resource rtc_resources[] = { + { + .start = RK808_IRQ_RTC_ALARM, + .end = RK808_IRQ_RTC_ALARM, + .flags = IORESOURCE_IRQ, + } +}; + +static const struct mfd_cell rk808s[] = { + { .name = "rk808-clkout", }, + { .name = "rk808-regulator", }, + { + .name = "rk808-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), + .resources = &rtc_resources[0], + }, +}; + +static const struct rk808_reg_data pre_init_reg[] = { + { RK808_BUCK3_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_150MA }, + { RK808_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_200MA }, + { RK808_BOOST_CONFIG_REG, BOOST_ILMIN_MASK, BOOST_ILMIN_100MA }, + { RK808_BUCK1_CONFIG_REG, BUCK1_RATE_MASK, BUCK_ILMIN_200MA }, + { RK808_BUCK2_CONFIG_REG, BUCK2_RATE_MASK, BUCK_ILMIN_200MA }, + { RK808_DCDC_UV_ACT_REG, BUCK_UV_ACT_MASK, BUCK_UV_ACT_DISABLE}, + { RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT | + VB_LO_SEL_3500MV }, +}; + +static const struct regmap_irq rk808_irqs[] = { + /* INT_STS */ + [RK808_IRQ_VOUT_LO] = { + .mask = RK808_IRQ_VOUT_LO_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_VB_LO] = { + .mask = RK808_IRQ_VB_LO_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_PWRON] = { + .mask = RK808_IRQ_PWRON_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_PWRON_LP] = { + .mask = RK808_IRQ_PWRON_LP_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_HOTDIE] = { + .mask = RK808_IRQ_HOTDIE_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_RTC_ALARM] = { + .mask = RK808_IRQ_RTC_ALARM_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_RTC_PERIOD] = { + .mask = RK808_IRQ_RTC_PERIOD_MSK, + .reg_offset = 0, + }, + + /* INT_STS2 */ + [RK808_IRQ_PLUG_IN_INT] = { + .mask = RK808_IRQ_PLUG_IN_INT_MSK, + .reg_offset = 1, + }, + [RK808_IRQ_PLUG_OUT_INT] = { + .mask = RK808_IRQ_PLUG_OUT_INT_MSK, + .reg_offset = 1, + }, +}; + +static struct regmap_irq_chip rk808_irq_chip = { + .name = "rk808", + .irqs = rk808_irqs, + .num_irqs = ARRAY_SIZE(rk808_irqs), + .num_regs = 2, + .irq_reg_stride = 2, + .status_base = RK808_INT_STS_REG1, + .mask_base = RK808_INT_STS_MSK_REG1, + .ack_base = RK808_INT_STS_REG1, + .init_ack_masked = true, +}; + +static struct i2c_client *rk808_i2c_client; +static void rk808_device_shutdown(void) +{ + int ret; + struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); + + if (!rk808) { + dev_warn(&rk808_i2c_client->dev, + "have no rk808, so do nothing here\n"); + return; + } + + ret = regmap_update_bits(rk808->regmap, + RK808_DEVCTRL_REG, + DEV_OFF_RST, DEV_OFF_RST); + if (ret) + dev_err(&rk808_i2c_client->dev, "power off error!\n"); +} + +static int rk808_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct rk808 *rk808; + int pm_off = 0; + int ret; + int i; + + if (!client->irq) { + dev_err(&client->dev, "No interrupt support, no core IRQ\n"); + return -EINVAL; + } + + rk808 = devm_kzalloc(&client->dev, sizeof(*rk808), GFP_KERNEL); + if (!rk808) + return -ENOMEM; + + rk808->regmap = devm_regmap_init_i2c(client, &rk808_regmap_config); + if (IS_ERR(rk808->regmap)) { + dev_err(&client->dev, "regmap initialization failed\n"); + return PTR_ERR(rk808->regmap); + } + + for (i = 0; i < ARRAY_SIZE(pre_init_reg); i++) { + ret = regmap_update_bits(rk808->regmap, pre_init_reg[i].addr, + pre_init_reg[i].mask, + pre_init_reg[i].value); + if (ret) { + dev_err(&client->dev, + "0x%x write err\n", pre_init_reg[i].addr); + return ret; + } + } + + ret = regmap_add_irq_chip(rk808->regmap, client->irq, + IRQF_ONESHOT, -1, + &rk808_irq_chip, &rk808->irq_data); + if (ret) { + dev_err(&client->dev, "Failed to add irq_chip %d\n", ret); + return ret; + } + + rk808->i2c = client; + i2c_set_clientdata(client, rk808); + + ret = mfd_add_devices(&client->dev, -1, + rk808s, ARRAY_SIZE(rk808s), + NULL, 0, regmap_irq_get_domain(rk808->irq_data)); + if (ret) { + dev_err(&client->dev, "failed to add MFD devices %d\n", ret); + goto err_irq; + } + + pm_off = of_property_read_bool(np, + "rockchip,system-power-controller"); + if (pm_off && !pm_power_off) { + rk808_i2c_client = client; + pm_power_off = rk808_device_shutdown; + } + + return 0; + +err_irq: + regmap_del_irq_chip(client->irq, rk808->irq_data); + return ret; +} + +static int rk808_remove(struct i2c_client *client) +{ + struct rk808 *rk808 = i2c_get_clientdata(client); + + regmap_del_irq_chip(client->irq, rk808->irq_data); + mfd_remove_devices(&client->dev); + pm_power_off = NULL; + + return 0; +} + +static const struct of_device_id rk808_of_match[] = { + { .compatible = "rockchip,rk808" }, + { }, +}; +MODULE_DEVICE_TABLE(of, rk808_of_match); + +static const struct i2c_device_id rk808_ids[] = { + { "rk808" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, rk808_ids); + +static struct i2c_driver rk808_i2c_driver = { + .driver = { + .name = "rk808", + .of_match_table = rk808_of_match, + }, + .probe = rk808_probe, + .remove = rk808_remove, + .id_table = rk808_ids, +}; + +module_i2c_driver(rk808_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>"); +MODULE_DESCRIPTION("RK808 PMIC driver"); diff --git a/kernel/drivers/mfd/rn5t618.c b/kernel/drivers/mfd/rn5t618.c new file mode 100644 index 000000000..666857192 --- /dev/null +++ b/kernel/drivers/mfd/rn5t618.c @@ -0,0 +1,134 @@ +/* + * MFD core driver for Ricoh RN5T618 PMIC + * + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rn5t618.h> +#include <linux/module.h> +#include <linux/regmap.h> + +static const struct mfd_cell rn5t618_cells[] = { + { .name = "rn5t618-regulator" }, + { .name = "rn5t618-wdt" }, +}; + +static bool rn5t618_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RN5T618_WATCHDOGCNT: + case RN5T618_DCIRQ: + case RN5T618_ILIMDATAH ... RN5T618_AIN0DATAL: + case RN5T618_IR_ADC1 ... RN5T618_IR_ADC3: + case RN5T618_IR_GPR: + case RN5T618_IR_GPF: + case RN5T618_MON_IOIN: + case RN5T618_INTMON: + return true; + default: + return false; + } +} + +static const struct regmap_config rn5t618_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = rn5t618_volatile_reg, + .max_register = RN5T618_MAX_REG, + .cache_type = REGCACHE_RBTREE, +}; + +static struct rn5t618 *rn5t618_pm_power_off; + +static void rn5t618_power_off(void) +{ + /* disable automatic repower-on */ + regmap_update_bits(rn5t618_pm_power_off->regmap, RN5T618_REPCNT, + RN5T618_REPCNT_REPWRON, 0); + /* start power-off sequence */ + regmap_update_bits(rn5t618_pm_power_off->regmap, RN5T618_SLPCNT, + RN5T618_SLPCNT_SWPWROFF, RN5T618_SLPCNT_SWPWROFF); +} + +static int rn5t618_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rn5t618 *priv; + int ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(i2c, priv); + + priv->regmap = devm_regmap_init_i2c(i2c, &rn5t618_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&i2c->dev, "regmap init failed: %d\n", ret); + return ret; + } + + ret = mfd_add_devices(&i2c->dev, -1, rn5t618_cells, + ARRAY_SIZE(rn5t618_cells), NULL, 0, NULL); + if (ret) { + dev_err(&i2c->dev, "failed to add sub-devices: %d\n", ret); + return ret; + } + + if (!pm_power_off) { + rn5t618_pm_power_off = priv; + pm_power_off = rn5t618_power_off; + } + + return 0; +} + +static int rn5t618_i2c_remove(struct i2c_client *i2c) +{ + struct rn5t618 *priv = i2c_get_clientdata(i2c); + + if (priv == rn5t618_pm_power_off) { + rn5t618_pm_power_off = NULL; + pm_power_off = NULL; + } + + mfd_remove_devices(&i2c->dev); + return 0; +} + +static const struct of_device_id rn5t618_of_match[] = { + { .compatible = "ricoh,rn5t618" }, + { } +}; +MODULE_DEVICE_TABLE(of, rn5t618_of_match); + +static const struct i2c_device_id rn5t618_i2c_id[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, rn5t618_i2c_id); + +static struct i2c_driver rn5t618_i2c_driver = { + .driver = { + .name = "rn5t618", + .of_match_table = of_match_ptr(rn5t618_of_match), + }, + .probe = rn5t618_i2c_probe, + .remove = rn5t618_i2c_remove, + .id_table = rn5t618_i2c_id, +}; + +module_i2c_driver(rn5t618_i2c_driver); + +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); +MODULE_DESCRIPTION("Ricoh RN5T618 MFD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/rt5033.c b/kernel/drivers/mfd/rt5033.c new file mode 100644 index 000000000..db395a6c5 --- /dev/null +++ b/kernel/drivers/mfd/rt5033.c @@ -0,0 +1,142 @@ +/* + * MFD core driver for the Richtek RT5033. + * + * RT5033 comprises multiple sub-devices switcing charger, fuel gauge, + * flash LED, current source, LDO and BUCK regulators. + * + * Copyright (C) 2014 Samsung Electronics, Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.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 bythe Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/of_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rt5033.h> +#include <linux/mfd/rt5033-private.h> + +static const struct regmap_irq rt5033_irqs[] = { + { .mask = RT5033_PMIC_IRQ_BUCKOCP, }, + { .mask = RT5033_PMIC_IRQ_BUCKLV, }, + { .mask = RT5033_PMIC_IRQ_SAFELDOLV, }, + { .mask = RT5033_PMIC_IRQ_LDOLV, }, + { .mask = RT5033_PMIC_IRQ_OT, }, + { .mask = RT5033_PMIC_IRQ_VDDA_UV, }, +}; + +static const struct regmap_irq_chip rt5033_irq_chip = { + .name = "rt5033", + .status_base = RT5033_REG_PMIC_IRQ_STAT, + .mask_base = RT5033_REG_PMIC_IRQ_CTRL, + .mask_invert = true, + .num_regs = 1, + .irqs = rt5033_irqs, + .num_irqs = ARRAY_SIZE(rt5033_irqs), +}; + +static const struct mfd_cell rt5033_devs[] = { + { .name = "rt5033-regulator", }, + { + .name = "rt5033-charger", + .of_compatible = "richtek,rt5033-charger", + }, { + .name = "rt5033-battery", + .of_compatible = "richtek,rt5033-battery", + }, +}; + +static const struct regmap_config rt5033_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RT5033_REG_END, +}; + +static int rt5033_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5033_dev *rt5033; + unsigned int dev_id; + int ret; + + rt5033 = devm_kzalloc(&i2c->dev, sizeof(*rt5033), GFP_KERNEL); + if (!rt5033) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5033); + rt5033->dev = &i2c->dev; + rt5033->irq = i2c->irq; + rt5033->wakeup = true; + + rt5033->regmap = devm_regmap_init_i2c(i2c, &rt5033_regmap_config); + if (IS_ERR(rt5033->regmap)) { + dev_err(&i2c->dev, "Failed to allocate register map.\n"); + return PTR_ERR(rt5033->regmap); + } + + ret = regmap_read(rt5033->regmap, RT5033_REG_DEVICE_ID, &dev_id); + if (ret) { + dev_err(&i2c->dev, "Device not found\n"); + return -ENODEV; + } + dev_info(&i2c->dev, "Device found Device ID: %04x\n", dev_id); + + ret = regmap_add_irq_chip(rt5033->regmap, rt5033->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + 0, &rt5033_irq_chip, &rt5033->irq_data); + if (ret) { + dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", + rt5033->irq, ret); + return ret; + } + + ret = mfd_add_devices(rt5033->dev, -1, rt5033_devs, + ARRAY_SIZE(rt5033_devs), NULL, 0, + regmap_irq_get_domain(rt5033->irq_data)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to add RT5033 child devices.\n"); + return ret; + } + + device_init_wakeup(rt5033->dev, rt5033->wakeup); + + return 0; +} + +static int rt5033_i2c_remove(struct i2c_client *i2c) +{ + mfd_remove_devices(&i2c->dev); + + return 0; +} + +static const struct i2c_device_id rt5033_i2c_id[] = { + { "rt5033", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5033_i2c_id); + +static const struct of_device_id rt5033_dt_match[] = { + { .compatible = "richtek,rt5033", }, + { } +}; + +static struct i2c_driver rt5033_driver = { + .driver = { + .name = "rt5033", + .of_match_table = of_match_ptr(rt5033_dt_match), + }, + .probe = rt5033_i2c_probe, + .remove = rt5033_i2c_remove, + .id_table = rt5033_i2c_id, +}; +module_i2c_driver(rt5033_driver); + +MODULE_ALIAS("i2c:rt5033"); +MODULE_DESCRIPTION("Richtek RT5033 multi-function core driver"); +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/rtl8411.c b/kernel/drivers/mfd/rtl8411.c new file mode 100644 index 000000000..b3ae65920 --- /dev/null +++ b/kernel/drivers/mfd/rtl8411.c @@ -0,0 +1,508 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * Roger Tseng <rogerable@realtek.com> + */ + +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rtl8411_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + rtsx_pci_read_register(pcr, SYS_VER, &val); + return val & 0x0F; +} + +static int rtl8411b_is_qfn48(struct rtsx_pcr *pcr) +{ + u8 val = 0; + + rtsx_pci_read_register(pcr, RTL8411B_PACKAGE_MODE, &val); + + if (val & 0x2) + return 1; + else + return 0; +} + +static void rtl8411_fetch_vendor_settings(struct rtsx_pcr *pcr) +{ + u32 reg1 = 0; + u8 reg3 = 0; + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, ®1); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg1); + + if (!rtsx_vendor_setting_valid(reg1)) + return; + + pcr->aspm_en = rtsx_reg_to_aspm(reg1); + pcr->sd30_drive_sel_1v8 = + map_sd_drive(rtsx_reg_to_sd30_drive_sel_1v8(reg1)); + pcr->card_drive_sel &= 0x3F; + pcr->card_drive_sel |= rtsx_reg_to_card_drive_sel(reg1); + + rtsx_pci_read_config_byte(pcr, PCR_SETTING_REG3, ®3); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG3, reg3); + pcr->sd30_drive_sel_3v3 = rtl8411_reg_to_sd30_drive_sel_3v3(reg3); +} + +static void rtl8411b_fetch_vendor_settings(struct rtsx_pcr *pcr) +{ + u32 reg = 0; + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + + if (!rtsx_vendor_setting_valid(reg)) + return; + + pcr->aspm_en = rtsx_reg_to_aspm(reg); + pcr->sd30_drive_sel_1v8 = + map_sd_drive(rtsx_reg_to_sd30_drive_sel_1v8(reg)); + pcr->sd30_drive_sel_3v3 = + map_sd_drive(rtl8411b_reg_to_sd30_drive_sel_3v3(reg)); +} + +static void rtl8411_force_power_down(struct rtsx_pcr *pcr, u8 pm_state) +{ + rtsx_pci_write_register(pcr, FPDCTL, 0x07, 0x07); +} + +static int rtl8411_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DRIVE_SEL, + 0xFF, pcr->sd30_drive_sel_3v3); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CD_PAD_CTL, + CD_DISABLE_MASK | CD_AUTO_DISABLE, CD_ENABLE); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rtl8411b_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + if (rtl8411b_is_qfn48(pcr)) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + CARD_PULL_CTL3, 0xFF, 0xF5); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DRIVE_SEL, + 0xFF, pcr->sd30_drive_sel_3v3); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CD_PAD_CTL, + CD_DISABLE_MASK | CD_AUTO_DISABLE, CD_ENABLE); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, FUNC_FORCE_CTL, + 0x06, 0x00); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rtl8411_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x00); +} + +static int rtl8411_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x01); +} + +static int rtl8411_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0xFF, 0x0D); +} + +static int rtl8411_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0x08, 0x00); +} + +static int rtl8411_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_5_PERCENT_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_CTL, + BPP_LDO_POWB, BPP_LDO_SUSPEND); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_10_PERCENT_ON); + if (err < 0) + return err; + + udelay(150); + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_15_PERCENT_ON); + if (err < 0) + return err; + + udelay(150); + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_ON); + if (err < 0) + return err; + + return rtsx_pci_write_register(pcr, LDO_CTL, BPP_LDO_POWB, BPP_LDO_ON); +} + +static int rtl8411_card_power_off(struct rtsx_pcr *pcr, int card) +{ + int err; + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_OFF); + if (err < 0) + return err; + + return rtsx_pci_write_register(pcr, LDO_CTL, + BPP_LDO_POWB, BPP_LDO_SUSPEND); +} + +static int rtl8411_do_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage, + int bpp_tuned18_shift, int bpp_asic_1v8) +{ + u8 mask, val; + int err; + + mask = (BPP_REG_TUNED18 << bpp_tuned18_shift) | BPP_PAD_MASK; + if (voltage == OUTPUT_3V3) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_3v3); + if (err < 0) + return err; + val = (BPP_ASIC_3V3 << bpp_tuned18_shift) | BPP_PAD_3V3; + } else if (voltage == OUTPUT_1V8) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_1v8); + if (err < 0) + return err; + val = (bpp_asic_1v8 << bpp_tuned18_shift) | BPP_PAD_1V8; + } else { + return -EINVAL; + } + + return rtsx_pci_write_register(pcr, LDO_CTL, mask, val); +} + +static int rtl8411_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + return rtl8411_do_switch_output_voltage(pcr, voltage, + BPP_TUNED18_SHIFT_8411, BPP_ASIC_1V8); +} + +static int rtl8402_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + return rtl8411_do_switch_output_voltage(pcr, voltage, + BPP_TUNED18_SHIFT_8402, BPP_ASIC_2V0); +} + +static unsigned int rtl8411_cd_deglitch(struct rtsx_pcr *pcr) +{ + unsigned int card_exist; + + card_exist = rtsx_pci_readl(pcr, RTSX_BIPR); + card_exist &= CARD_EXIST; + if (!card_exist) { + /* Enable card CD */ + rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK, CD_ENABLE); + /* Enable card interrupt */ + rtsx_pci_write_register(pcr, EFUSE_CONTENT, 0xe0, 0x00); + return 0; + } + + if (hweight32(card_exist) > 1) { + rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_5_PERCENT_ON); + msleep(100); + + card_exist = rtsx_pci_readl(pcr, RTSX_BIPR); + if (card_exist & MS_EXIST) + card_exist = MS_EXIST; + else if (card_exist & SD_EXIST) + card_exist = SD_EXIST; + else + card_exist = 0; + + rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_OFF); + + pcr_dbg(pcr, "After CD deglitch, card_exist = 0x%x\n", + card_exist); + } + + if (card_exist & MS_EXIST) { + /* Disable SD interrupt */ + rtsx_pci_write_register(pcr, EFUSE_CONTENT, 0xe0, 0x40); + rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK, MS_CD_EN_ONLY); + } else if (card_exist & SD_EXIST) { + /* Disable MS interrupt */ + rtsx_pci_write_register(pcr, EFUSE_CONTENT, 0xe0, 0x80); + rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK, SD_CD_EN_ONLY); + } + + return card_exist; +} + +static int rtl8411_conv_clk_and_div_n(int input, int dir) +{ + int output; + + if (dir == CLK_TO_DIV_N) + output = input * 4 / 5 - 2; + else + output = (input + 2) * 5 / 4; + + return output; +} + +static const struct pcr_ops rtl8411_pcr_ops = { + .fetch_vendor_settings = rtl8411_fetch_vendor_settings, + .extra_init_hw = rtl8411_extra_init_hw, + .optimize_phy = NULL, + .turn_on_led = rtl8411_turn_on_led, + .turn_off_led = rtl8411_turn_off_led, + .enable_auto_blink = rtl8411_enable_auto_blink, + .disable_auto_blink = rtl8411_disable_auto_blink, + .card_power_on = rtl8411_card_power_on, + .card_power_off = rtl8411_card_power_off, + .switch_output_voltage = rtl8411_switch_output_voltage, + .cd_deglitch = rtl8411_cd_deglitch, + .conv_clk_and_div_n = rtl8411_conv_clk_and_div_n, + .force_power_down = rtl8411_force_power_down, +}; + +static const struct pcr_ops rtl8402_pcr_ops = { + .fetch_vendor_settings = rtl8411_fetch_vendor_settings, + .extra_init_hw = rtl8411_extra_init_hw, + .optimize_phy = NULL, + .turn_on_led = rtl8411_turn_on_led, + .turn_off_led = rtl8411_turn_off_led, + .enable_auto_blink = rtl8411_enable_auto_blink, + .disable_auto_blink = rtl8411_disable_auto_blink, + .card_power_on = rtl8411_card_power_on, + .card_power_off = rtl8411_card_power_off, + .switch_output_voltage = rtl8402_switch_output_voltage, + .cd_deglitch = rtl8411_cd_deglitch, + .conv_clk_and_div_n = rtl8411_conv_clk_and_div_n, + .force_power_down = rtl8411_force_power_down, +}; + +static const struct pcr_ops rtl8411b_pcr_ops = { + .fetch_vendor_settings = rtl8411b_fetch_vendor_settings, + .extra_init_hw = rtl8411b_extra_init_hw, + .optimize_phy = NULL, + .turn_on_led = rtl8411_turn_on_led, + .turn_off_led = rtl8411_turn_off_led, + .enable_auto_blink = rtl8411_enable_auto_blink, + .disable_auto_blink = rtl8411_disable_auto_blink, + .card_power_on = rtl8411_card_power_on, + .card_power_off = rtl8411_card_power_off, + .switch_output_voltage = rtl8411_switch_output_voltage, + .cd_deglitch = rtl8411_cd_deglitch, + .conv_clk_and_div_n = rtl8411_conv_clk_and_div_n, + .force_power_down = rtl8411_force_power_down, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rtl8411_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xA9), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rtl8411_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x95), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rtl8411_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x95), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rtl8411_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x95), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +static const u32 rtl8411b_qfn64_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x09 | 0xD0), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn48_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x69 | 0x90), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x08 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn64_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x05 | 0xD0), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn48_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x65 | 0x90), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn64_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x05 | 0xD0), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x05 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn48_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x65 | 0x90), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn64_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x05 | 0xD0), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05 | 0x50), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static const u32 rtl8411b_qfn48_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x65 | 0x90), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04 | 0x11), + 0, +}; + +static void rtl8411_init_common_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->flags = 0; + pcr->card_drive_sel = RTL8411_CARD_DRIVE_DEFAULT; + pcr->sd30_drive_sel_1v8 = DRIVER_TYPE_B; + pcr->sd30_drive_sel_3v3 = DRIVER_TYPE_D; + pcr->aspm_en = ASPM_L1_EN; + pcr->tx_initial_phase = SET_CLOCK_PHASE(23, 7, 14); + pcr->rx_initial_phase = SET_CLOCK_PHASE(4, 3, 10); + pcr->ic_version = rtl8411_get_ic_version(pcr); +} + +void rtl8411_init_params(struct rtsx_pcr *pcr) +{ + rtl8411_init_common_params(pcr); + pcr->ops = &rtl8411_pcr_ops; + set_pull_ctrl_tables(pcr, rtl8411); +} + +void rtl8411b_init_params(struct rtsx_pcr *pcr) +{ + rtl8411_init_common_params(pcr); + pcr->ops = &rtl8411b_pcr_ops; + if (rtl8411b_is_qfn48(pcr)) + set_pull_ctrl_tables(pcr, rtl8411b_qfn48); + else + set_pull_ctrl_tables(pcr, rtl8411b_qfn64); +} + +void rtl8402_init_params(struct rtsx_pcr *pcr) +{ + rtl8411_init_common_params(pcr); + pcr->ops = &rtl8402_pcr_ops; + set_pull_ctrl_tables(pcr, rtl8411); +} diff --git a/kernel/drivers/mfd/rts5209.c b/kernel/drivers/mfd/rts5209.c new file mode 100644 index 000000000..373e253c3 --- /dev/null +++ b/kernel/drivers/mfd/rts5209.c @@ -0,0 +1,281 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rts5209_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + val = rtsx_pci_readb(pcr, 0x1C); + return val & 0x0F; +} + +static void rts5209_fetch_vendor_settings(struct rtsx_pcr *pcr) +{ + u32 reg; + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + + if (rts5209_vendor_setting1_valid(reg)) { + if (rts5209_reg_check_ms_pmos(reg)) + pcr->flags |= PCR_MS_PMOS; + pcr->aspm_en = rts5209_reg_to_aspm(reg); + } + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG2, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG2, reg); + + if (rts5209_vendor_setting2_valid(reg)) { + pcr->sd30_drive_sel_1v8 = + rts5209_reg_to_sd30_drive_sel_1v8(reg); + pcr->sd30_drive_sel_3v3 = + rts5209_reg_to_sd30_drive_sel_3v3(reg); + pcr->card_drive_sel = rts5209_reg_to_card_drive_sel(reg); + } +} + +static void rts5209_force_power_down(struct rtsx_pcr *pcr, u8 pm_state) +{ + rtsx_pci_write_register(pcr, FPDCTL, 0x07, 0x07); +} + +static int rts5209_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + /* Turn off LED */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_GPIO, 0xFF, 0x03); + /* Reset ASPM state to default value */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 0); + /* Force CLKREQ# PIN to drive 0 to request clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x08, 0x08); + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_GPIO_DIR, 0xFF, 0x03); + /* Configure driving */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DRIVE_SEL, + 0xFF, pcr->sd30_drive_sel_3v3); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5209_optimize_phy(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_phy_register(pcr, 0x00, 0xB966); +} + +static int rts5209_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x00); +} + +static int rts5209_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x01); +} + +static int rts5209_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0xFF, 0x0D); +} + +static int rts5209_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0x08, 0x00); +} + +static int rts5209_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + u8 pwr_mask, partial_pwr_on, pwr_on; + + pwr_mask = SD_POWER_MASK; + partial_pwr_on = SD_PARTIAL_POWER_ON; + pwr_on = SD_POWER_ON; + + if ((pcr->flags & PCR_MS_PMOS) && (card == RTSX_MS_CARD)) { + pwr_mask = MS_POWER_MASK; + partial_pwr_on = MS_PARTIAL_POWER_ON; + pwr_on = MS_POWER_ON; + } + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + pwr_mask, partial_pwr_on); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x04); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, pwr_mask, pwr_on); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x00); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rts5209_card_power_off(struct rtsx_pcr *pcr, int card) +{ + u8 pwr_mask, pwr_off; + + pwr_mask = SD_POWER_MASK; + pwr_off = SD_POWER_OFF; + + if ((pcr->flags & PCR_MS_PMOS) && (card == RTSX_MS_CARD)) { + pwr_mask = MS_POWER_MASK; + pwr_off = MS_POWER_OFF; + } + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + pwr_mask | PMOS_STRG_MASK, pwr_off | PMOS_STRG_400mA); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x06); + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5209_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + int err; + + if (voltage == OUTPUT_3V3) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_3v3); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24); + if (err < 0) + return err; + } else if (voltage == OUTPUT_1V8) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_1v8); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C40 | 0x24); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + return 0; +} + +static const struct pcr_ops rts5209_pcr_ops = { + .fetch_vendor_settings = rts5209_fetch_vendor_settings, + .extra_init_hw = rts5209_extra_init_hw, + .optimize_phy = rts5209_optimize_phy, + .turn_on_led = rts5209_turn_on_led, + .turn_off_led = rts5209_turn_off_led, + .enable_auto_blink = rts5209_enable_auto_blink, + .disable_auto_blink = rts5209_disable_auto_blink, + .card_power_on = rts5209_card_power_on, + .card_power_off = rts5209_card_power_off, + .switch_output_voltage = rts5209_switch_output_voltage, + .cd_deglitch = NULL, + .conv_clk_and_div_n = NULL, + .force_power_down = rts5209_force_power_down, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5209_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5209_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5209_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5209_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5209_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | + EXTRA_CAPS_SD_SDR104 | EXTRA_CAPS_MMC_8BIT; + pcr->num_slots = 2; + pcr->ops = &rts5209_pcr_ops; + + pcr->flags = 0; + pcr->card_drive_sel = RTS5209_CARD_DRIVE_DEFAULT; + pcr->sd30_drive_sel_1v8 = DRIVER_TYPE_B; + pcr->sd30_drive_sel_3v3 = DRIVER_TYPE_D; + pcr->aspm_en = ASPM_L1_EN; + pcr->tx_initial_phase = SET_CLOCK_PHASE(27, 27, 16); + pcr->rx_initial_phase = SET_CLOCK_PHASE(24, 6, 5); + + pcr->ic_version = rts5209_get_ic_version(pcr); + pcr->sd_pull_ctl_enable_tbl = rts5209_sd_pull_ctl_enable_tbl; + pcr->sd_pull_ctl_disable_tbl = rts5209_sd_pull_ctl_disable_tbl; + pcr->ms_pull_ctl_enable_tbl = rts5209_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5209_ms_pull_ctl_disable_tbl; +} diff --git a/kernel/drivers/mfd/rts5227.c b/kernel/drivers/mfd/rts5227.c new file mode 100644 index 000000000..ce012d78c --- /dev/null +++ b/kernel/drivers/mfd/rts5227.c @@ -0,0 +1,305 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * Roger Tseng <rogerable@realtek.com> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static void rts5227_fill_driving(struct rtsx_pcr *pcr, u8 voltage) +{ + u8 driving_3v3[4][3] = { + {0x13, 0x13, 0x13}, + {0x96, 0x96, 0x96}, + {0x7F, 0x7F, 0x7F}, + {0x96, 0x96, 0x96}, + }; + u8 driving_1v8[4][3] = { + {0x99, 0x99, 0x99}, + {0xAA, 0xAA, 0xAA}, + {0xFE, 0xFE, 0xFE}, + {0xB3, 0xB3, 0xB3}, + }; + u8 (*driving)[3], drive_sel; + + if (voltage == OUTPUT_3V3) { + driving = driving_3v3; + drive_sel = pcr->sd30_drive_sel_3v3; + } else { + driving = driving_1v8; + drive_sel = pcr->sd30_drive_sel_1v8; + } + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CLK_DRIVE_SEL, + 0xFF, driving[drive_sel][0]); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CMD_DRIVE_SEL, + 0xFF, driving[drive_sel][1]); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DAT_DRIVE_SEL, + 0xFF, driving[drive_sel][2]); +} + +static void rts5227_fetch_vendor_settings(struct rtsx_pcr *pcr) +{ + u32 reg; + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + + if (!rtsx_vendor_setting_valid(reg)) + return; + + pcr->aspm_en = rtsx_reg_to_aspm(reg); + pcr->sd30_drive_sel_1v8 = rtsx_reg_to_sd30_drive_sel_1v8(reg); + pcr->card_drive_sel &= 0x3F; + pcr->card_drive_sel |= rtsx_reg_to_card_drive_sel(reg); + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG2, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG2, reg); + pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); + if (rtsx_reg_check_reverse_socket(reg)) + pcr->flags |= PCR_REVERSE_SOCKET; +} + +static void rts5227_force_power_down(struct rtsx_pcr *pcr, u8 pm_state) +{ + /* Set relink_time to 0 */ + rtsx_pci_write_register(pcr, AUTOLOAD_CFG_BASE + 1, 0xFF, 0); + rtsx_pci_write_register(pcr, AUTOLOAD_CFG_BASE + 2, 0xFF, 0); + rtsx_pci_write_register(pcr, AUTOLOAD_CFG_BASE + 3, 0x01, 0); + + if (pm_state == HOST_ENTER_S3) + rtsx_pci_write_register(pcr, PM_CTRL3, 0x10, 0x10); + + rtsx_pci_write_register(pcr, FPDCTL, 0x03, 0x03); +} + +static int rts5227_extra_init_hw(struct rtsx_pcr *pcr) +{ + u16 cap; + + rtsx_pci_init_cmd(pcr); + + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, GPIO_CTL, 0x02, 0x02); + /* Reset ASPM state to default value */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 0); + /* Switch LDO3318 source from DV33 to card_3v3 */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x00); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x01); + /* LED shine disabled, set initial shine cycle period */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, OLT_LED_CTL, 0x0F, 0x02); + /* Configure LTR */ + pcie_capability_read_word(pcr->pci, PCI_EXP_DEVCTL2, &cap); + if (cap & PCI_EXP_DEVCTL2_LTR_EN) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LTR_CTL, 0xFF, 0xA3); + /* Configure OBFF */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, OBFF_CFG, 0x03, 0x03); + /* Configure driving */ + rts5227_fill_driving(pcr, OUTPUT_3V3); + /* Configure force_clock_req */ + if (pcr->flags & PCR_REVERSE_SOCKET) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB8, 0xB8); + else + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB8, 0x88); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PM_CTRL3, 0x10, 0x00); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5227_optimize_phy(struct rtsx_pcr *pcr) +{ + int err; + + err = rtsx_pci_write_register(pcr, PM_CTRL3, D3_DELINK_MODE_EN, 0x00); + if (err < 0) + return err; + + /* Optimize RX sensitivity */ + return rtsx_pci_write_phy_register(pcr, 0x00, 0xBA42); +} + +static int rts5227_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x02); +} + +static int rts5227_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x00); +} + +static int rts5227_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x08); +} + +static int rts5227_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x00); +} + +static int rts5227_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_PARTIAL_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x02); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x06); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rts5227_card_power_off(struct rtsx_pcr *pcr, int card) +{ + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK | PMOS_STRG_MASK, + SD_POWER_OFF | PMOS_STRG_400mA); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0X00); + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5227_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + int err; + + if (voltage == OUTPUT_3V3) { + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24); + if (err < 0) + return err; + } else if (voltage == OUTPUT_1V8) { + err = rtsx_pci_write_phy_register(pcr, 0x11, 0x3C02); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C80 | 0x24); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + /* set pad drive */ + rtsx_pci_init_cmd(pcr); + rts5227_fill_driving(pcr, voltage); + return rtsx_pci_send_cmd(pcr, 100); +} + +static const struct pcr_ops rts5227_pcr_ops = { + .fetch_vendor_settings = rts5227_fetch_vendor_settings, + .extra_init_hw = rts5227_extra_init_hw, + .optimize_phy = rts5227_optimize_phy, + .turn_on_led = rts5227_turn_on_led, + .turn_off_led = rts5227_turn_off_led, + .enable_auto_blink = rts5227_enable_auto_blink, + .disable_auto_blink = rts5227_disable_auto_blink, + .card_power_on = rts5227_card_power_on, + .card_power_off = rts5227_card_power_off, + .switch_output_voltage = rts5227_switch_output_voltage, + .cd_deglitch = NULL, + .conv_clk_and_div_n = NULL, + .force_power_down = rts5227_force_power_down, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5227_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5227_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5227_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5227_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5227_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->ops = &rts5227_pcr_ops; + + pcr->flags = 0; + pcr->card_drive_sel = RTSX_CARD_DRIVE_DEFAULT; + pcr->sd30_drive_sel_1v8 = CFG_DRIVER_TYPE_B; + pcr->sd30_drive_sel_3v3 = CFG_DRIVER_TYPE_B; + pcr->aspm_en = ASPM_L1_EN; + pcr->tx_initial_phase = SET_CLOCK_PHASE(27, 27, 15); + pcr->rx_initial_phase = SET_CLOCK_PHASE(30, 7, 7); + + pcr->sd_pull_ctl_enable_tbl = rts5227_sd_pull_ctl_enable_tbl; + pcr->sd_pull_ctl_disable_tbl = rts5227_sd_pull_ctl_disable_tbl; + pcr->ms_pull_ctl_enable_tbl = rts5227_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5227_ms_pull_ctl_disable_tbl; +} diff --git a/kernel/drivers/mfd/rts5229.c b/kernel/drivers/mfd/rts5229.c new file mode 100644 index 000000000..ace45384e --- /dev/null +++ b/kernel/drivers/mfd/rts5229.c @@ -0,0 +1,277 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rts5229_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + rtsx_pci_read_register(pcr, DUMMY_REG_RESET_0, &val); + return val & 0x0F; +} + +static void rts5229_fetch_vendor_settings(struct rtsx_pcr *pcr) +{ + u32 reg; + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + + if (!rtsx_vendor_setting_valid(reg)) + return; + + pcr->aspm_en = rtsx_reg_to_aspm(reg); + pcr->sd30_drive_sel_1v8 = + map_sd_drive(rtsx_reg_to_sd30_drive_sel_1v8(reg)); + pcr->card_drive_sel &= 0x3F; + pcr->card_drive_sel |= rtsx_reg_to_card_drive_sel(reg); + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG2, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG2, reg); + pcr->sd30_drive_sel_3v3 = + map_sd_drive(rtsx_reg_to_sd30_drive_sel_3v3(reg)); +} + +static void rts5229_force_power_down(struct rtsx_pcr *pcr, u8 pm_state) +{ + rtsx_pci_write_register(pcr, FPDCTL, 0x03, 0x03); +} + +static int rts5229_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, GPIO_CTL, 0x02, 0x02); + /* Reset ASPM state to default value */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 0); + /* Force CLKREQ# PIN to drive 0 to request clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x08, 0x08); + /* Switch LDO3318 source from DV33 to card_3v3 */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x00); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x01); + /* LED shine disabled, set initial shine cycle period */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, OLT_LED_CTL, 0x0F, 0x02); + /* Configure driving */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DRIVE_SEL, + 0xFF, pcr->sd30_drive_sel_3v3); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5229_optimize_phy(struct rtsx_pcr *pcr) +{ + /* Optimize RX sensitivity */ + return rtsx_pci_write_phy_register(pcr, 0x00, 0xBA42); +} + +static int rts5229_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x02); +} + +static int rts5229_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x00); +} + +static int rts5229_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x08); +} + +static int rts5229_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x00); +} + +static int rts5229_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_PARTIAL_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x02); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x06); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rts5229_card_power_off(struct rtsx_pcr *pcr, int card) +{ + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK | PMOS_STRG_MASK, + SD_POWER_OFF | PMOS_STRG_400mA); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x00); + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5229_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + int err; + + if (voltage == OUTPUT_3V3) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_3v3); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24); + if (err < 0) + return err; + } else if (voltage == OUTPUT_1V8) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_1v8); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C40 | 0x24); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + return 0; +} + +static const struct pcr_ops rts5229_pcr_ops = { + .fetch_vendor_settings = rts5229_fetch_vendor_settings, + .extra_init_hw = rts5229_extra_init_hw, + .optimize_phy = rts5229_optimize_phy, + .turn_on_led = rts5229_turn_on_led, + .turn_off_led = rts5229_turn_off_led, + .enable_auto_blink = rts5229_enable_auto_blink, + .disable_auto_blink = rts5229_disable_auto_blink, + .card_power_on = rts5229_card_power_on, + .card_power_off = rts5229_card_power_off, + .switch_output_voltage = rts5229_switch_output_voltage, + .cd_deglitch = NULL, + .conv_clk_and_div_n = NULL, + .force_power_down = rts5229_force_power_down, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5229_sd_pull_ctl_enable_tbl1[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + 0, +}; + +/* For RTS5229 version C */ +static const u32 rts5229_sd_pull_ctl_enable_tbl2[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD9), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5229_sd_pull_ctl_disable_tbl1[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + 0, +}; + +/* For RTS5229 version C */ +static const u32 rts5229_sd_pull_ctl_disable_tbl2[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE5), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5229_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5229_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5229_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->ops = &rts5229_pcr_ops; + + pcr->flags = 0; + pcr->card_drive_sel = RTSX_CARD_DRIVE_DEFAULT; + pcr->sd30_drive_sel_1v8 = DRIVER_TYPE_B; + pcr->sd30_drive_sel_3v3 = DRIVER_TYPE_D; + pcr->aspm_en = ASPM_L1_EN; + pcr->tx_initial_phase = SET_CLOCK_PHASE(27, 27, 15); + pcr->rx_initial_phase = SET_CLOCK_PHASE(30, 6, 6); + + pcr->ic_version = rts5229_get_ic_version(pcr); + if (pcr->ic_version == IC_VER_C) { + pcr->sd_pull_ctl_enable_tbl = rts5229_sd_pull_ctl_enable_tbl2; + pcr->sd_pull_ctl_disable_tbl = rts5229_sd_pull_ctl_disable_tbl2; + } else { + pcr->sd_pull_ctl_enable_tbl = rts5229_sd_pull_ctl_enable_tbl1; + pcr->sd_pull_ctl_disable_tbl = rts5229_sd_pull_ctl_disable_tbl1; + } + pcr->ms_pull_ctl_enable_tbl = rts5229_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5229_ms_pull_ctl_disable_tbl; +} diff --git a/kernel/drivers/mfd/rts5249.c b/kernel/drivers/mfd/rts5249.c new file mode 100644 index 000000000..eb2d5866f --- /dev/null +++ b/kernel/drivers/mfd/rts5249.c @@ -0,0 +1,592 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rts5249_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + rtsx_pci_read_register(pcr, DUMMY_REG_RESET_0, &val); + return val & 0x0F; +} + +static void rts5249_fill_driving(struct rtsx_pcr *pcr, u8 voltage) +{ + u8 driving_3v3[4][3] = { + {0x11, 0x11, 0x18}, + {0x55, 0x55, 0x5C}, + {0xFF, 0xFF, 0xFF}, + {0x96, 0x96, 0x96}, + }; + u8 driving_1v8[4][3] = { + {0xC4, 0xC4, 0xC4}, + {0x3C, 0x3C, 0x3C}, + {0xFE, 0xFE, 0xFE}, + {0xB3, 0xB3, 0xB3}, + }; + u8 (*driving)[3], drive_sel; + + if (voltage == OUTPUT_3V3) { + driving = driving_3v3; + drive_sel = pcr->sd30_drive_sel_3v3; + } else { + driving = driving_1v8; + drive_sel = pcr->sd30_drive_sel_1v8; + } + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CLK_DRIVE_SEL, + 0xFF, driving[drive_sel][0]); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CMD_DRIVE_SEL, + 0xFF, driving[drive_sel][1]); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DAT_DRIVE_SEL, + 0xFF, driving[drive_sel][2]); +} + +static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) +{ + u32 reg; + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + + if (!rtsx_vendor_setting_valid(reg)) { + pcr_dbg(pcr, "skip fetch vendor setting\n"); + return; + } + + pcr->aspm_en = rtsx_reg_to_aspm(reg); + pcr->sd30_drive_sel_1v8 = rtsx_reg_to_sd30_drive_sel_1v8(reg); + pcr->card_drive_sel &= 0x3F; + pcr->card_drive_sel |= rtsx_reg_to_card_drive_sel(reg); + + rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG2, ®); + pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG2, reg); + pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); + if (rtsx_reg_check_reverse_socket(reg)) + pcr->flags |= PCR_REVERSE_SOCKET; +} + +static void rtsx_base_force_power_down(struct rtsx_pcr *pcr, u8 pm_state) +{ + /* Set relink_time to 0 */ + rtsx_pci_write_register(pcr, AUTOLOAD_CFG_BASE + 1, 0xFF, 0); + rtsx_pci_write_register(pcr, AUTOLOAD_CFG_BASE + 2, 0xFF, 0); + rtsx_pci_write_register(pcr, AUTOLOAD_CFG_BASE + 3, 0x01, 0); + + if (pm_state == HOST_ENTER_S3) + rtsx_pci_write_register(pcr, pcr->reg_pm_ctrl3, + D3_DELINK_MODE_EN, D3_DELINK_MODE_EN); + + rtsx_pci_write_register(pcr, FPDCTL, 0x03, 0x03); +} + +static int rts5249_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + /* Rest L1SUB Config */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, L1SUB_CONFIG3, 0xFF, 0x00); + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, GPIO_CTL, 0x02, 0x02); + /* Reset ASPM state to default value */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 0); + /* Switch LDO3318 source from DV33 to card_3v3 */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x00); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x01); + /* LED shine disabled, set initial shine cycle period */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, OLT_LED_CTL, 0x0F, 0x02); + /* Configure driving */ + rts5249_fill_driving(pcr, OUTPUT_3V3); + if (pcr->flags & PCR_REVERSE_SOCKET) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0xB0); + else + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0x80); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5249_optimize_phy(struct rtsx_pcr *pcr) +{ + int err; + + err = rtsx_pci_write_register(pcr, PM_CTRL3, D3_DELINK_MODE_EN, 0x00); + if (err < 0) + return err; + + err = rtsx_pci_write_phy_register(pcr, PHY_REV, + PHY_REV_RESV | PHY_REV_RXIDLE_LATCHED | + PHY_REV_P1_EN | PHY_REV_RXIDLE_EN | + PHY_REV_CLKREQ_TX_EN | PHY_REV_RX_PWST | + PHY_REV_CLKREQ_DT_1_0 | PHY_REV_STOP_CLKRD | + PHY_REV_STOP_CLKWR); + if (err < 0) + return err; + + msleep(1); + + err = rtsx_pci_write_phy_register(pcr, PHY_BPCR, + PHY_BPCR_IBRXSEL | PHY_BPCR_IBTXSEL | + PHY_BPCR_IB_FILTER | PHY_BPCR_CMIRROR_EN); + if (err < 0) + return err; + + err = rtsx_pci_write_phy_register(pcr, PHY_PCR, + PHY_PCR_FORCE_CODE | PHY_PCR_OOBS_CALI_50 | + PHY_PCR_OOBS_VCM_08 | PHY_PCR_OOBS_SEN_90 | + PHY_PCR_RSSI_EN | PHY_PCR_RX10K); + if (err < 0) + return err; + + err = rtsx_pci_write_phy_register(pcr, PHY_RCR2, + PHY_RCR2_EMPHASE_EN | PHY_RCR2_NADJR | + PHY_RCR2_CDR_SR_2 | PHY_RCR2_FREQSEL_12 | + PHY_RCR2_CDR_SC_12P | PHY_RCR2_CALIB_LATE); + if (err < 0) + return err; + + err = rtsx_pci_write_phy_register(pcr, PHY_FLD4, + PHY_FLD4_FLDEN_SEL | PHY_FLD4_REQ_REF | + PHY_FLD4_RXAMP_OFF | PHY_FLD4_REQ_ADDA | + PHY_FLD4_BER_COUNT | PHY_FLD4_BER_TIMER | + PHY_FLD4_BER_CHK_EN); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, PHY_RDR, + PHY_RDR_RXDSEL_1_9 | PHY_SSC_AUTO_PWD); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, PHY_RCR1, + PHY_RCR1_ADP_TIME_4 | PHY_RCR1_VCO_COARSE); + if (err < 0) + return err; + err = rtsx_pci_write_phy_register(pcr, PHY_FLD3, + PHY_FLD3_TIMER_4 | PHY_FLD3_TIMER_6 | + PHY_FLD3_RXDELINK); + if (err < 0) + return err; + + return rtsx_pci_write_phy_register(pcr, PHY_TUNE, + PHY_TUNE_TUNEREF_1_0 | PHY_TUNE_VBGSEL_1252 | + PHY_TUNE_SDBUS_33 | PHY_TUNE_TUNED18 | + PHY_TUNE_TUNED12 | PHY_TUNE_TUNEA12); +} + +static int rtsx_base_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x02); +} + +static int rtsx_base_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x00); +} + +static int rtsx_base_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x08); +} + +static int rtsx_base_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x00); +} + +static int rtsx_base_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_VCC_PARTIAL_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x02); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + msleep(5); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_VCC_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x06); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rtsx_base_card_power_off(struct rtsx_pcr *pcr, int card) +{ + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_POWER_OFF); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x00); + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rtsx_base_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + int err; + u16 append; + + switch (voltage) { + case OUTPUT_3V3: + err = rtsx_pci_update_phy(pcr, PHY_TUNE, PHY_TUNE_VOLTAGE_MASK, + PHY_TUNE_VOLTAGE_3V3); + if (err < 0) + return err; + break; + case OUTPUT_1V8: + append = PHY_TUNE_D18_1V8; + if (CHK_PCI_PID(pcr, 0x5249)) { + err = rtsx_pci_update_phy(pcr, PHY_BACR, + PHY_BACR_BASIC_MASK, 0); + if (err < 0) + return err; + append = PHY_TUNE_D18_1V7; + } + + err = rtsx_pci_update_phy(pcr, PHY_TUNE, PHY_TUNE_VOLTAGE_MASK, + append); + if (err < 0) + return err; + break; + default: + pcr_dbg(pcr, "unknown output voltage %d\n", voltage); + return -EINVAL; + } + + /* set pad drive */ + rtsx_pci_init_cmd(pcr); + rts5249_fill_driving(pcr, voltage); + return rtsx_pci_send_cmd(pcr, 100); +} + +static const struct pcr_ops rts5249_pcr_ops = { + .fetch_vendor_settings = rtsx_base_fetch_vendor_settings, + .extra_init_hw = rts5249_extra_init_hw, + .optimize_phy = rts5249_optimize_phy, + .turn_on_led = rtsx_base_turn_on_led, + .turn_off_led = rtsx_base_turn_off_led, + .enable_auto_blink = rtsx_base_enable_auto_blink, + .disable_auto_blink = rtsx_base_disable_auto_blink, + .card_power_on = rtsx_base_card_power_on, + .card_power_off = rtsx_base_card_power_off, + .switch_output_voltage = rtsx_base_switch_output_voltage, + .force_power_down = rtsx_base_force_power_down, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5249_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x66), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0xAA), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5249_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x66), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5249_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5249_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5249_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->ops = &rts5249_pcr_ops; + + pcr->flags = 0; + pcr->card_drive_sel = RTSX_CARD_DRIVE_DEFAULT; + pcr->sd30_drive_sel_1v8 = CFG_DRIVER_TYPE_B; + pcr->sd30_drive_sel_3v3 = CFG_DRIVER_TYPE_B; + pcr->aspm_en = ASPM_L1_EN; + pcr->tx_initial_phase = SET_CLOCK_PHASE(1, 29, 16); + pcr->rx_initial_phase = SET_CLOCK_PHASE(24, 6, 5); + + pcr->ic_version = rts5249_get_ic_version(pcr); + pcr->sd_pull_ctl_enable_tbl = rts5249_sd_pull_ctl_enable_tbl; + pcr->sd_pull_ctl_disable_tbl = rts5249_sd_pull_ctl_disable_tbl; + pcr->ms_pull_ctl_enable_tbl = rts5249_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5249_ms_pull_ctl_disable_tbl; + + pcr->reg_pm_ctrl3 = PM_CTRL3; +} + +static int rts524a_write_phy(struct rtsx_pcr *pcr, u8 addr, u16 val) +{ + addr = addr & 0x80 ? (addr & 0x7F) | 0x40 : addr; + + return __rtsx_pci_write_phy_register(pcr, addr, val); +} + +static int rts524a_read_phy(struct rtsx_pcr *pcr, u8 addr, u16 *val) +{ + addr = addr & 0x80 ? (addr & 0x7F) | 0x40 : addr; + + return __rtsx_pci_read_phy_register(pcr, addr, val); +} + +static int rts524a_optimize_phy(struct rtsx_pcr *pcr) +{ + int err; + + err = rtsx_pci_write_register(pcr, RTS524A_PM_CTRL3, + D3_DELINK_MODE_EN, 0x00); + if (err < 0) + return err; + + rtsx_pci_write_phy_register(pcr, PHY_PCR, + PHY_PCR_FORCE_CODE | PHY_PCR_OOBS_CALI_50 | + PHY_PCR_OOBS_VCM_08 | PHY_PCR_OOBS_SEN_90 | PHY_PCR_RSSI_EN); + rtsx_pci_write_phy_register(pcr, PHY_SSCCR3, + PHY_SSCCR3_STEP_IN | PHY_SSCCR3_CHECK_DELAY); + + if (is_version(pcr, 0x524A, IC_VER_A)) { + rtsx_pci_write_phy_register(pcr, PHY_SSCCR3, + PHY_SSCCR3_STEP_IN | PHY_SSCCR3_CHECK_DELAY); + rtsx_pci_write_phy_register(pcr, PHY_SSCCR2, + PHY_SSCCR2_PLL_NCODE | PHY_SSCCR2_TIME0 | + PHY_SSCCR2_TIME2_WIDTH); + rtsx_pci_write_phy_register(pcr, PHY_ANA1A, + PHY_ANA1A_TXR_LOOPBACK | PHY_ANA1A_RXT_BIST | + PHY_ANA1A_TXR_BIST | PHY_ANA1A_REV); + rtsx_pci_write_phy_register(pcr, PHY_ANA1D, + PHY_ANA1D_DEBUG_ADDR); + rtsx_pci_write_phy_register(pcr, PHY_DIG1E, + PHY_DIG1E_REV | PHY_DIG1E_D0_X_D1 | + PHY_DIG1E_RX_ON_HOST | PHY_DIG1E_RCLK_REF_HOST | + PHY_DIG1E_RCLK_TX_EN_KEEP | + PHY_DIG1E_RCLK_TX_TERM_KEEP | + PHY_DIG1E_RCLK_RX_EIDLE_ON | PHY_DIG1E_TX_TERM_KEEP | + PHY_DIG1E_RX_TERM_KEEP | PHY_DIG1E_TX_EN_KEEP | + PHY_DIG1E_RX_EN_KEEP); + } + + rtsx_pci_write_phy_register(pcr, PHY_ANA08, + PHY_ANA08_RX_EQ_DCGAIN | PHY_ANA08_SEL_RX_EN | + PHY_ANA08_RX_EQ_VAL | PHY_ANA08_SCP | PHY_ANA08_SEL_IPI); + + return 0; +} + +static int rts524a_extra_init_hw(struct rtsx_pcr *pcr) +{ + rts5249_extra_init_hw(pcr); + + rtsx_pci_write_register(pcr, FUNC_FORCE_CTL, + FORCE_ASPM_L1_EN, FORCE_ASPM_L1_EN); + rtsx_pci_write_register(pcr, PM_EVENT_DEBUG, PME_DEBUG_0, PME_DEBUG_0); + rtsx_pci_write_register(pcr, LDO_VCC_CFG1, LDO_VCC_LMT_EN, + LDO_VCC_LMT_EN); + rtsx_pci_write_register(pcr, PCLK_CTL, PCLK_MODE_SEL, PCLK_MODE_SEL); + if (is_version(pcr, 0x524A, IC_VER_A)) { + rtsx_pci_write_register(pcr, LDO_DV18_CFG, + LDO_DV18_SR_MASK, LDO_DV18_SR_DF); + rtsx_pci_write_register(pcr, LDO_VCC_CFG1, + LDO_VCC_REF_TUNE_MASK, LDO_VCC_REF_1V2); + rtsx_pci_write_register(pcr, LDO_VIO_CFG, + LDO_VIO_REF_TUNE_MASK, LDO_VIO_REF_1V2); + rtsx_pci_write_register(pcr, LDO_VIO_CFG, + LDO_VIO_SR_MASK, LDO_VIO_SR_DF); + rtsx_pci_write_register(pcr, LDO_DV12S_CFG, + LDO_REF12_TUNE_MASK, LDO_REF12_TUNE_DF); + rtsx_pci_write_register(pcr, SD40_LDO_CTL1, + SD40_VIO_TUNE_MASK, SD40_VIO_TUNE_1V7); + } + + return 0; +} + +static const struct pcr_ops rts524a_pcr_ops = { + .write_phy = rts524a_write_phy, + .read_phy = rts524a_read_phy, + .fetch_vendor_settings = rtsx_base_fetch_vendor_settings, + .extra_init_hw = rts524a_extra_init_hw, + .optimize_phy = rts524a_optimize_phy, + .turn_on_led = rtsx_base_turn_on_led, + .turn_off_led = rtsx_base_turn_off_led, + .enable_auto_blink = rtsx_base_enable_auto_blink, + .disable_auto_blink = rtsx_base_disable_auto_blink, + .card_power_on = rtsx_base_card_power_on, + .card_power_off = rtsx_base_card_power_off, + .switch_output_voltage = rtsx_base_switch_output_voltage, + .force_power_down = rtsx_base_force_power_down, +}; + +void rts524a_init_params(struct rtsx_pcr *pcr) +{ + rts5249_init_params(pcr); + + pcr->reg_pm_ctrl3 = RTS524A_PM_CTRL3; + pcr->ops = &rts524a_pcr_ops; +} + +static int rts525a_card_power_on(struct rtsx_pcr *pcr, int card) +{ + rtsx_pci_write_register(pcr, LDO_VCC_CFG1, + LDO_VCC_TUNE_MASK, LDO_VCC_3V3); + return rtsx_base_card_power_on(pcr, card); +} + +static int rts525a_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + switch (voltage) { + case OUTPUT_3V3: + rtsx_pci_write_register(pcr, LDO_CONFIG2, + LDO_D3318_MASK, LDO_D3318_33V); + rtsx_pci_write_register(pcr, SD_PAD_CTL, SD_IO_USING_1V8, 0); + break; + case OUTPUT_1V8: + rtsx_pci_write_register(pcr, LDO_CONFIG2, + LDO_D3318_MASK, LDO_D3318_18V); + rtsx_pci_write_register(pcr, SD_PAD_CTL, SD_IO_USING_1V8, + SD_IO_USING_1V8); + break; + default: + return -EINVAL; + } + + rtsx_pci_init_cmd(pcr); + rts5249_fill_driving(pcr, voltage); + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts525a_optimize_phy(struct rtsx_pcr *pcr) +{ + int err; + + err = rtsx_pci_write_register(pcr, RTS524A_PM_CTRL3, + D3_DELINK_MODE_EN, 0x00); + if (err < 0) + return err; + + rtsx_pci_write_phy_register(pcr, _PHY_FLD0, + _PHY_FLD0_CLK_REQ_20C | _PHY_FLD0_RX_IDLE_EN | + _PHY_FLD0_BIT_ERR_RSTN | _PHY_FLD0_BER_COUNT | + _PHY_FLD0_BER_TIMER | _PHY_FLD0_CHECK_EN); + + rtsx_pci_write_phy_register(pcr, _PHY_ANA03, + _PHY_ANA03_TIMER_MAX | _PHY_ANA03_OOBS_DEB_EN | + _PHY_CMU_DEBUG_EN); + + if (is_version(pcr, 0x525A, IC_VER_A)) + rtsx_pci_write_phy_register(pcr, _PHY_REV0, + _PHY_REV0_FILTER_OUT | _PHY_REV0_CDR_BYPASS_PFD | + _PHY_REV0_CDR_RX_IDLE_BYPASS); + + return 0; +} + +static int rts525a_extra_init_hw(struct rtsx_pcr *pcr) +{ + rts5249_extra_init_hw(pcr); + + rtsx_pci_write_register(pcr, PCLK_CTL, PCLK_MODE_SEL, PCLK_MODE_SEL); + if (is_version(pcr, 0x525A, IC_VER_A)) { + rtsx_pci_write_register(pcr, L1SUB_CONFIG2, + L1SUB_AUTO_CFG, L1SUB_AUTO_CFG); + rtsx_pci_write_register(pcr, RREF_CFG, + RREF_VBGSEL_MASK, RREF_VBGSEL_1V25); + rtsx_pci_write_register(pcr, LDO_VIO_CFG, + LDO_VIO_TUNE_MASK, LDO_VIO_1V7); + rtsx_pci_write_register(pcr, LDO_DV12S_CFG, + LDO_D12_TUNE_MASK, LDO_D12_TUNE_DF); + rtsx_pci_write_register(pcr, LDO_AV12S_CFG, + LDO_AV12S_TUNE_MASK, LDO_AV12S_TUNE_DF); + rtsx_pci_write_register(pcr, LDO_VCC_CFG0, + LDO_VCC_LMTVTH_MASK, LDO_VCC_LMTVTH_2A); + rtsx_pci_write_register(pcr, OOBS_CONFIG, + OOBS_AUTOK_DIS | OOBS_VAL_MASK, 0x89); + } + + return 0; +} + +static const struct pcr_ops rts525a_pcr_ops = { + .fetch_vendor_settings = rtsx_base_fetch_vendor_settings, + .extra_init_hw = rts525a_extra_init_hw, + .optimize_phy = rts525a_optimize_phy, + .turn_on_led = rtsx_base_turn_on_led, + .turn_off_led = rtsx_base_turn_off_led, + .enable_auto_blink = rtsx_base_enable_auto_blink, + .disable_auto_blink = rtsx_base_disable_auto_blink, + .card_power_on = rts525a_card_power_on, + .card_power_off = rtsx_base_card_power_off, + .switch_output_voltage = rts525a_switch_output_voltage, + .force_power_down = rtsx_base_force_power_down, +}; + +void rts525a_init_params(struct rtsx_pcr *pcr) +{ + rts5249_init_params(pcr); + + pcr->reg_pm_ctrl3 = RTS524A_PM_CTRL3; + pcr->ops = &rts525a_pcr_ops; +} + diff --git a/kernel/drivers/mfd/rtsx_pcr.c b/kernel/drivers/mfd/rtsx_pcr.c new file mode 100644 index 000000000..a66540a49 --- /dev/null +++ b/kernel/drivers/mfd/rtsx_pcr.c @@ -0,0 +1,1425 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/idr.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rtsx_pci.h> +#include <asm/unaligned.h> + +#include "rtsx_pcr.h" + +static bool msi_en = true; +module_param(msi_en, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(msi_en, "Enable MSI"); + +static DEFINE_IDR(rtsx_pci_idr); +static DEFINE_SPINLOCK(rtsx_pci_lock); + +static struct mfd_cell rtsx_pcr_cells[] = { + [RTSX_SD_CARD] = { + .name = DRV_NAME_RTSX_PCI_SDMMC, + }, + [RTSX_MS_CARD] = { + .name = DRV_NAME_RTSX_PCI_MS, + }, +}; + +static const struct pci_device_id rtsx_pci_ids[] = { + { PCI_DEVICE(0x10EC, 0x5209), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5229), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5289), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5227), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5249), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5287), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5286), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x524A), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x525A), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, rtsx_pci_ids); + +static inline void rtsx_pci_enable_aspm(struct rtsx_pcr *pcr) +{ + rtsx_pci_update_cfg_byte(pcr, pcr->pcie_cap + PCI_EXP_LNKCTL, + 0xFC, pcr->aspm_en); +} + +static inline void rtsx_pci_disable_aspm(struct rtsx_pcr *pcr) +{ + rtsx_pci_update_cfg_byte(pcr, pcr->pcie_cap + PCI_EXP_LNKCTL, + 0xFC, 0); +} + +void rtsx_pci_start_run(struct rtsx_pcr *pcr) +{ + /* If pci device removed, don't queue idle work any more */ + if (pcr->remove_pci) + return; + + if (pcr->state != PDEV_STAT_RUN) { + pcr->state = PDEV_STAT_RUN; + if (pcr->ops->enable_auto_blink) + pcr->ops->enable_auto_blink(pcr); + + if (pcr->aspm_en) + rtsx_pci_disable_aspm(pcr); + } + + mod_delayed_work(system_wq, &pcr->idle_work, msecs_to_jiffies(200)); +} +EXPORT_SYMBOL_GPL(rtsx_pci_start_run); + +int rtsx_pci_write_register(struct rtsx_pcr *pcr, u16 addr, u8 mask, u8 data) +{ + int i; + u32 val = HAIMR_WRITE_START; + + val |= (u32)(addr & 0x3FFF) << 16; + val |= (u32)mask << 8; + val |= (u32)data; + + rtsx_pci_writel(pcr, RTSX_HAIMR, val); + + for (i = 0; i < MAX_RW_REG_CNT; i++) { + val = rtsx_pci_readl(pcr, RTSX_HAIMR); + if ((val & HAIMR_TRANS_END) == 0) { + if (data != (u8)val) + return -EIO; + return 0; + } + } + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(rtsx_pci_write_register); + +int rtsx_pci_read_register(struct rtsx_pcr *pcr, u16 addr, u8 *data) +{ + u32 val = HAIMR_READ_START; + int i; + + val |= (u32)(addr & 0x3FFF) << 16; + rtsx_pci_writel(pcr, RTSX_HAIMR, val); + + for (i = 0; i < MAX_RW_REG_CNT; i++) { + val = rtsx_pci_readl(pcr, RTSX_HAIMR); + if ((val & HAIMR_TRANS_END) == 0) + break; + } + + if (i >= MAX_RW_REG_CNT) + return -ETIMEDOUT; + + if (data) + *data = (u8)(val & 0xFF); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_read_register); + +int __rtsx_pci_write_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 val) +{ + int err, i, finished = 0; + u8 tmp; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYDATA0, 0xFF, (u8)val); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYDATA1, 0xFF, (u8)(val >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYADDR, 0xFF, addr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYRWCTL, 0xFF, 0x81); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + for (i = 0; i < 100000; i++) { + err = rtsx_pci_read_register(pcr, PHYRWCTL, &tmp); + if (err < 0) + return err; + + if (!(tmp & 0x80)) { + finished = 1; + break; + } + } + + if (!finished) + return -ETIMEDOUT; + + return 0; +} + +int rtsx_pci_write_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 val) +{ + if (pcr->ops->write_phy) + return pcr->ops->write_phy(pcr, addr, val); + + return __rtsx_pci_write_phy_register(pcr, addr, val); +} +EXPORT_SYMBOL_GPL(rtsx_pci_write_phy_register); + +int __rtsx_pci_read_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 *val) +{ + int err, i, finished = 0; + u16 data; + u8 *ptr, tmp; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYADDR, 0xFF, addr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYRWCTL, 0xFF, 0x80); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + for (i = 0; i < 100000; i++) { + err = rtsx_pci_read_register(pcr, PHYRWCTL, &tmp); + if (err < 0) + return err; + + if (!(tmp & 0x80)) { + finished = 1; + break; + } + } + + if (!finished) + return -ETIMEDOUT; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, READ_REG_CMD, PHYDATA0, 0, 0); + rtsx_pci_add_cmd(pcr, READ_REG_CMD, PHYDATA1, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + ptr = rtsx_pci_get_cmd_data(pcr); + data = ((u16)ptr[1] << 8) | ptr[0]; + + if (val) + *val = data; + + return 0; +} + +int rtsx_pci_read_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 *val) +{ + if (pcr->ops->read_phy) + return pcr->ops->read_phy(pcr, addr, val); + + return __rtsx_pci_read_phy_register(pcr, addr, val); +} +EXPORT_SYMBOL_GPL(rtsx_pci_read_phy_register); + +void rtsx_pci_stop_cmd(struct rtsx_pcr *pcr) +{ + rtsx_pci_writel(pcr, RTSX_HCBCTLR, STOP_CMD); + rtsx_pci_writel(pcr, RTSX_HDBCTLR, STOP_DMA); + + rtsx_pci_write_register(pcr, DMACTL, 0x80, 0x80); + rtsx_pci_write_register(pcr, RBCTL, 0x80, 0x80); +} +EXPORT_SYMBOL_GPL(rtsx_pci_stop_cmd); + +void rtsx_pci_add_cmd(struct rtsx_pcr *pcr, + u8 cmd_type, u16 reg_addr, u8 mask, u8 data) +{ + unsigned long flags; + u32 val = 0; + u32 *ptr = (u32 *)(pcr->host_cmds_ptr); + + val |= (u32)(cmd_type & 0x03) << 30; + val |= (u32)(reg_addr & 0x3FFF) << 16; + val |= (u32)mask << 8; + val |= (u32)data; + + spin_lock_irqsave(&pcr->lock, flags); + ptr += pcr->ci; + if (pcr->ci < (HOST_CMDS_BUF_LEN / 4)) { + put_unaligned_le32(val, ptr); + ptr++; + pcr->ci++; + } + spin_unlock_irqrestore(&pcr->lock, flags); +} +EXPORT_SYMBOL_GPL(rtsx_pci_add_cmd); + +void rtsx_pci_send_cmd_no_wait(struct rtsx_pcr *pcr) +{ + u32 val = 1 << 31; + + rtsx_pci_writel(pcr, RTSX_HCBAR, pcr->host_cmds_addr); + + val |= (u32)(pcr->ci * 4) & 0x00FFFFFF; + /* Hardware Auto Response */ + val |= 0x40000000; + rtsx_pci_writel(pcr, RTSX_HCBCTLR, val); +} +EXPORT_SYMBOL_GPL(rtsx_pci_send_cmd_no_wait); + +int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout) +{ + struct completion trans_done; + u32 val = 1 << 31; + long timeleft; + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&pcr->lock, flags); + + /* set up data structures for the wakeup system */ + pcr->done = &trans_done; + pcr->trans_result = TRANS_NOT_READY; + init_completion(&trans_done); + + rtsx_pci_writel(pcr, RTSX_HCBAR, pcr->host_cmds_addr); + + val |= (u32)(pcr->ci * 4) & 0x00FFFFFF; + /* Hardware Auto Response */ + val |= 0x40000000; + rtsx_pci_writel(pcr, RTSX_HCBCTLR, val); + + spin_unlock_irqrestore(&pcr->lock, flags); + + /* Wait for TRANS_OK_INT */ + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + pcr_dbg(pcr, "Timeout (%s %d)\n", __func__, __LINE__); + err = -ETIMEDOUT; + goto finish_send_cmd; + } + + spin_lock_irqsave(&pcr->lock, flags); + if (pcr->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pcr->trans_result == TRANS_RESULT_OK) + err = 0; + else if (pcr->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + spin_unlock_irqrestore(&pcr->lock, flags); + +finish_send_cmd: + spin_lock_irqsave(&pcr->lock, flags); + pcr->done = NULL; + spin_unlock_irqrestore(&pcr->lock, flags); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pcr); + + if (pcr->finish_me) + complete(pcr->finish_me); + + return err; +} +EXPORT_SYMBOL_GPL(rtsx_pci_send_cmd); + +static void rtsx_pci_add_sg_tbl(struct rtsx_pcr *pcr, + dma_addr_t addr, unsigned int len, int end) +{ + u64 *ptr = (u64 *)(pcr->host_sg_tbl_ptr) + pcr->sgi; + u64 val; + u8 option = SG_VALID | SG_TRANS_DATA; + + pcr_dbg(pcr, "DMA addr: 0x%x, Len: 0x%x\n", (unsigned int)addr, len); + + if (end) + option |= SG_END; + val = ((u64)addr << 32) | ((u64)len << 12) | option; + + put_unaligned_le64(val, ptr); + pcr->sgi++; +} + +int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read, int timeout) +{ + int err = 0, count; + + pcr_dbg(pcr, "--> %s: num_sg = %d\n", __func__, num_sg); + count = rtsx_pci_dma_map_sg(pcr, sglist, num_sg, read); + if (count < 1) + return -EINVAL; + pcr_dbg(pcr, "DMA mapping count: %d\n", count); + + err = rtsx_pci_dma_transfer(pcr, sglist, count, read, timeout); + + rtsx_pci_dma_unmap_sg(pcr, sglist, num_sg, read); + + return err; +} +EXPORT_SYMBOL_GPL(rtsx_pci_transfer_data); + +int rtsx_pci_dma_map_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read) +{ + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + if (pcr->remove_pci) + return -EINVAL; + + if ((sglist == NULL) || (num_sg <= 0)) + return -EINVAL; + + return dma_map_sg(&(pcr->pci->dev), sglist, num_sg, dir); +} +EXPORT_SYMBOL_GPL(rtsx_pci_dma_map_sg); + +void rtsx_pci_dma_unmap_sg(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read) +{ + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + dma_unmap_sg(&(pcr->pci->dev), sglist, num_sg, dir); +} +EXPORT_SYMBOL_GPL(rtsx_pci_dma_unmap_sg); + +int rtsx_pci_dma_transfer(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int count, bool read, int timeout) +{ + struct completion trans_done; + struct scatterlist *sg; + dma_addr_t addr; + long timeleft; + unsigned long flags; + unsigned int len; + int i, err = 0; + u32 val; + u8 dir = read ? DEVICE_TO_HOST : HOST_TO_DEVICE; + + if (pcr->remove_pci) + return -ENODEV; + + if ((sglist == NULL) || (count < 1)) + return -EINVAL; + + val = ((u32)(dir & 0x01) << 29) | TRIG_DMA | ADMA_MODE; + pcr->sgi = 0; + for_each_sg(sglist, sg, count, i) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + rtsx_pci_add_sg_tbl(pcr, addr, len, i == count - 1); + } + + spin_lock_irqsave(&pcr->lock, flags); + + pcr->done = &trans_done; + pcr->trans_result = TRANS_NOT_READY; + init_completion(&trans_done); + rtsx_pci_writel(pcr, RTSX_HDBAR, pcr->host_sg_tbl_addr); + rtsx_pci_writel(pcr, RTSX_HDBCTLR, val); + + spin_unlock_irqrestore(&pcr->lock, flags); + + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + pcr_dbg(pcr, "Timeout (%s %d)\n", __func__, __LINE__); + err = -ETIMEDOUT; + goto out; + } + + spin_lock_irqsave(&pcr->lock, flags); + if (pcr->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pcr->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + spin_unlock_irqrestore(&pcr->lock, flags); + +out: + spin_lock_irqsave(&pcr->lock, flags); + pcr->done = NULL; + spin_unlock_irqrestore(&pcr->lock, flags); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pcr); + + if (pcr->finish_me) + complete(pcr->finish_me); + + return err; +} +EXPORT_SYMBOL_GPL(rtsx_pci_dma_transfer); + +int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len) +{ + int err; + int i, j; + u16 reg; + u8 *ptr; + + if (buf_len > 512) + buf_len = 512; + + ptr = buf; + reg = PPBUF_BASE2; + for (i = 0; i < buf_len / 256; i++) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < 256; j++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, reg++, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + + memcpy(ptr, rtsx_pci_get_cmd_data(pcr), 256); + ptr += 256; + } + + if (buf_len % 256) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < buf_len % 256; j++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, reg++, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + } + + memcpy(ptr, rtsx_pci_get_cmd_data(pcr), buf_len % 256); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_read_ppbuf); + +int rtsx_pci_write_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len) +{ + int err; + int i, j; + u16 reg; + u8 *ptr; + + if (buf_len > 512) + buf_len = 512; + + ptr = buf; + reg = PPBUF_BASE2; + for (i = 0; i < buf_len / 256; i++) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < 256; j++) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + reg++, 0xFF, *ptr); + ptr++; + } + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + } + + if (buf_len % 256) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < buf_len % 256; j++) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + reg++, 0xFF, *ptr); + ptr++; + } + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_write_ppbuf); + +static int rtsx_pci_set_pull_ctl(struct rtsx_pcr *pcr, const u32 *tbl) +{ + int err; + + rtsx_pci_init_cmd(pcr); + + while (*tbl & 0xFFFF0000) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + (u16)(*tbl >> 16), 0xFF, (u8)(*tbl)); + tbl++; + } + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +int rtsx_pci_card_pull_ctl_enable(struct rtsx_pcr *pcr, int card) +{ + const u32 *tbl; + + if (card == RTSX_SD_CARD) + tbl = pcr->sd_pull_ctl_enable_tbl; + else if (card == RTSX_MS_CARD) + tbl = pcr->ms_pull_ctl_enable_tbl; + else + return -EINVAL; + + return rtsx_pci_set_pull_ctl(pcr, tbl); +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_pull_ctl_enable); + +int rtsx_pci_card_pull_ctl_disable(struct rtsx_pcr *pcr, int card) +{ + const u32 *tbl; + + if (card == RTSX_SD_CARD) + tbl = pcr->sd_pull_ctl_disable_tbl; + else if (card == RTSX_MS_CARD) + tbl = pcr->ms_pull_ctl_disable_tbl; + else + return -EINVAL; + + + return rtsx_pci_set_pull_ctl(pcr, tbl); +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_pull_ctl_disable); + +static void rtsx_pci_enable_bus_int(struct rtsx_pcr *pcr) +{ + pcr->bier = TRANS_OK_INT_EN | TRANS_FAIL_INT_EN | SD_INT_EN; + + if (pcr->num_slots > 1) + pcr->bier |= MS_INT_EN; + + /* Enable Bus Interrupt */ + rtsx_pci_writel(pcr, RTSX_BIER, pcr->bier); + + pcr_dbg(pcr, "RTSX_BIER: 0x%08x\n", pcr->bier); +} + +static inline u8 double_ssc_depth(u8 depth) +{ + return ((depth > 1) ? (depth - 1) : depth); +} + +static u8 revise_ssc_depth(u8 ssc_depth, u8 div) +{ + if (div > CLK_DIV_1) { + if (ssc_depth > (div - 1)) + ssc_depth -= (div - 1); + else + ssc_depth = SSC_DEPTH_4M; + } + + return ssc_depth; +} + +int rtsx_pci_switch_clock(struct rtsx_pcr *pcr, unsigned int card_clock, + u8 ssc_depth, bool initial_mode, bool double_clk, bool vpclk) +{ + int err, clk; + u8 n, clk_divider, mcu_cnt, div; + u8 depth[] = { + [RTSX_SSC_DEPTH_4M] = SSC_DEPTH_4M, + [RTSX_SSC_DEPTH_2M] = SSC_DEPTH_2M, + [RTSX_SSC_DEPTH_1M] = SSC_DEPTH_1M, + [RTSX_SSC_DEPTH_500K] = SSC_DEPTH_500K, + [RTSX_SSC_DEPTH_250K] = SSC_DEPTH_250K, + }; + + if (initial_mode) { + /* We use 250k(around) here, in initial stage */ + clk_divider = SD_CLK_DIVIDE_128; + card_clock = 30000000; + } else { + clk_divider = SD_CLK_DIVIDE_0; + } + err = rtsx_pci_write_register(pcr, SD_CFG1, + SD_CLK_DIVIDE_MASK, clk_divider); + if (err < 0) + return err; + + card_clock /= 1000000; + pcr_dbg(pcr, "Switch card clock to %dMHz\n", card_clock); + + clk = card_clock; + if (!initial_mode && double_clk) + clk = card_clock * 2; + pcr_dbg(pcr, "Internal SSC clock: %dMHz (cur_clock = %d)\n", + clk, pcr->cur_clock); + + if (clk == pcr->cur_clock) + return 0; + + if (pcr->ops->conv_clk_and_div_n) + n = (u8)pcr->ops->conv_clk_and_div_n(clk, CLK_TO_DIV_N); + else + n = (u8)(clk - 2); + if ((clk <= 2) || (n > MAX_DIV_N_PCR)) + return -EINVAL; + + mcu_cnt = (u8)(125/clk + 3); + if (mcu_cnt > 15) + mcu_cnt = 15; + + /* Make sure that the SSC clock div_n is not less than MIN_DIV_N_PCR */ + div = CLK_DIV_1; + while ((n < MIN_DIV_N_PCR) && (div < CLK_DIV_8)) { + if (pcr->ops->conv_clk_and_div_n) { + int dbl_clk = pcr->ops->conv_clk_and_div_n(n, + DIV_N_TO_CLK) * 2; + n = (u8)pcr->ops->conv_clk_and_div_n(dbl_clk, + CLK_TO_DIV_N); + } else { + n = (n + 2) * 2 - 2; + } + div++; + } + pcr_dbg(pcr, "n = %d, div = %d\n", n, div); + + ssc_depth = depth[ssc_depth]; + if (double_clk) + ssc_depth = double_ssc_depth(ssc_depth); + + ssc_depth = revise_ssc_depth(ssc_depth, div); + pcr_dbg(pcr, "ssc_depth = %d\n", ssc_depth); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_DIV, + 0xFF, (div << 4) | mcu_cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL2, + SSC_DEPTH_MASK, ssc_depth); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_DIV_N_0, 0xFF, n); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, SSC_RSTB); + if (vpclk) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, PHASE_NOT_RESET); + } + + err = rtsx_pci_send_cmd(pcr, 2000); + if (err < 0) + return err; + + /* Wait SSC clock stable */ + udelay(10); + err = rtsx_pci_write_register(pcr, CLK_CTL, CLK_LOW_FREQ, 0); + if (err < 0) + return err; + + pcr->cur_clock = clk; + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_switch_clock); + +int rtsx_pci_card_power_on(struct rtsx_pcr *pcr, int card) +{ + if (pcr->ops->card_power_on) + return pcr->ops->card_power_on(pcr, card); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_power_on); + +int rtsx_pci_card_power_off(struct rtsx_pcr *pcr, int card) +{ + if (pcr->ops->card_power_off) + return pcr->ops->card_power_off(pcr, card); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_power_off); + +int rtsx_pci_card_exclusive_check(struct rtsx_pcr *pcr, int card) +{ + unsigned int cd_mask[] = { + [RTSX_SD_CARD] = SD_EXIST, + [RTSX_MS_CARD] = MS_EXIST + }; + + if (!(pcr->flags & PCR_MS_PMOS)) { + /* When using single PMOS, accessing card is not permitted + * if the existing card is not the designated one. + */ + if (pcr->card_exist & (~cd_mask[card])) + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_exclusive_check); + +int rtsx_pci_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) +{ + if (pcr->ops->switch_output_voltage) + return pcr->ops->switch_output_voltage(pcr, voltage); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_switch_output_voltage); + +unsigned int rtsx_pci_card_exist(struct rtsx_pcr *pcr) +{ + unsigned int val; + + val = rtsx_pci_readl(pcr, RTSX_BIPR); + if (pcr->ops->cd_deglitch) + val = pcr->ops->cd_deglitch(pcr); + + return val; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_exist); + +void rtsx_pci_complete_unfinished_transfer(struct rtsx_pcr *pcr) +{ + struct completion finish; + + pcr->finish_me = &finish; + init_completion(&finish); + + if (pcr->done) + complete(pcr->done); + + if (!pcr->remove_pci) + rtsx_pci_stop_cmd(pcr); + + wait_for_completion_interruptible_timeout(&finish, + msecs_to_jiffies(2)); + pcr->finish_me = NULL; +} +EXPORT_SYMBOL_GPL(rtsx_pci_complete_unfinished_transfer); + +static void rtsx_pci_card_detect(struct work_struct *work) +{ + struct delayed_work *dwork; + struct rtsx_pcr *pcr; + unsigned long flags; + unsigned int card_detect = 0, card_inserted, card_removed; + u32 irq_status; + + dwork = to_delayed_work(work); + pcr = container_of(dwork, struct rtsx_pcr, carddet_work); + + pcr_dbg(pcr, "--> %s\n", __func__); + + mutex_lock(&pcr->pcr_mutex); + spin_lock_irqsave(&pcr->lock, flags); + + irq_status = rtsx_pci_readl(pcr, RTSX_BIPR); + pcr_dbg(pcr, "irq_status: 0x%08x\n", irq_status); + + irq_status &= CARD_EXIST; + card_inserted = pcr->card_inserted & irq_status; + card_removed = pcr->card_removed; + pcr->card_inserted = 0; + pcr->card_removed = 0; + + spin_unlock_irqrestore(&pcr->lock, flags); + + if (card_inserted || card_removed) { + pcr_dbg(pcr, "card_inserted: 0x%x, card_removed: 0x%x\n", + card_inserted, card_removed); + + if (pcr->ops->cd_deglitch) + card_inserted = pcr->ops->cd_deglitch(pcr); + + card_detect = card_inserted | card_removed; + + pcr->card_exist |= card_inserted; + pcr->card_exist &= ~card_removed; + } + + mutex_unlock(&pcr->pcr_mutex); + + if ((card_detect & SD_EXIST) && pcr->slots[RTSX_SD_CARD].card_event) + pcr->slots[RTSX_SD_CARD].card_event( + pcr->slots[RTSX_SD_CARD].p_dev); + if ((card_detect & MS_EXIST) && pcr->slots[RTSX_MS_CARD].card_event) + pcr->slots[RTSX_MS_CARD].card_event( + pcr->slots[RTSX_MS_CARD].p_dev); +} + +static irqreturn_t rtsx_pci_isr(int irq, void *dev_id) +{ + struct rtsx_pcr *pcr = dev_id; + u32 int_reg; + + if (!pcr) + return IRQ_NONE; + + spin_lock(&pcr->lock); + + int_reg = rtsx_pci_readl(pcr, RTSX_BIPR); + /* Clear interrupt flag */ + rtsx_pci_writel(pcr, RTSX_BIPR, int_reg); + if ((int_reg & pcr->bier) == 0) { + spin_unlock(&pcr->lock); + return IRQ_NONE; + } + if (int_reg == 0xFFFFFFFF) { + spin_unlock(&pcr->lock); + return IRQ_HANDLED; + } + + int_reg &= (pcr->bier | 0x7FFFFF); + + if (int_reg & SD_INT) { + if (int_reg & SD_EXIST) { + pcr->card_inserted |= SD_EXIST; + } else { + pcr->card_removed |= SD_EXIST; + pcr->card_inserted &= ~SD_EXIST; + } + } + + if (int_reg & MS_INT) { + if (int_reg & MS_EXIST) { + pcr->card_inserted |= MS_EXIST; + } else { + pcr->card_removed |= MS_EXIST; + pcr->card_inserted &= ~MS_EXIST; + } + } + + if (int_reg & (NEED_COMPLETE_INT | DELINK_INT)) { + if (int_reg & (TRANS_FAIL_INT | DELINK_INT)) { + pcr->trans_result = TRANS_RESULT_FAIL; + if (pcr->done) + complete(pcr->done); + } else if (int_reg & TRANS_OK_INT) { + pcr->trans_result = TRANS_RESULT_OK; + if (pcr->done) + complete(pcr->done); + } + } + + if (pcr->card_inserted || pcr->card_removed) + schedule_delayed_work(&pcr->carddet_work, + msecs_to_jiffies(200)); + + spin_unlock(&pcr->lock); + return IRQ_HANDLED; +} + +static int rtsx_pci_acquire_irq(struct rtsx_pcr *pcr) +{ + dev_info(&(pcr->pci->dev), "%s: pcr->msi_en = %d, pci->irq = %d\n", + __func__, pcr->msi_en, pcr->pci->irq); + + if (request_irq(pcr->pci->irq, rtsx_pci_isr, + pcr->msi_en ? 0 : IRQF_SHARED, + DRV_NAME_RTSX_PCI, pcr)) { + dev_err(&(pcr->pci->dev), + "rtsx_sdmmc: unable to grab IRQ %d, disabling device\n", + pcr->pci->irq); + return -1; + } + + pcr->irq = pcr->pci->irq; + pci_intx(pcr->pci, !pcr->msi_en); + + return 0; +} + +static void rtsx_pci_idle_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct rtsx_pcr *pcr = container_of(dwork, struct rtsx_pcr, idle_work); + + pcr_dbg(pcr, "--> %s\n", __func__); + + mutex_lock(&pcr->pcr_mutex); + + pcr->state = PDEV_STAT_IDLE; + + if (pcr->ops->disable_auto_blink) + pcr->ops->disable_auto_blink(pcr); + if (pcr->ops->turn_off_led) + pcr->ops->turn_off_led(pcr); + + if (pcr->aspm_en) + rtsx_pci_enable_aspm(pcr); + + mutex_unlock(&pcr->pcr_mutex); +} + +#ifdef CONFIG_PM +static void rtsx_pci_power_off(struct rtsx_pcr *pcr, u8 pm_state) +{ + if (pcr->ops->turn_off_led) + pcr->ops->turn_off_led(pcr); + + rtsx_pci_writel(pcr, RTSX_BIER, 0); + pcr->bier = 0; + + rtsx_pci_write_register(pcr, PETXCFG, 0x08, 0x08); + rtsx_pci_write_register(pcr, HOST_SLEEP_STATE, 0x03, pm_state); + + if (pcr->ops->force_power_down) + pcr->ops->force_power_down(pcr, pm_state); +} +#endif + +static int rtsx_pci_init_hw(struct rtsx_pcr *pcr) +{ + int err; + + pcr->pcie_cap = pci_find_capability(pcr->pci, PCI_CAP_ID_EXP); + rtsx_pci_writel(pcr, RTSX_HCBAR, pcr->host_cmds_addr); + + rtsx_pci_enable_bus_int(pcr); + + /* Power on SSC */ + err = rtsx_pci_write_register(pcr, FPDCTL, SSC_POWER_DOWN, 0); + if (err < 0) + return err; + + /* Wait SSC power stable */ + udelay(200); + + rtsx_pci_disable_aspm(pcr); + if (pcr->ops->optimize_phy) { + err = pcr->ops->optimize_phy(pcr); + if (err < 0) + return err; + } + + rtsx_pci_init_cmd(pcr); + + /* Set mcu_cnt to 7 to ensure data can be sampled properly */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_DIV, 0x07, 0x07); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, HOST_SLEEP_STATE, 0x03, 0x00); + /* Disable card clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, 0x1E, 0); + /* Reset delink mode */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CHANGE_LINK_STATE, 0x0A, 0); + /* Card driving select */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DRIVE_SEL, + 0xFF, pcr->card_drive_sel); + /* Enable SSC Clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL1, + 0xFF, SSC_8X_EN | SSC_SEL_4M); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL2, 0xFF, 0x12); + /* Disable cd_pwr_save */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CHANGE_LINK_STATE, 0x16, 0x10); + /* Clear Link Ready Interrupt */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, IRQSTAT0, + LINK_RDY_INT, LINK_RDY_INT); + /* Enlarge the estimation window of PERST# glitch + * to reduce the chance of invalid card interrupt + */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PERST_GLITCH_WIDTH, 0xFF, 0x80); + /* Update RC oscillator to 400k + * bit[0] F_HIGH: for RC oscillator, Rst_value is 1'b1 + * 1: 2M 0: 400k + */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, RCCTL, 0x01, 0x00); + /* Set interrupt write clear + * bit 1: U_elbi_if_rd_clr_en + * 1: Enable ELBI interrupt[31:22] & [7:0] flag read clear + * 0: ELBI interrupt flag[31:22] & [7:0] only can be write clear + */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, NFTS_TX_CTRL, 0x02, 0); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* Enable clk_request_n to enable clock power management */ + rtsx_pci_write_config_byte(pcr, pcr->pcie_cap + PCI_EXP_LNKCTL + 1, 1); + /* Enter L1 when host tx idle */ + rtsx_pci_write_config_byte(pcr, 0x70F, 0x5B); + + if (pcr->ops->extra_init_hw) { + err = pcr->ops->extra_init_hw(pcr); + if (err < 0) + return err; + } + + /* No CD interrupt if probing driver with card inserted. + * So we need to initialize pcr->card_exist here. + */ + if (pcr->ops->cd_deglitch) + pcr->card_exist = pcr->ops->cd_deglitch(pcr); + else + pcr->card_exist = rtsx_pci_readl(pcr, RTSX_BIPR) & CARD_EXIST; + + return 0; +} + +static int rtsx_pci_init_chip(struct rtsx_pcr *pcr) +{ + int err; + + spin_lock_init(&pcr->lock); + mutex_init(&pcr->pcr_mutex); + + switch (PCI_PID(pcr)) { + default: + case 0x5209: + rts5209_init_params(pcr); + break; + + case 0x5229: + rts5229_init_params(pcr); + break; + + case 0x5289: + rtl8411_init_params(pcr); + break; + + case 0x5227: + rts5227_init_params(pcr); + break; + + case 0x5249: + rts5249_init_params(pcr); + break; + + case 0x524A: + rts524a_init_params(pcr); + break; + + case 0x525A: + rts525a_init_params(pcr); + break; + + case 0x5287: + rtl8411b_init_params(pcr); + break; + + case 0x5286: + rtl8402_init_params(pcr); + break; + } + + pcr_dbg(pcr, "PID: 0x%04x, IC version: 0x%02x\n", + PCI_PID(pcr), pcr->ic_version); + + pcr->slots = kcalloc(pcr->num_slots, sizeof(struct rtsx_slot), + GFP_KERNEL); + if (!pcr->slots) + return -ENOMEM; + + if (pcr->ops->fetch_vendor_settings) + pcr->ops->fetch_vendor_settings(pcr); + + pcr_dbg(pcr, "pcr->aspm_en = 0x%x\n", pcr->aspm_en); + pcr_dbg(pcr, "pcr->sd30_drive_sel_1v8 = 0x%x\n", + pcr->sd30_drive_sel_1v8); + pcr_dbg(pcr, "pcr->sd30_drive_sel_3v3 = 0x%x\n", + pcr->sd30_drive_sel_3v3); + pcr_dbg(pcr, "pcr->card_drive_sel = 0x%x\n", + pcr->card_drive_sel); + pcr_dbg(pcr, "pcr->flags = 0x%x\n", pcr->flags); + + pcr->state = PDEV_STAT_IDLE; + err = rtsx_pci_init_hw(pcr); + if (err < 0) { + kfree(pcr->slots); + return err; + } + + return 0; +} + +static int rtsx_pci_probe(struct pci_dev *pcidev, + const struct pci_device_id *id) +{ + struct rtsx_pcr *pcr; + struct pcr_handle *handle; + u32 base, len; + int ret, i, bar = 0; + + dev_dbg(&(pcidev->dev), + ": Realtek PCI-E Card Reader found at %s [%04x:%04x] (rev %x)\n", + pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device, + (int)pcidev->revision); + + ret = pci_set_dma_mask(pcidev, DMA_BIT_MASK(32)); + if (ret < 0) + return ret; + + ret = pci_enable_device(pcidev); + if (ret) + return ret; + + ret = pci_request_regions(pcidev, DRV_NAME_RTSX_PCI); + if (ret) + goto disable; + + pcr = kzalloc(sizeof(*pcr), GFP_KERNEL); + if (!pcr) { + ret = -ENOMEM; + goto release_pci; + } + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) { + ret = -ENOMEM; + goto free_pcr; + } + handle->pcr = pcr; + + idr_preload(GFP_KERNEL); + spin_lock(&rtsx_pci_lock); + ret = idr_alloc(&rtsx_pci_idr, pcr, 0, 0, GFP_NOWAIT); + if (ret >= 0) + pcr->id = ret; + spin_unlock(&rtsx_pci_lock); + idr_preload_end(); + if (ret < 0) + goto free_handle; + + pcr->pci = pcidev; + dev_set_drvdata(&pcidev->dev, handle); + + if (CHK_PCI_PID(pcr, 0x525A)) + bar = 1; + len = pci_resource_len(pcidev, bar); + base = pci_resource_start(pcidev, bar); + pcr->remap_addr = ioremap_nocache(base, len); + if (!pcr->remap_addr) { + ret = -ENOMEM; + goto free_handle; + } + + pcr->rtsx_resv_buf = dma_alloc_coherent(&(pcidev->dev), + RTSX_RESV_BUF_LEN, &(pcr->rtsx_resv_buf_addr), + GFP_KERNEL); + if (pcr->rtsx_resv_buf == NULL) { + ret = -ENXIO; + goto unmap; + } + pcr->host_cmds_ptr = pcr->rtsx_resv_buf; + pcr->host_cmds_addr = pcr->rtsx_resv_buf_addr; + pcr->host_sg_tbl_ptr = pcr->rtsx_resv_buf + HOST_CMDS_BUF_LEN; + pcr->host_sg_tbl_addr = pcr->rtsx_resv_buf_addr + HOST_CMDS_BUF_LEN; + + pcr->card_inserted = 0; + pcr->card_removed = 0; + INIT_DELAYED_WORK(&pcr->carddet_work, rtsx_pci_card_detect); + INIT_DELAYED_WORK(&pcr->idle_work, rtsx_pci_idle_work); + + pcr->msi_en = msi_en; + if (pcr->msi_en) { + ret = pci_enable_msi(pcidev); + if (ret) + pcr->msi_en = false; + } + + ret = rtsx_pci_acquire_irq(pcr); + if (ret < 0) + goto disable_msi; + + pci_set_master(pcidev); + synchronize_irq(pcr->irq); + + ret = rtsx_pci_init_chip(pcr); + if (ret < 0) + goto disable_irq; + + for (i = 0; i < ARRAY_SIZE(rtsx_pcr_cells); i++) { + rtsx_pcr_cells[i].platform_data = handle; + rtsx_pcr_cells[i].pdata_size = sizeof(*handle); + } + ret = mfd_add_devices(&pcidev->dev, pcr->id, rtsx_pcr_cells, + ARRAY_SIZE(rtsx_pcr_cells), NULL, 0, NULL); + if (ret < 0) + goto disable_irq; + + schedule_delayed_work(&pcr->idle_work, msecs_to_jiffies(200)); + + return 0; + +disable_irq: + free_irq(pcr->irq, (void *)pcr); +disable_msi: + if (pcr->msi_en) + pci_disable_msi(pcr->pci); + dma_free_coherent(&(pcr->pci->dev), RTSX_RESV_BUF_LEN, + pcr->rtsx_resv_buf, pcr->rtsx_resv_buf_addr); +unmap: + iounmap(pcr->remap_addr); +free_handle: + kfree(handle); +free_pcr: + kfree(pcr); +release_pci: + pci_release_regions(pcidev); +disable: + pci_disable_device(pcidev); + + return ret; +} + +static void rtsx_pci_remove(struct pci_dev *pcidev) +{ + struct pcr_handle *handle = pci_get_drvdata(pcidev); + struct rtsx_pcr *pcr = handle->pcr; + + pcr->remove_pci = true; + + /* Disable interrupts at the pcr level */ + spin_lock_irq(&pcr->lock); + rtsx_pci_writel(pcr, RTSX_BIER, 0); + pcr->bier = 0; + spin_unlock_irq(&pcr->lock); + + cancel_delayed_work_sync(&pcr->carddet_work); + cancel_delayed_work_sync(&pcr->idle_work); + + mfd_remove_devices(&pcidev->dev); + + dma_free_coherent(&(pcr->pci->dev), RTSX_RESV_BUF_LEN, + pcr->rtsx_resv_buf, pcr->rtsx_resv_buf_addr); + free_irq(pcr->irq, (void *)pcr); + if (pcr->msi_en) + pci_disable_msi(pcr->pci); + iounmap(pcr->remap_addr); + + pci_release_regions(pcidev); + pci_disable_device(pcidev); + + spin_lock(&rtsx_pci_lock); + idr_remove(&rtsx_pci_idr, pcr->id); + spin_unlock(&rtsx_pci_lock); + + kfree(pcr->slots); + kfree(pcr); + kfree(handle); + + dev_dbg(&(pcidev->dev), + ": Realtek PCI-E Card Reader at %s [%04x:%04x] has been removed\n", + pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device); +} + +#ifdef CONFIG_PM + +static int rtsx_pci_suspend(struct pci_dev *pcidev, pm_message_t state) +{ + struct pcr_handle *handle; + struct rtsx_pcr *pcr; + + dev_dbg(&(pcidev->dev), "--> %s\n", __func__); + + handle = pci_get_drvdata(pcidev); + pcr = handle->pcr; + + cancel_delayed_work(&pcr->carddet_work); + cancel_delayed_work(&pcr->idle_work); + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_power_off(pcr, HOST_ENTER_S3); + + pci_save_state(pcidev); + pci_enable_wake(pcidev, pci_choose_state(pcidev, state), 0); + pci_disable_device(pcidev); + pci_set_power_state(pcidev, pci_choose_state(pcidev, state)); + + mutex_unlock(&pcr->pcr_mutex); + return 0; +} + +static int rtsx_pci_resume(struct pci_dev *pcidev) +{ + struct pcr_handle *handle; + struct rtsx_pcr *pcr; + int ret = 0; + + dev_dbg(&(pcidev->dev), "--> %s\n", __func__); + + handle = pci_get_drvdata(pcidev); + pcr = handle->pcr; + + mutex_lock(&pcr->pcr_mutex); + + pci_set_power_state(pcidev, PCI_D0); + pci_restore_state(pcidev); + ret = pci_enable_device(pcidev); + if (ret) + goto out; + pci_set_master(pcidev); + + ret = rtsx_pci_write_register(pcr, HOST_SLEEP_STATE, 0x03, 0x00); + if (ret) + goto out; + + ret = rtsx_pci_init_hw(pcr); + if (ret) + goto out; + + schedule_delayed_work(&pcr->idle_work, msecs_to_jiffies(200)); + +out: + mutex_unlock(&pcr->pcr_mutex); + return ret; +} + +static void rtsx_pci_shutdown(struct pci_dev *pcidev) +{ + struct pcr_handle *handle; + struct rtsx_pcr *pcr; + + dev_dbg(&(pcidev->dev), "--> %s\n", __func__); + + handle = pci_get_drvdata(pcidev); + pcr = handle->pcr; + rtsx_pci_power_off(pcr, HOST_ENTER_S1); + + pci_disable_device(pcidev); +} + +#else /* CONFIG_PM */ + +#define rtsx_pci_suspend NULL +#define rtsx_pci_resume NULL +#define rtsx_pci_shutdown NULL + +#endif /* CONFIG_PM */ + +static struct pci_driver rtsx_pci_driver = { + .name = DRV_NAME_RTSX_PCI, + .id_table = rtsx_pci_ids, + .probe = rtsx_pci_probe, + .remove = rtsx_pci_remove, + .suspend = rtsx_pci_suspend, + .resume = rtsx_pci_resume, + .shutdown = rtsx_pci_shutdown, +}; +module_pci_driver(rtsx_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>"); +MODULE_DESCRIPTION("Realtek PCI-E Card Reader Driver"); diff --git a/kernel/drivers/mfd/rtsx_pcr.h b/kernel/drivers/mfd/rtsx_pcr.h new file mode 100644 index 000000000..ce4884257 --- /dev/null +++ b/kernel/drivers/mfd/rtsx_pcr.h @@ -0,0 +1,86 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, or (at your option) any + * later version. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + */ + +#ifndef __RTSX_PCR_H +#define __RTSX_PCR_H + +#include <linux/mfd/rtsx_pci.h> + +#define MIN_DIV_N_PCR 80 +#define MAX_DIV_N_PCR 208 + +#define RTS524A_PME_FORCE_CTL 0xFF78 +#define RTS524A_PM_CTRL3 0xFF7E + +int __rtsx_pci_write_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 val); +int __rtsx_pci_read_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 *val); + +void rts5209_init_params(struct rtsx_pcr *pcr); +void rts5229_init_params(struct rtsx_pcr *pcr); +void rtl8411_init_params(struct rtsx_pcr *pcr); +void rtl8402_init_params(struct rtsx_pcr *pcr); +void rts5227_init_params(struct rtsx_pcr *pcr); +void rts5249_init_params(struct rtsx_pcr *pcr); +void rts524a_init_params(struct rtsx_pcr *pcr); +void rts525a_init_params(struct rtsx_pcr *pcr); +void rtl8411b_init_params(struct rtsx_pcr *pcr); + +static inline u8 map_sd_drive(int idx) +{ + u8 sd_drive[4] = { + 0x01, /* Type D */ + 0x02, /* Type C */ + 0x05, /* Type A */ + 0x03 /* Type B */ + }; + + return sd_drive[idx]; +} + +#define rtsx_vendor_setting_valid(reg) (!((reg) & 0x1000000)) +#define rts5209_vendor_setting1_valid(reg) (!((reg) & 0x80)) +#define rts5209_vendor_setting2_valid(reg) ((reg) & 0x80) + +#define rtsx_reg_to_aspm(reg) (((reg) >> 28) & 0x03) +#define rtsx_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 26) & 0x03) +#define rtsx_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 5) & 0x03) +#define rtsx_reg_to_card_drive_sel(reg) ((((reg) >> 25) & 0x01) << 6) +#define rtsx_reg_check_reverse_socket(reg) ((reg) & 0x4000) +#define rts5209_reg_to_aspm(reg) (((reg) >> 5) & 0x03) +#define rts5209_reg_check_ms_pmos(reg) (!((reg) & 0x08)) +#define rts5209_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 3) & 0x07) +#define rts5209_reg_to_sd30_drive_sel_3v3(reg) ((reg) & 0x07) +#define rts5209_reg_to_card_drive_sel(reg) ((reg) >> 8) +#define rtl8411_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 5) & 0x07) +#define rtl8411b_reg_to_sd30_drive_sel_3v3(reg) ((reg) & 0x03) + +#define set_pull_ctrl_tables(pcr, __device) \ +do { \ + pcr->sd_pull_ctl_enable_tbl = __device##_sd_pull_ctl_enable_tbl; \ + pcr->sd_pull_ctl_disable_tbl = __device##_sd_pull_ctl_disable_tbl; \ + pcr->ms_pull_ctl_enable_tbl = __device##_ms_pull_ctl_enable_tbl; \ + pcr->ms_pull_ctl_disable_tbl = __device##_ms_pull_ctl_disable_tbl; \ +} while (0) + +/* generic operations */ +int rtsx_gops_pm_reset(struct rtsx_pcr *pcr); + +#endif diff --git a/kernel/drivers/mfd/rtsx_usb.c b/kernel/drivers/mfd/rtsx_usb.c new file mode 100644 index 000000000..dbd907d71 --- /dev/null +++ b/kernel/drivers/mfd/rtsx_usb.c @@ -0,0 +1,791 @@ +/* Driver for Realtek USB card reader + * + * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * Author: + * Roger Tseng <rogerable@realtek.com> + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/usb.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rtsx_usb.h> + +static int polling_pipe = 1; +module_param(polling_pipe, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(polling_pipe, "polling pipe (0: ctl, 1: bulk)"); + +static const struct mfd_cell rtsx_usb_cells[] = { + [RTSX_USB_SD_CARD] = { + .name = "rtsx_usb_sdmmc", + .pdata_size = 0, + }, + [RTSX_USB_MS_CARD] = { + .name = "rtsx_usb_ms", + .pdata_size = 0, + }, +}; + +static void rtsx_usb_sg_timed_out(unsigned long data) +{ + struct rtsx_ucr *ucr = (struct rtsx_ucr *)data; + + dev_dbg(&ucr->pusb_intf->dev, "%s: sg transfer timed out", __func__); + usb_sg_cancel(&ucr->current_sg); + + /* we know the cancellation is caused by time-out */ + ucr->current_sg.status = -ETIMEDOUT; +} + +static int rtsx_usb_bulk_transfer_sglist(struct rtsx_ucr *ucr, + unsigned int pipe, struct scatterlist *sg, int num_sg, + unsigned int length, unsigned int *act_len, int timeout) +{ + int ret; + + dev_dbg(&ucr->pusb_intf->dev, "%s: xfer %u bytes, %d entries\n", + __func__, length, num_sg); + ret = usb_sg_init(&ucr->current_sg, ucr->pusb_dev, pipe, 0, + sg, num_sg, length, GFP_NOIO); + if (ret) + return ret; + + ucr->sg_timer.expires = jiffies + msecs_to_jiffies(timeout); + add_timer(&ucr->sg_timer); + usb_sg_wait(&ucr->current_sg); + del_timer_sync(&ucr->sg_timer); + + if (act_len) + *act_len = ucr->current_sg.bytes; + + return ucr->current_sg.status; +} + +int rtsx_usb_transfer_data(struct rtsx_ucr *ucr, unsigned int pipe, + void *buf, unsigned int len, int num_sg, + unsigned int *act_len, int timeout) +{ + if (timeout < 600) + timeout = 600; + + if (num_sg) + return rtsx_usb_bulk_transfer_sglist(ucr, pipe, + (struct scatterlist *)buf, num_sg, len, act_len, + timeout); + else + return usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len, + timeout); +} +EXPORT_SYMBOL_GPL(rtsx_usb_transfer_data); + +static inline void rtsx_usb_seq_cmd_hdr(struct rtsx_ucr *ucr, + u16 addr, u16 len, u8 seq_type) +{ + rtsx_usb_cmd_hdr_tag(ucr); + + ucr->cmd_buf[PACKET_TYPE] = seq_type; + ucr->cmd_buf[5] = (u8)(len >> 8); + ucr->cmd_buf[6] = (u8)len; + ucr->cmd_buf[8] = (u8)(addr >> 8); + ucr->cmd_buf[9] = (u8)addr; + + if (seq_type == SEQ_WRITE) + ucr->cmd_buf[STAGE_FLAG] = 0; + else + ucr->cmd_buf[STAGE_FLAG] = STAGE_R; +} + +static int rtsx_usb_seq_write_register(struct rtsx_ucr *ucr, + u16 addr, u16 len, u8 *data) +{ + u16 cmd_len = ALIGN(SEQ_WRITE_DATA_OFFSET + len, 4); + + if (!data) + return -EINVAL; + + if (cmd_len > IOBUF_SIZE) + return -EINVAL; + + rtsx_usb_seq_cmd_hdr(ucr, addr, len, SEQ_WRITE); + memcpy(ucr->cmd_buf + SEQ_WRITE_DATA_OFFSET, data, len); + + return rtsx_usb_transfer_data(ucr, + usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT), + ucr->cmd_buf, cmd_len, 0, NULL, 100); +} + +static int rtsx_usb_seq_read_register(struct rtsx_ucr *ucr, + u16 addr, u16 len, u8 *data) +{ + int i, ret; + u16 rsp_len = round_down(len, 4); + u16 res_len = len - rsp_len; + + if (!data) + return -EINVAL; + + /* 4-byte aligned part */ + if (rsp_len) { + rtsx_usb_seq_cmd_hdr(ucr, addr, len, SEQ_READ); + ret = rtsx_usb_transfer_data(ucr, + usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT), + ucr->cmd_buf, 12, 0, NULL, 100); + if (ret) + return ret; + + ret = rtsx_usb_transfer_data(ucr, + usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN), + data, rsp_len, 0, NULL, 100); + if (ret) + return ret; + } + + /* unaligned part */ + for (i = 0; i < res_len; i++) { + ret = rtsx_usb_read_register(ucr, addr + rsp_len + i, + data + rsp_len + i); + if (ret) + return ret; + } + + return 0; +} + +int rtsx_usb_read_ppbuf(struct rtsx_ucr *ucr, u8 *buf, int buf_len) +{ + return rtsx_usb_seq_read_register(ucr, PPBUF_BASE2, (u16)buf_len, buf); +} +EXPORT_SYMBOL_GPL(rtsx_usb_read_ppbuf); + +int rtsx_usb_write_ppbuf(struct rtsx_ucr *ucr, u8 *buf, int buf_len) +{ + return rtsx_usb_seq_write_register(ucr, PPBUF_BASE2, (u16)buf_len, buf); +} +EXPORT_SYMBOL_GPL(rtsx_usb_write_ppbuf); + +int rtsx_usb_ep0_write_register(struct rtsx_ucr *ucr, u16 addr, + u8 mask, u8 data) +{ + u16 value, index; + + addr |= EP0_WRITE_REG_CMD << EP0_OP_SHIFT; + value = swab16(addr); + index = mask | data << 8; + + return usb_control_msg(ucr->pusb_dev, + usb_sndctrlpipe(ucr->pusb_dev, 0), RTSX_USB_REQ_REG_OP, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, NULL, 0, 100); +} +EXPORT_SYMBOL_GPL(rtsx_usb_ep0_write_register); + +int rtsx_usb_ep0_read_register(struct rtsx_ucr *ucr, u16 addr, u8 *data) +{ + u16 value; + u8 *buf; + int ret; + + if (!data) + return -EINVAL; + + buf = kzalloc(sizeof(u8), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + addr |= EP0_READ_REG_CMD << EP0_OP_SHIFT; + value = swab16(addr); + + ret = usb_control_msg(ucr->pusb_dev, + usb_rcvctrlpipe(ucr->pusb_dev, 0), RTSX_USB_REQ_REG_OP, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, buf, 1, 100); + *data = *buf; + + kfree(buf); + return ret; +} +EXPORT_SYMBOL_GPL(rtsx_usb_ep0_read_register); + +void rtsx_usb_add_cmd(struct rtsx_ucr *ucr, u8 cmd_type, u16 reg_addr, + u8 mask, u8 data) +{ + int i; + + if (ucr->cmd_idx < (IOBUF_SIZE - CMD_OFFSET) / 4) { + i = CMD_OFFSET + ucr->cmd_idx * 4; + + ucr->cmd_buf[i++] = ((cmd_type & 0x03) << 6) | + (u8)((reg_addr >> 8) & 0x3F); + ucr->cmd_buf[i++] = (u8)reg_addr; + ucr->cmd_buf[i++] = mask; + ucr->cmd_buf[i++] = data; + + ucr->cmd_idx++; + } +} +EXPORT_SYMBOL_GPL(rtsx_usb_add_cmd); + +int rtsx_usb_send_cmd(struct rtsx_ucr *ucr, u8 flag, int timeout) +{ + int ret; + + ucr->cmd_buf[CNT_H] = (u8)(ucr->cmd_idx >> 8); + ucr->cmd_buf[CNT_L] = (u8)(ucr->cmd_idx); + ucr->cmd_buf[STAGE_FLAG] = flag; + + ret = rtsx_usb_transfer_data(ucr, + usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT), + ucr->cmd_buf, ucr->cmd_idx * 4 + CMD_OFFSET, + 0, NULL, timeout); + if (ret) { + rtsx_usb_clear_fsm_err(ucr); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_usb_send_cmd); + +int rtsx_usb_get_rsp(struct rtsx_ucr *ucr, int rsp_len, int timeout) +{ + if (rsp_len <= 0) + return -EINVAL; + + rsp_len = ALIGN(rsp_len, 4); + + return rtsx_usb_transfer_data(ucr, + usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN), + ucr->rsp_buf, rsp_len, 0, NULL, timeout); +} +EXPORT_SYMBOL_GPL(rtsx_usb_get_rsp); + +static int rtsx_usb_get_status_with_bulk(struct rtsx_ucr *ucr, u16 *status) +{ + int ret; + + rtsx_usb_init_cmd(ucr); + rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_EXIST, 0x00, 0x00); + rtsx_usb_add_cmd(ucr, READ_REG_CMD, OCPSTAT, 0x00, 0x00); + ret = rtsx_usb_send_cmd(ucr, MODE_CR, 100); + if (ret) + return ret; + + ret = rtsx_usb_get_rsp(ucr, 2, 100); + if (ret) + return ret; + + *status = ((ucr->rsp_buf[0] >> 2) & 0x0f) | + ((ucr->rsp_buf[1] & 0x03) << 4); + + return 0; +} + +int rtsx_usb_get_card_status(struct rtsx_ucr *ucr, u16 *status) +{ + int ret; + u16 *buf; + + if (!status) + return -EINVAL; + + if (polling_pipe == 0) { + buf = kzalloc(sizeof(u16), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usb_control_msg(ucr->pusb_dev, + usb_rcvctrlpipe(ucr->pusb_dev, 0), + RTSX_USB_REQ_POLL, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, buf, 2, 100); + *status = *buf; + + kfree(buf); + } else { + ret = rtsx_usb_get_status_with_bulk(ucr, status); + } + + /* usb_control_msg may return positive when success */ + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_usb_get_card_status); + +static int rtsx_usb_write_phy_register(struct rtsx_ucr *ucr, u8 addr, u8 val) +{ + dev_dbg(&ucr->pusb_intf->dev, "Write 0x%x to phy register 0x%x\n", + val, addr); + + rtsx_usb_init_cmd(ucr); + + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VSTAIN, 0xFF, val); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VCONTROL, 0xFF, addr & 0x0F); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x01); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VCONTROL, + 0xFF, (addr >> 4) & 0x0F); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x01); + + return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} + +int rtsx_usb_write_register(struct rtsx_ucr *ucr, u16 addr, u8 mask, u8 data) +{ + rtsx_usb_init_cmd(ucr); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, addr, mask, data); + return rtsx_usb_send_cmd(ucr, MODE_C, 100); +} +EXPORT_SYMBOL_GPL(rtsx_usb_write_register); + +int rtsx_usb_read_register(struct rtsx_ucr *ucr, u16 addr, u8 *data) +{ + int ret; + + if (data != NULL) + *data = 0; + + rtsx_usb_init_cmd(ucr); + rtsx_usb_add_cmd(ucr, READ_REG_CMD, addr, 0, 0); + ret = rtsx_usb_send_cmd(ucr, MODE_CR, 100); + if (ret) + return ret; + + ret = rtsx_usb_get_rsp(ucr, 1, 100); + if (ret) + return ret; + + if (data != NULL) + *data = ucr->rsp_buf[0]; + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_usb_read_register); + +static inline u8 double_ssc_depth(u8 depth) +{ + return (depth > 1) ? (depth - 1) : depth; +} + +static u8 revise_ssc_depth(u8 ssc_depth, u8 div) +{ + if (div > CLK_DIV_1) { + if (ssc_depth > div - 1) + ssc_depth -= (div - 1); + else + ssc_depth = SSC_DEPTH_2M; + } + + return ssc_depth; +} + +int rtsx_usb_switch_clock(struct rtsx_ucr *ucr, unsigned int card_clock, + u8 ssc_depth, bool initial_mode, bool double_clk, bool vpclk) +{ + int ret; + u8 n, clk_divider, mcu_cnt, div; + + if (!card_clock) { + ucr->cur_clk = 0; + return 0; + } + + if (initial_mode) { + /* We use 250k(around) here, in initial stage */ + clk_divider = SD_CLK_DIVIDE_128; + card_clock = 30000000; + } else { + clk_divider = SD_CLK_DIVIDE_0; + } + + ret = rtsx_usb_write_register(ucr, SD_CFG1, + SD_CLK_DIVIDE_MASK, clk_divider); + if (ret < 0) + return ret; + + card_clock /= 1000000; + dev_dbg(&ucr->pusb_intf->dev, + "Switch card clock to %dMHz\n", card_clock); + + if (!initial_mode && double_clk) + card_clock *= 2; + dev_dbg(&ucr->pusb_intf->dev, + "Internal SSC clock: %dMHz (cur_clk = %d)\n", + card_clock, ucr->cur_clk); + + if (card_clock == ucr->cur_clk) + return 0; + + /* Converting clock value into internal settings: n and div */ + n = card_clock - 2; + if ((card_clock <= 2) || (n > MAX_DIV_N)) + return -EINVAL; + + mcu_cnt = 60/card_clock + 3; + if (mcu_cnt > 15) + mcu_cnt = 15; + + /* Make sure that the SSC clock div_n is not less than MIN_DIV_N */ + + div = CLK_DIV_1; + while (n < MIN_DIV_N && div < CLK_DIV_4) { + n = (n + 2) * 2 - 2; + div++; + } + dev_dbg(&ucr->pusb_intf->dev, "n = %d, div = %d\n", n, div); + + if (double_clk) + ssc_depth = double_ssc_depth(ssc_depth); + + ssc_depth = revise_ssc_depth(ssc_depth, div); + dev_dbg(&ucr->pusb_intf->dev, "ssc_depth = %d\n", ssc_depth); + + rtsx_usb_init_cmd(ucr); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, CLK_CHANGE, CLK_CHANGE); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, + 0x3F, (div << 4) | mcu_cnt); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, 0); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_CTL2, + SSC_DEPTH_MASK, ssc_depth); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_DIV_N_0, 0xFF, n); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, SSC_RSTB); + if (vpclk) { + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, 0); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, PHASE_NOT_RESET); + } + + ret = rtsx_usb_send_cmd(ucr, MODE_C, 2000); + if (ret < 0) + return ret; + + ret = rtsx_usb_write_register(ucr, SSC_CTL1, 0xff, + SSC_RSTB | SSC_8X_EN | SSC_SEL_4M); + if (ret < 0) + return ret; + + /* Wait SSC clock stable */ + usleep_range(100, 1000); + + ret = rtsx_usb_write_register(ucr, CLK_DIV, CLK_CHANGE, 0); + if (ret < 0) + return ret; + + ucr->cur_clk = card_clock; + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_usb_switch_clock); + +int rtsx_usb_card_exclusive_check(struct rtsx_ucr *ucr, int card) +{ + int ret; + u16 val; + u16 cd_mask[] = { + [RTSX_USB_SD_CARD] = (CD_MASK & ~SD_CD), + [RTSX_USB_MS_CARD] = (CD_MASK & ~MS_CD) + }; + + ret = rtsx_usb_get_card_status(ucr, &val); + /* + * If get status fails, return 0 (ok) for the exclusive check + * and let the flow fail at somewhere else. + */ + if (ret) + return 0; + + if (val & cd_mask[card]) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_usb_card_exclusive_check); + +static int rtsx_usb_reset_chip(struct rtsx_ucr *ucr) +{ + int ret; + u8 val; + + rtsx_usb_init_cmd(ucr); + + if (CHECK_PKG(ucr, LQFP48)) { + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL, + LDO3318_PWR_MASK, LDO_SUSPEND); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL, + FORCE_LDO_POWERB, FORCE_LDO_POWERB); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, + 0x30, 0x10); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, + 0x03, 0x01); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, + 0x0C, 0x04); + } + + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SYS_DUMMY0, NYET_MSAK, NYET_EN); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CD_DEGLITCH_WIDTH, 0xFF, 0x08); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, + CD_DEGLITCH_EN, XD_CD_DEGLITCH_EN, 0x0); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD30_DRIVE_SEL, + SD30_DRIVE_MASK, DRIVER_TYPE_D); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, + CARD_DRIVE_SEL, SD20_DRIVE_MASK, 0x0); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG, 0xE0, 0x0); + + if (ucr->is_rts5179) + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, + CARD_PULL_CTL5, 0x03, 0x01); + + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DMA1_CTL, + EXTEND_DMA1_ASYNC_SIGNAL, EXTEND_DMA1_ASYNC_SIGNAL); + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_INT_PEND, + XD_INT | MS_INT | SD_INT, + XD_INT | MS_INT | SD_INT); + + ret = rtsx_usb_send_cmd(ucr, MODE_C, 100); + if (ret) + return ret; + + /* config non-crystal mode */ + rtsx_usb_read_register(ucr, CFG_MODE, &val); + if ((val & XTAL_FREE) || ((val & CLK_MODE_MASK) == CLK_MODE_NON_XTAL)) { + ret = rtsx_usb_write_phy_register(ucr, 0xC2, 0x7C); + if (ret) + return ret; + } + + return 0; +} + +static int rtsx_usb_init_chip(struct rtsx_ucr *ucr) +{ + int ret; + u8 val; + + rtsx_usb_clear_fsm_err(ucr); + + /* power on SSC */ + ret = rtsx_usb_write_register(ucr, + FPDCTL, SSC_POWER_MASK, SSC_POWER_ON); + if (ret) + return ret; + + usleep_range(100, 1000); + ret = rtsx_usb_write_register(ucr, CLK_DIV, CLK_CHANGE, 0x00); + if (ret) + return ret; + + /* determine IC version */ + ret = rtsx_usb_read_register(ucr, HW_VERSION, &val); + if (ret) + return ret; + + ucr->ic_version = val & HW_VER_MASK; + + /* determine package */ + ret = rtsx_usb_read_register(ucr, CARD_SHARE_MODE, &val); + if (ret) + return ret; + + if (val & CARD_SHARE_LQFP_SEL) { + ucr->package = LQFP48; + dev_dbg(&ucr->pusb_intf->dev, "Package: LQFP48\n"); + } else { + ucr->package = QFN24; + dev_dbg(&ucr->pusb_intf->dev, "Package: QFN24\n"); + } + + /* determine IC variations */ + rtsx_usb_read_register(ucr, CFG_MODE_1, &val); + if (val & RTS5179) { + ucr->is_rts5179 = true; + dev_dbg(&ucr->pusb_intf->dev, "Device is rts5179\n"); + } else { + ucr->is_rts5179 = false; + } + + return rtsx_usb_reset_chip(ucr); +} + +static int rtsx_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct rtsx_ucr *ucr; + int ret; + + dev_dbg(&intf->dev, + ": Realtek USB Card Reader found at bus %03d address %03d\n", + usb_dev->bus->busnum, usb_dev->devnum); + + ucr = devm_kzalloc(&intf->dev, sizeof(*ucr), GFP_KERNEL); + if (!ucr) + return -ENOMEM; + + ucr->pusb_dev = usb_dev; + + ucr->iobuf = usb_alloc_coherent(ucr->pusb_dev, IOBUF_SIZE, + GFP_KERNEL, &ucr->iobuf_dma); + if (!ucr->iobuf) + return -ENOMEM; + + usb_set_intfdata(intf, ucr); + + ucr->vendor_id = id->idVendor; + ucr->product_id = id->idProduct; + ucr->cmd_buf = ucr->rsp_buf = ucr->iobuf; + + mutex_init(&ucr->dev_mutex); + + ucr->pusb_intf = intf; + + /* initialize */ + ret = rtsx_usb_init_chip(ucr); + if (ret) + goto out_init_fail; + + /* initialize USB SG transfer timer */ + setup_timer(&ucr->sg_timer, rtsx_usb_sg_timed_out, (unsigned long) ucr); + + ret = mfd_add_hotplug_devices(&intf->dev, rtsx_usb_cells, + ARRAY_SIZE(rtsx_usb_cells)); + if (ret) + goto out_init_fail; + +#ifdef CONFIG_PM + intf->needs_remote_wakeup = 1; + usb_enable_autosuspend(usb_dev); +#endif + + return 0; + +out_init_fail: + usb_free_coherent(ucr->pusb_dev, IOBUF_SIZE, ucr->iobuf, + ucr->iobuf_dma); + return ret; +} + +static void rtsx_usb_disconnect(struct usb_interface *intf) +{ + struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s called\n", __func__); + + mfd_remove_devices(&intf->dev); + + usb_set_intfdata(ucr->pusb_intf, NULL); + usb_free_coherent(ucr->pusb_dev, IOBUF_SIZE, ucr->iobuf, + ucr->iobuf_dma); +} + +#ifdef CONFIG_PM +static int rtsx_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct rtsx_ucr *ucr = + (struct rtsx_ucr *)usb_get_intfdata(intf); + u16 val = 0; + + dev_dbg(&intf->dev, "%s called with pm message 0x%04x\n", + __func__, message.event); + + if (PMSG_IS_AUTO(message)) { + if (mutex_trylock(&ucr->dev_mutex)) { + rtsx_usb_get_card_status(ucr, &val); + mutex_unlock(&ucr->dev_mutex); + + /* Defer the autosuspend if card exists */ + if (val & (SD_CD | MS_CD)) + return -EAGAIN; + } else { + /* There is an ongoing operation*/ + return -EAGAIN; + } + } + + return 0; +} + +static int rtsx_usb_resume(struct usb_interface *intf) +{ + return 0; +} + +static int rtsx_usb_reset_resume(struct usb_interface *intf) +{ + struct rtsx_ucr *ucr = + (struct rtsx_ucr *)usb_get_intfdata(intf); + + rtsx_usb_reset_chip(ucr); + return 0; +} + +#else /* CONFIG_PM */ + +#define rtsx_usb_suspend NULL +#define rtsx_usb_resume NULL +#define rtsx_usb_reset_resume NULL + +#endif /* CONFIG_PM */ + + +static int rtsx_usb_pre_reset(struct usb_interface *intf) +{ + struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); + + mutex_lock(&ucr->dev_mutex); + return 0; +} + +static int rtsx_usb_post_reset(struct usb_interface *intf) +{ + struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); + + mutex_unlock(&ucr->dev_mutex); + return 0; +} + +static struct usb_device_id rtsx_usb_usb_ids[] = { + { USB_DEVICE(0x0BDA, 0x0129) }, + { USB_DEVICE(0x0BDA, 0x0139) }, + { USB_DEVICE(0x0BDA, 0x0140) }, + { } +}; +MODULE_DEVICE_TABLE(usb, rtsx_usb_usb_ids); + +static struct usb_driver rtsx_usb_driver = { + .name = "rtsx_usb", + .probe = rtsx_usb_probe, + .disconnect = rtsx_usb_disconnect, + .suspend = rtsx_usb_suspend, + .resume = rtsx_usb_resume, + .reset_resume = rtsx_usb_reset_resume, + .pre_reset = rtsx_usb_pre_reset, + .post_reset = rtsx_usb_post_reset, + .id_table = rtsx_usb_usb_ids, + .supports_autosuspend = 1, + .soft_unbind = 1, +}; + +module_usb_driver(rtsx_usb_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Roger Tseng <rogerable@realtek.com>"); +MODULE_DESCRIPTION("Realtek USB Card Reader Driver"); diff --git a/kernel/drivers/mfd/sec-core.c b/kernel/drivers/mfd/sec-core.c new file mode 100644 index 000000000..4a69afb42 --- /dev/null +++ b/kernel/drivers/mfd/sec-core.c @@ -0,0 +1,513 @@ +/* + * sec-core.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * http://www.samsung.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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/samsung/core.h> +#include <linux/mfd/samsung/irq.h> +#include <linux/mfd/samsung/s2mpa01.h> +#include <linux/mfd/samsung/s2mps11.h> +#include <linux/mfd/samsung/s2mps13.h> +#include <linux/mfd/samsung/s2mps14.h> +#include <linux/mfd/samsung/s2mpu02.h> +#include <linux/mfd/samsung/s5m8763.h> +#include <linux/mfd/samsung/s5m8767.h> +#include <linux/regmap.h> + +static const struct mfd_cell s5m8751_devs[] = { + { + .name = "s5m8751-pmic", + }, { + .name = "s5m-charger", + }, { + .name = "s5m8751-codec", + }, +}; + +static const struct mfd_cell s5m8763_devs[] = { + { + .name = "s5m8763-pmic", + }, { + .name = "s5m-rtc", + }, { + .name = "s5m-charger", + }, +}; + +static const struct mfd_cell s5m8767_devs[] = { + { + .name = "s5m8767-pmic", + }, { + .name = "s5m-rtc", + }, { + .name = "s5m8767-clk", + .of_compatible = "samsung,s5m8767-clk", + } +}; + +static const struct mfd_cell s2mps11_devs[] = { + { + .name = "s2mps11-pmic", + }, { + .name = "s2mps14-rtc", + }, { + .name = "s2mps11-clk", + .of_compatible = "samsung,s2mps11-clk", + } +}; + +static const struct mfd_cell s2mps13_devs[] = { + { .name = "s2mps13-pmic", }, + { .name = "s2mps13-rtc", }, + { + .name = "s2mps13-clk", + .of_compatible = "samsung,s2mps13-clk", + }, +}; + +static const struct mfd_cell s2mps14_devs[] = { + { + .name = "s2mps14-pmic", + }, { + .name = "s2mps14-rtc", + }, { + .name = "s2mps14-clk", + .of_compatible = "samsung,s2mps14-clk", + } +}; + +static const struct mfd_cell s2mpa01_devs[] = { + { + .name = "s2mpa01-pmic", + }, +}; + +static const struct mfd_cell s2mpu02_devs[] = { + { .name = "s2mpu02-pmic", }, + { .name = "s2mpu02-rtc", }, + { + .name = "s2mpu02-clk", + .of_compatible = "samsung,s2mpu02-clk", + } +}; + +#ifdef CONFIG_OF +static const struct of_device_id sec_dt_match[] = { + { .compatible = "samsung,s5m8767-pmic", + .data = (void *)S5M8767X, + }, { + .compatible = "samsung,s2mps11-pmic", + .data = (void *)S2MPS11X, + }, { + .compatible = "samsung,s2mps13-pmic", + .data = (void *)S2MPS13X, + }, { + .compatible = "samsung,s2mps14-pmic", + .data = (void *)S2MPS14X, + }, { + .compatible = "samsung,s2mpa01-pmic", + .data = (void *)S2MPA01, + }, { + .compatible = "samsung,s2mpu02-pmic", + .data = (void *)S2MPU02, + }, { + /* Sentinel */ + }, +}; +#endif + +static bool s2mpa01_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case S2MPA01_REG_INT1M: + case S2MPA01_REG_INT2M: + case S2MPA01_REG_INT3M: + return false; + default: + return true; + } +} + +static bool s2mps11_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case S2MPS11_REG_INT1M: + case S2MPS11_REG_INT2M: + case S2MPS11_REG_INT3M: + return false; + default: + return true; + } +} + +static bool s2mpu02_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case S2MPU02_REG_INT1M: + case S2MPU02_REG_INT2M: + case S2MPU02_REG_INT3M: + return false; + default: + return true; + } +} + +static bool s5m8763_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case S5M8763_REG_IRQM1: + case S5M8763_REG_IRQM2: + case S5M8763_REG_IRQM3: + case S5M8763_REG_IRQM4: + return false; + default: + return true; + } +} + +static const struct regmap_config sec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config s2mpa01_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S2MPA01_REG_LDO_OVCB4, + .volatile_reg = s2mpa01_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config s2mps11_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S2MPS11_REG_L38CTRL, + .volatile_reg = s2mps11_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config s2mps13_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S2MPS13_REG_LDODSCH5, + .volatile_reg = s2mps11_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config s2mps14_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S2MPS14_REG_LDODSCH3, + .volatile_reg = s2mps11_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config s2mpu02_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S2MPU02_REG_DVSDATA, + .volatile_reg = s2mpu02_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config s5m8763_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S5M8763_REG_LBCNFG2, + .volatile_reg = s5m8763_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config s5m8767_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = S5M8767_REG_LDO28CTRL, + .volatile_reg = s2mps11_volatile, + .cache_type = REGCACHE_FLAT, +}; + +#ifdef CONFIG_OF +/* + * Only the common platform data elements for s5m8767 are parsed here from the + * device tree. Other sub-modules of s5m8767 such as pmic, rtc , charger and + * others have to parse their own platform data elements from device tree. + * + * The s5m8767 platform data structure is instantiated here and the drivers for + * the sub-modules need not instantiate another instance while parsing their + * platform data. + */ +static struct sec_platform_data *sec_pmic_i2c_parse_dt_pdata( + struct device *dev) +{ + struct sec_platform_data *pd; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + /* + * ToDo: the 'wakeup' member in the platform data is more of a linux + * specfic information. Hence, there is no binding for that yet and + * not parsed here. + */ + + return pd; +} +#else +static struct sec_platform_data *sec_pmic_i2c_parse_dt_pdata( + struct device *dev) +{ + return NULL; +} +#endif + +static inline unsigned long sec_i2c_get_driver_data(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ +#ifdef CONFIG_OF + if (i2c->dev.of_node) { + const struct of_device_id *match; + + match = of_match_node(sec_dt_match, i2c->dev.of_node); + return (unsigned long)match->data; + } +#endif + return id->driver_data; +} + +static int sec_pmic_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sec_platform_data *pdata = dev_get_platdata(&i2c->dev); + const struct regmap_config *regmap; + const struct mfd_cell *sec_devs; + struct sec_pmic_dev *sec_pmic; + unsigned long device_type; + int ret, num_sec_devs; + + sec_pmic = devm_kzalloc(&i2c->dev, sizeof(struct sec_pmic_dev), + GFP_KERNEL); + if (sec_pmic == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, sec_pmic); + sec_pmic->dev = &i2c->dev; + sec_pmic->i2c = i2c; + sec_pmic->irq = i2c->irq; + device_type = sec_i2c_get_driver_data(i2c, id); + + if (sec_pmic->dev->of_node) { + pdata = sec_pmic_i2c_parse_dt_pdata(sec_pmic->dev); + if (IS_ERR(pdata)) { + ret = PTR_ERR(pdata); + return ret; + } + pdata->device_type = device_type; + } + if (pdata) { + sec_pmic->device_type = pdata->device_type; + sec_pmic->irq_base = pdata->irq_base; + sec_pmic->wakeup = pdata->wakeup; + sec_pmic->pdata = pdata; + } + + switch (sec_pmic->device_type) { + case S2MPA01: + regmap = &s2mpa01_regmap_config; + break; + case S2MPS11X: + regmap = &s2mps11_regmap_config; + break; + case S2MPS13X: + regmap = &s2mps13_regmap_config; + break; + case S2MPS14X: + regmap = &s2mps14_regmap_config; + break; + case S5M8763X: + regmap = &s5m8763_regmap_config; + break; + case S5M8767X: + regmap = &s5m8767_regmap_config; + break; + case S2MPU02: + regmap = &s2mpu02_regmap_config; + break; + default: + regmap = &sec_regmap_config; + break; + } + + sec_pmic->regmap_pmic = devm_regmap_init_i2c(i2c, regmap); + if (IS_ERR(sec_pmic->regmap_pmic)) { + ret = PTR_ERR(sec_pmic->regmap_pmic); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + if (pdata && pdata->cfg_pmic_irq) + pdata->cfg_pmic_irq(); + + sec_irq_init(sec_pmic); + + pm_runtime_set_active(sec_pmic->dev); + + switch (sec_pmic->device_type) { + case S5M8751X: + sec_devs = s5m8751_devs; + num_sec_devs = ARRAY_SIZE(s5m8751_devs); + break; + case S5M8763X: + sec_devs = s5m8763_devs; + num_sec_devs = ARRAY_SIZE(s5m8763_devs); + break; + case S5M8767X: + sec_devs = s5m8767_devs; + num_sec_devs = ARRAY_SIZE(s5m8767_devs); + break; + case S2MPA01: + sec_devs = s2mpa01_devs; + num_sec_devs = ARRAY_SIZE(s2mpa01_devs); + break; + case S2MPS11X: + sec_devs = s2mps11_devs; + num_sec_devs = ARRAY_SIZE(s2mps11_devs); + break; + case S2MPS13X: + sec_devs = s2mps13_devs; + num_sec_devs = ARRAY_SIZE(s2mps13_devs); + break; + case S2MPS14X: + sec_devs = s2mps14_devs; + num_sec_devs = ARRAY_SIZE(s2mps14_devs); + break; + case S2MPU02: + sec_devs = s2mpu02_devs; + num_sec_devs = ARRAY_SIZE(s2mpu02_devs); + break; + default: + /* If this happens the probe function is problem */ + BUG(); + } + ret = mfd_add_devices(sec_pmic->dev, -1, sec_devs, num_sec_devs, NULL, + 0, NULL); + if (ret) + goto err_mfd; + + device_init_wakeup(sec_pmic->dev, sec_pmic->wakeup); + + return ret; + +err_mfd: + sec_irq_exit(sec_pmic); + return ret; +} + +static int sec_pmic_remove(struct i2c_client *i2c) +{ + struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c); + + mfd_remove_devices(sec_pmic->dev); + sec_irq_exit(sec_pmic); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sec_pmic_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + enable_irq_wake(sec_pmic->irq); + /* + * PMIC IRQ must be disabled during suspend for RTC alarm + * to work properly. + * When device is woken up from suspend, an + * interrupt occurs before resuming I2C bus controller. + * The interrupt is handled by regmap_irq_thread which tries + * to read RTC registers. This read fails (I2C is still + * suspended) and RTC Alarm interrupt is disabled. + */ + disable_irq(sec_pmic->irq); + + return 0; +} + +static int sec_pmic_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(sec_pmic->irq); + enable_irq(sec_pmic->irq); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(sec_pmic_pm_ops, sec_pmic_suspend, sec_pmic_resume); + +static const struct i2c_device_id sec_pmic_id[] = { + { "sec_pmic", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sec_pmic_id); + +static struct i2c_driver sec_pmic_driver = { + .driver = { + .name = "sec_pmic", + .owner = THIS_MODULE, + .pm = &sec_pmic_pm_ops, + .of_match_table = of_match_ptr(sec_dt_match), + }, + .probe = sec_pmic_probe, + .remove = sec_pmic_remove, + .id_table = sec_pmic_id, +}; + +static int __init sec_pmic_init(void) +{ + return i2c_add_driver(&sec_pmic_driver); +} + +subsys_initcall(sec_pmic_init); + +static void __exit sec_pmic_exit(void) +{ + i2c_del_driver(&sec_pmic_driver); +} +module_exit(sec_pmic_exit); + +MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>"); +MODULE_DESCRIPTION("Core support for the S5M MFD"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/sec-irq.c b/kernel/drivers/mfd/sec-irq.c new file mode 100644 index 000000000..806fa8dbb --- /dev/null +++ b/kernel/drivers/mfd/sec-irq.c @@ -0,0 +1,499 @@ +/* + * sec-irq.c + * + * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd + * http://www.samsung.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/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/regmap.h> + +#include <linux/mfd/samsung/core.h> +#include <linux/mfd/samsung/irq.h> +#include <linux/mfd/samsung/s2mps11.h> +#include <linux/mfd/samsung/s2mps14.h> +#include <linux/mfd/samsung/s2mpu02.h> +#include <linux/mfd/samsung/s5m8763.h> +#include <linux/mfd/samsung/s5m8767.h> + +static const struct regmap_irq s2mps11_irqs[] = { + [S2MPS11_IRQ_PWRONF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRONF_MASK, + }, + [S2MPS11_IRQ_PWRONR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRONR_MASK, + }, + [S2MPS11_IRQ_JIGONBF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_JIGONBF_MASK, + }, + [S2MPS11_IRQ_JIGONBR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_JIGONBR_MASK, + }, + [S2MPS11_IRQ_ACOKBF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_ACOKBF_MASK, + }, + [S2MPS11_IRQ_ACOKBR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_ACOKBR_MASK, + }, + [S2MPS11_IRQ_PWRON1S] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRON1S_MASK, + }, + [S2MPS11_IRQ_MRB] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_MRB_MASK, + }, + [S2MPS11_IRQ_RTC60S] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTC60S_MASK, + }, + [S2MPS11_IRQ_RTCA1] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTCA1_MASK, + }, + [S2MPS11_IRQ_RTCA0] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTCA0_MASK, + }, + [S2MPS11_IRQ_SMPL] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_SMPL_MASK, + }, + [S2MPS11_IRQ_RTC1S] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTC1S_MASK, + }, + [S2MPS11_IRQ_WTSR] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_WTSR_MASK, + }, + [S2MPS11_IRQ_INT120C] = { + .reg_offset = 2, + .mask = S2MPS11_IRQ_INT120C_MASK, + }, + [S2MPS11_IRQ_INT140C] = { + .reg_offset = 2, + .mask = S2MPS11_IRQ_INT140C_MASK, + }, +}; + +static const struct regmap_irq s2mps14_irqs[] = { + [S2MPS14_IRQ_PWRONF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRONF_MASK, + }, + [S2MPS14_IRQ_PWRONR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRONR_MASK, + }, + [S2MPS14_IRQ_JIGONBF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_JIGONBF_MASK, + }, + [S2MPS14_IRQ_JIGONBR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_JIGONBR_MASK, + }, + [S2MPS14_IRQ_ACOKBF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_ACOKBF_MASK, + }, + [S2MPS14_IRQ_ACOKBR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_ACOKBR_MASK, + }, + [S2MPS14_IRQ_PWRON1S] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRON1S_MASK, + }, + [S2MPS14_IRQ_MRB] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_MRB_MASK, + }, + [S2MPS14_IRQ_RTC60S] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTC60S_MASK, + }, + [S2MPS14_IRQ_RTCA1] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTCA1_MASK, + }, + [S2MPS14_IRQ_RTCA0] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTCA0_MASK, + }, + [S2MPS14_IRQ_SMPL] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_SMPL_MASK, + }, + [S2MPS14_IRQ_RTC1S] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTC1S_MASK, + }, + [S2MPS14_IRQ_WTSR] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_WTSR_MASK, + }, + [S2MPS14_IRQ_INT120C] = { + .reg_offset = 2, + .mask = S2MPS11_IRQ_INT120C_MASK, + }, + [S2MPS14_IRQ_INT140C] = { + .reg_offset = 2, + .mask = S2MPS11_IRQ_INT140C_MASK, + }, + [S2MPS14_IRQ_TSD] = { + .reg_offset = 2, + .mask = S2MPS14_IRQ_TSD_MASK, + }, +}; + +static const struct regmap_irq s2mpu02_irqs[] = { + [S2MPU02_IRQ_PWRONF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRONF_MASK, + }, + [S2MPU02_IRQ_PWRONR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRONR_MASK, + }, + [S2MPU02_IRQ_JIGONBF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_JIGONBF_MASK, + }, + [S2MPU02_IRQ_JIGONBR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_JIGONBR_MASK, + }, + [S2MPU02_IRQ_ACOKBF] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_ACOKBF_MASK, + }, + [S2MPU02_IRQ_ACOKBR] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_ACOKBR_MASK, + }, + [S2MPU02_IRQ_PWRON1S] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_PWRON1S_MASK, + }, + [S2MPU02_IRQ_MRB] = { + .reg_offset = 0, + .mask = S2MPS11_IRQ_MRB_MASK, + }, + [S2MPU02_IRQ_RTC60S] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTC60S_MASK, + }, + [S2MPU02_IRQ_RTCA1] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTCA1_MASK, + }, + [S2MPU02_IRQ_RTCA0] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTCA0_MASK, + }, + [S2MPU02_IRQ_SMPL] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_SMPL_MASK, + }, + [S2MPU02_IRQ_RTC1S] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_RTC1S_MASK, + }, + [S2MPU02_IRQ_WTSR] = { + .reg_offset = 1, + .mask = S2MPS11_IRQ_WTSR_MASK, + }, + [S2MPU02_IRQ_INT120C] = { + .reg_offset = 2, + .mask = S2MPS11_IRQ_INT120C_MASK, + }, + [S2MPU02_IRQ_INT140C] = { + .reg_offset = 2, + .mask = S2MPS11_IRQ_INT140C_MASK, + }, + [S2MPU02_IRQ_TSD] = { + .reg_offset = 2, + .mask = S2MPS14_IRQ_TSD_MASK, + }, +}; + +static const struct regmap_irq s5m8767_irqs[] = { + [S5M8767_IRQ_PWRR] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_PWRR_MASK, + }, + [S5M8767_IRQ_PWRF] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_PWRF_MASK, + }, + [S5M8767_IRQ_PWR1S] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_PWR1S_MASK, + }, + [S5M8767_IRQ_JIGR] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_JIGR_MASK, + }, + [S5M8767_IRQ_JIGF] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_JIGF_MASK, + }, + [S5M8767_IRQ_LOWBAT2] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_LOWBAT2_MASK, + }, + [S5M8767_IRQ_LOWBAT1] = { + .reg_offset = 0, + .mask = S5M8767_IRQ_LOWBAT1_MASK, + }, + [S5M8767_IRQ_MRB] = { + .reg_offset = 1, + .mask = S5M8767_IRQ_MRB_MASK, + }, + [S5M8767_IRQ_DVSOK2] = { + .reg_offset = 1, + .mask = S5M8767_IRQ_DVSOK2_MASK, + }, + [S5M8767_IRQ_DVSOK3] = { + .reg_offset = 1, + .mask = S5M8767_IRQ_DVSOK3_MASK, + }, + [S5M8767_IRQ_DVSOK4] = { + .reg_offset = 1, + .mask = S5M8767_IRQ_DVSOK4_MASK, + }, + [S5M8767_IRQ_RTC60S] = { + .reg_offset = 2, + .mask = S5M8767_IRQ_RTC60S_MASK, + }, + [S5M8767_IRQ_RTCA1] = { + .reg_offset = 2, + .mask = S5M8767_IRQ_RTCA1_MASK, + }, + [S5M8767_IRQ_RTCA2] = { + .reg_offset = 2, + .mask = S5M8767_IRQ_RTCA2_MASK, + }, + [S5M8767_IRQ_SMPL] = { + .reg_offset = 2, + .mask = S5M8767_IRQ_SMPL_MASK, + }, + [S5M8767_IRQ_RTC1S] = { + .reg_offset = 2, + .mask = S5M8767_IRQ_RTC1S_MASK, + }, + [S5M8767_IRQ_WTSR] = { + .reg_offset = 2, + .mask = S5M8767_IRQ_WTSR_MASK, + }, +}; + +static const struct regmap_irq s5m8763_irqs[] = { + [S5M8763_IRQ_DCINF] = { + .reg_offset = 0, + .mask = S5M8763_IRQ_DCINF_MASK, + }, + [S5M8763_IRQ_DCINR] = { + .reg_offset = 0, + .mask = S5M8763_IRQ_DCINR_MASK, + }, + [S5M8763_IRQ_JIGF] = { + .reg_offset = 0, + .mask = S5M8763_IRQ_JIGF_MASK, + }, + [S5M8763_IRQ_JIGR] = { + .reg_offset = 0, + .mask = S5M8763_IRQ_JIGR_MASK, + }, + [S5M8763_IRQ_PWRONF] = { + .reg_offset = 0, + .mask = S5M8763_IRQ_PWRONF_MASK, + }, + [S5M8763_IRQ_PWRONR] = { + .reg_offset = 0, + .mask = S5M8763_IRQ_PWRONR_MASK, + }, + [S5M8763_IRQ_WTSREVNT] = { + .reg_offset = 1, + .mask = S5M8763_IRQ_WTSREVNT_MASK, + }, + [S5M8763_IRQ_SMPLEVNT] = { + .reg_offset = 1, + .mask = S5M8763_IRQ_SMPLEVNT_MASK, + }, + [S5M8763_IRQ_ALARM1] = { + .reg_offset = 1, + .mask = S5M8763_IRQ_ALARM1_MASK, + }, + [S5M8763_IRQ_ALARM0] = { + .reg_offset = 1, + .mask = S5M8763_IRQ_ALARM0_MASK, + }, + [S5M8763_IRQ_ONKEY1S] = { + .reg_offset = 2, + .mask = S5M8763_IRQ_ONKEY1S_MASK, + }, + [S5M8763_IRQ_TOPOFFR] = { + .reg_offset = 2, + .mask = S5M8763_IRQ_TOPOFFR_MASK, + }, + [S5M8763_IRQ_DCINOVPR] = { + .reg_offset = 2, + .mask = S5M8763_IRQ_DCINOVPR_MASK, + }, + [S5M8763_IRQ_CHGRSTF] = { + .reg_offset = 2, + .mask = S5M8763_IRQ_CHGRSTF_MASK, + }, + [S5M8763_IRQ_DONER] = { + .reg_offset = 2, + .mask = S5M8763_IRQ_DONER_MASK, + }, + [S5M8763_IRQ_CHGFAULT] = { + .reg_offset = 2, + .mask = S5M8763_IRQ_CHGFAULT_MASK, + }, + [S5M8763_IRQ_LOBAT1] = { + .reg_offset = 3, + .mask = S5M8763_IRQ_LOBAT1_MASK, + }, + [S5M8763_IRQ_LOBAT2] = { + .reg_offset = 3, + .mask = S5M8763_IRQ_LOBAT2_MASK, + }, +}; + +static const struct regmap_irq_chip s2mps11_irq_chip = { + .name = "s2mps11", + .irqs = s2mps11_irqs, + .num_irqs = ARRAY_SIZE(s2mps11_irqs), + .num_regs = 3, + .status_base = S2MPS11_REG_INT1, + .mask_base = S2MPS11_REG_INT1M, + .ack_base = S2MPS11_REG_INT1, +}; + +#define S2MPS1X_IRQ_CHIP_COMMON_DATA \ + .irqs = s2mps14_irqs, \ + .num_irqs = ARRAY_SIZE(s2mps14_irqs), \ + .num_regs = 3, \ + .status_base = S2MPS14_REG_INT1, \ + .mask_base = S2MPS14_REG_INT1M, \ + .ack_base = S2MPS14_REG_INT1 \ + +static const struct regmap_irq_chip s2mps13_irq_chip = { + .name = "s2mps13", + S2MPS1X_IRQ_CHIP_COMMON_DATA, +}; + +static const struct regmap_irq_chip s2mps14_irq_chip = { + .name = "s2mps14", + S2MPS1X_IRQ_CHIP_COMMON_DATA, +}; + +static const struct regmap_irq_chip s2mpu02_irq_chip = { + .name = "s2mpu02", + .irqs = s2mpu02_irqs, + .num_irqs = ARRAY_SIZE(s2mpu02_irqs), + .num_regs = 3, + .status_base = S2MPU02_REG_INT1, + .mask_base = S2MPU02_REG_INT1M, + .ack_base = S2MPU02_REG_INT1, +}; + +static const struct regmap_irq_chip s5m8767_irq_chip = { + .name = "s5m8767", + .irqs = s5m8767_irqs, + .num_irqs = ARRAY_SIZE(s5m8767_irqs), + .num_regs = 3, + .status_base = S5M8767_REG_INT1, + .mask_base = S5M8767_REG_INT1M, + .ack_base = S5M8767_REG_INT1, +}; + +static const struct regmap_irq_chip s5m8763_irq_chip = { + .name = "s5m8763", + .irqs = s5m8763_irqs, + .num_irqs = ARRAY_SIZE(s5m8763_irqs), + .num_regs = 4, + .status_base = S5M8763_REG_IRQ1, + .mask_base = S5M8763_REG_IRQM1, + .ack_base = S5M8763_REG_IRQ1, +}; + +int sec_irq_init(struct sec_pmic_dev *sec_pmic) +{ + int ret = 0; + int type = sec_pmic->device_type; + const struct regmap_irq_chip *sec_irq_chip; + + if (!sec_pmic->irq) { + dev_warn(sec_pmic->dev, + "No interrupt specified, no interrupts\n"); + sec_pmic->irq_base = 0; + return 0; + } + + switch (type) { + case S5M8763X: + sec_irq_chip = &s5m8763_irq_chip; + break; + case S5M8767X: + sec_irq_chip = &s5m8767_irq_chip; + break; + case S2MPS11X: + sec_irq_chip = &s2mps11_irq_chip; + break; + case S2MPS13X: + sec_irq_chip = &s2mps13_irq_chip; + break; + case S2MPS14X: + sec_irq_chip = &s2mps14_irq_chip; + break; + case S2MPU02: + sec_irq_chip = &s2mpu02_irq_chip; + break; + default: + dev_err(sec_pmic->dev, "Unknown device type %lu\n", + sec_pmic->device_type); + return -EINVAL; + } + + ret = regmap_add_irq_chip(sec_pmic->regmap_pmic, sec_pmic->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + sec_pmic->irq_base, sec_irq_chip, + &sec_pmic->irq_data); + if (ret != 0) { + dev_err(sec_pmic->dev, "Failed to register IRQ chip: %d\n", ret); + return ret; + } + + /* + * The rtc-s5m driver requests S2MPS14_IRQ_RTCA0 also for S2MPS11 + * so the interrupt number must be consistent. + */ + BUILD_BUG_ON(((enum s2mps14_irq)S2MPS11_IRQ_RTCA0) != S2MPS14_IRQ_RTCA0); + + return 0; +} + +void sec_irq_exit(struct sec_pmic_dev *sec_pmic) +{ + regmap_del_irq_chip(sec_pmic->irq, sec_pmic->irq_data); +} diff --git a/kernel/drivers/mfd/si476x-cmd.c b/kernel/drivers/mfd/si476x-cmd.c new file mode 100644 index 000000000..2086b4665 --- /dev/null +++ b/kernel/drivers/mfd/si476x-cmd.c @@ -0,0 +1,1555 @@ +/* + * drivers/mfd/si476x-cmd.c -- Subroutines implementing command + * protocol of si476x series of chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@gmail.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; version 2 of the License. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/atomic.h> +#include <linux/i2c.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/videodev2.h> + +#include <linux/mfd/si476x-core.h> + +#include <asm/unaligned.h> + +#define msb(x) ((u8)((u16) x >> 8)) +#define lsb(x) ((u8)((u16) x & 0x00FF)) + + + +#define CMD_POWER_UP 0x01 +#define CMD_POWER_UP_A10_NRESP 1 +#define CMD_POWER_UP_A10_NARGS 5 + +#define CMD_POWER_UP_A20_NRESP 1 +#define CMD_POWER_UP_A20_NARGS 5 + +#define POWER_UP_DELAY_MS 110 + +#define CMD_POWER_DOWN 0x11 +#define CMD_POWER_DOWN_A10_NRESP 1 + +#define CMD_POWER_DOWN_A20_NRESP 1 +#define CMD_POWER_DOWN_A20_NARGS 1 + +#define CMD_FUNC_INFO 0x12 +#define CMD_FUNC_INFO_NRESP 7 + +#define CMD_SET_PROPERTY 0x13 +#define CMD_SET_PROPERTY_NARGS 5 +#define CMD_SET_PROPERTY_NRESP 1 + +#define CMD_GET_PROPERTY 0x14 +#define CMD_GET_PROPERTY_NARGS 3 +#define CMD_GET_PROPERTY_NRESP 4 + +#define CMD_AGC_STATUS 0x17 +#define CMD_AGC_STATUS_NRESP_A10 2 +#define CMD_AGC_STATUS_NRESP_A20 6 + +#define PIN_CFG_BYTE(x) (0x7F & (x)) +#define CMD_DIG_AUDIO_PIN_CFG 0x18 +#define CMD_DIG_AUDIO_PIN_CFG_NARGS 4 +#define CMD_DIG_AUDIO_PIN_CFG_NRESP 5 + +#define CMD_ZIF_PIN_CFG 0x19 +#define CMD_ZIF_PIN_CFG_NARGS 4 +#define CMD_ZIF_PIN_CFG_NRESP 5 + +#define CMD_IC_LINK_GPO_CTL_PIN_CFG 0x1A +#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS 4 +#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP 5 + +#define CMD_ANA_AUDIO_PIN_CFG 0x1B +#define CMD_ANA_AUDIO_PIN_CFG_NARGS 1 +#define CMD_ANA_AUDIO_PIN_CFG_NRESP 2 + +#define CMD_INTB_PIN_CFG 0x1C +#define CMD_INTB_PIN_CFG_NARGS 2 +#define CMD_INTB_PIN_CFG_A10_NRESP 6 +#define CMD_INTB_PIN_CFG_A20_NRESP 3 + +#define CMD_FM_TUNE_FREQ 0x30 +#define CMD_FM_TUNE_FREQ_A10_NARGS 5 +#define CMD_FM_TUNE_FREQ_A20_NARGS 3 +#define CMD_FM_TUNE_FREQ_NRESP 1 + +#define CMD_FM_RSQ_STATUS 0x32 + +#define CMD_FM_RSQ_STATUS_A10_NARGS 1 +#define CMD_FM_RSQ_STATUS_A10_NRESP 17 +#define CMD_FM_RSQ_STATUS_A30_NARGS 1 +#define CMD_FM_RSQ_STATUS_A30_NRESP 23 + + +#define CMD_FM_SEEK_START 0x31 +#define CMD_FM_SEEK_START_NARGS 1 +#define CMD_FM_SEEK_START_NRESP 1 + +#define CMD_FM_RDS_STATUS 0x36 +#define CMD_FM_RDS_STATUS_NARGS 1 +#define CMD_FM_RDS_STATUS_NRESP 16 + +#define CMD_FM_RDS_BLOCKCOUNT 0x37 +#define CMD_FM_RDS_BLOCKCOUNT_NARGS 1 +#define CMD_FM_RDS_BLOCKCOUNT_NRESP 8 + +#define CMD_FM_PHASE_DIVERSITY 0x38 +#define CMD_FM_PHASE_DIVERSITY_NARGS 1 +#define CMD_FM_PHASE_DIVERSITY_NRESP 1 + +#define CMD_FM_PHASE_DIV_STATUS 0x39 +#define CMD_FM_PHASE_DIV_STATUS_NRESP 2 + +#define CMD_AM_TUNE_FREQ 0x40 +#define CMD_AM_TUNE_FREQ_NARGS 3 +#define CMD_AM_TUNE_FREQ_NRESP 1 + +#define CMD_AM_RSQ_STATUS 0x42 +#define CMD_AM_RSQ_STATUS_NARGS 1 +#define CMD_AM_RSQ_STATUS_NRESP 13 + +#define CMD_AM_SEEK_START 0x41 +#define CMD_AM_SEEK_START_NARGS 1 +#define CMD_AM_SEEK_START_NRESP 1 + + +#define CMD_AM_ACF_STATUS 0x45 +#define CMD_AM_ACF_STATUS_NRESP 6 +#define CMD_AM_ACF_STATUS_NARGS 1 + +#define CMD_FM_ACF_STATUS 0x35 +#define CMD_FM_ACF_STATUS_NRESP 8 +#define CMD_FM_ACF_STATUS_NARGS 1 + +#define CMD_MAX_ARGS_COUNT (10) + + +enum si476x_acf_status_report_bits { + SI476X_ACF_BLEND_INT = (1 << 4), + SI476X_ACF_HIBLEND_INT = (1 << 3), + SI476X_ACF_HICUT_INT = (1 << 2), + SI476X_ACF_CHBW_INT = (1 << 1), + SI476X_ACF_SOFTMUTE_INT = (1 << 0), + + SI476X_ACF_SMUTE = (1 << 0), + SI476X_ACF_SMATTN = 0x1f, + SI476X_ACF_PILOT = (1 << 7), + SI476X_ACF_STBLEND = ~SI476X_ACF_PILOT, +}; + +enum si476x_agc_status_report_bits { + SI476X_AGC_MXHI = (1 << 5), + SI476X_AGC_MXLO = (1 << 4), + SI476X_AGC_LNAHI = (1 << 3), + SI476X_AGC_LNALO = (1 << 2), +}; + +enum si476x_errors { + SI476X_ERR_BAD_COMMAND = 0x10, + SI476X_ERR_BAD_ARG1 = 0x11, + SI476X_ERR_BAD_ARG2 = 0x12, + SI476X_ERR_BAD_ARG3 = 0x13, + SI476X_ERR_BAD_ARG4 = 0x14, + SI476X_ERR_BUSY = 0x18, + SI476X_ERR_BAD_INTERNAL_MEMORY = 0x20, + SI476X_ERR_BAD_PATCH = 0x30, + SI476X_ERR_BAD_BOOT_MODE = 0x31, + SI476X_ERR_BAD_PROPERTY = 0x40, +}; + +static int si476x_core_parse_and_nag_about_error(struct si476x_core *core) +{ + int err; + char *cause; + u8 buffer[2]; + + if (core->revision != SI476X_REVISION_A10) { + err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, + buffer, sizeof(buffer)); + if (err == sizeof(buffer)) { + switch (buffer[1]) { + case SI476X_ERR_BAD_COMMAND: + cause = "Bad command"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG1: + cause = "Bad argument #1"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG2: + cause = "Bad argument #2"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG3: + cause = "Bad argument #3"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_ARG4: + cause = "Bad argument #4"; + err = -EINVAL; + break; + case SI476X_ERR_BUSY: + cause = "Chip is busy"; + err = -EBUSY; + break; + case SI476X_ERR_BAD_INTERNAL_MEMORY: + cause = "Bad internal memory"; + err = -EIO; + break; + case SI476X_ERR_BAD_PATCH: + cause = "Bad patch"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_BOOT_MODE: + cause = "Bad boot mode"; + err = -EINVAL; + break; + case SI476X_ERR_BAD_PROPERTY: + cause = "Bad property"; + err = -EINVAL; + break; + default: + cause = "Unknown"; + err = -EIO; + } + + dev_err(&core->client->dev, + "[Chip error status]: %s\n", cause); + } else { + dev_err(&core->client->dev, + "Failed to fetch error code\n"); + err = (err >= 0) ? -EIO : err; + } + } else { + err = -EIO; + } + + return err; +} + +/** + * si476x_core_send_command() - sends a command to si476x and waits its + * response + * @core: si476x_device structure for the device we are + * communicating with + * @command: command id + * @args: command arguments we are sending + * @argn: actual size of @args + * @response: buffer to place the expected response from the device + * @respn: actual size of @response + * @usecs: amount of time to wait before reading the response (in + * usecs) + * + * Function returns 0 on succsess and negative error code on + * failure + */ +static int si476x_core_send_command(struct si476x_core *core, + const u8 command, + const u8 args[], + const int argn, + u8 resp[], + const int respn, + const int usecs) +{ + struct i2c_client *client = core->client; + int err; + u8 data[CMD_MAX_ARGS_COUNT + 1]; + + if (argn > CMD_MAX_ARGS_COUNT) { + err = -ENOMEM; + goto exit; + } + + if (!client->adapter) { + err = -ENODEV; + goto exit; + } + + /* First send the command and its arguments */ + data[0] = command; + memcpy(&data[1], args, argn); + dev_dbg(&client->dev, "Command:\n %*ph\n", argn + 1, data); + + err = si476x_core_i2c_xfer(core, SI476X_I2C_SEND, + (char *) data, argn + 1); + if (err != argn + 1) { + dev_err(&core->client->dev, + "Error while sending command 0x%02x\n", + command); + err = (err >= 0) ? -EIO : err; + goto exit; + } + /* Set CTS to zero only after the command is send to avoid + * possible racing conditions when working in polling mode */ + atomic_set(&core->cts, 0); + + /* if (unlikely(command == CMD_POWER_DOWN) */ + if (!wait_event_timeout(core->command, + atomic_read(&core->cts), + usecs_to_jiffies(usecs) + 1)) + dev_warn(&core->client->dev, + "(%s) [CMD 0x%02x] Answer timeout.\n", + __func__, command); + + /* + When working in polling mode, for some reason the tuner will + report CTS bit as being set in the first status byte read, + but all the consequtive ones will return zeros until the + tuner is actually completed the POWER_UP command. To + workaround that we wait for second CTS to be reported + */ + if (unlikely(!core->client->irq && command == CMD_POWER_UP)) { + if (!wait_event_timeout(core->command, + atomic_read(&core->cts), + usecs_to_jiffies(usecs) + 1)) + dev_warn(&core->client->dev, + "(%s) Power up took too much time.\n", + __func__); + } + + /* Then get the response */ + err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, resp, respn); + if (err != respn) { + dev_err(&core->client->dev, + "Error while reading response for command 0x%02x\n", + command); + err = (err >= 0) ? -EIO : err; + goto exit; + } + dev_dbg(&client->dev, "Response:\n %*ph\n", respn, resp); + + err = 0; + + if (resp[0] & SI476X_ERR) { + dev_err(&core->client->dev, + "[CMD 0x%02x] Chip set error flag\n", command); + err = si476x_core_parse_and_nag_about_error(core); + goto exit; + } + + if (!(resp[0] & SI476X_CTS)) + err = -EBUSY; +exit: + return err; +} + +static int si476x_cmd_clear_stc(struct si476x_core *core) +{ + int err; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = true, + }; + + switch (core->power_up_parameters.func) { + case SI476X_FUNC_FM_RECEIVER: + err = si476x_core_cmd_fm_rsq_status(core, &args, NULL); + break; + case SI476X_FUNC_AM_RECEIVER: + err = si476x_core_cmd_am_rsq_status(core, &args, NULL); + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_cmd_tune_seek_freq(struct si476x_core *core, + uint8_t cmd, + const uint8_t args[], size_t argn, + uint8_t *resp, size_t respn) +{ + int err; + + + atomic_set(&core->stc, 0); + err = si476x_core_send_command(core, cmd, args, argn, resp, respn, + SI476X_TIMEOUT_TUNE); + if (!err) { + wait_event_killable(core->tuning, + atomic_read(&core->stc)); + si476x_cmd_clear_stc(core); + } + + return err; +} + +/** + * si476x_cmd_func_info() - send 'FUNC_INFO' command to the device + * @core: device to send the command to + * @info: struct si476x_func_info to fill all the information + * returned by the command + * + * The command requests the firmware and patch version for currently + * loaded firmware (dependent on the function of the device FM/AM/WB) + * + * Function returns 0 on succsess and negative error code on + * failure + */ +int si476x_core_cmd_func_info(struct si476x_core *core, + struct si476x_func_info *info) +{ + int err; + u8 resp[CMD_FUNC_INFO_NRESP]; + + err = si476x_core_send_command(core, CMD_FUNC_INFO, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + + info->firmware.major = resp[1]; + info->firmware.minor[0] = resp[2]; + info->firmware.minor[1] = resp[3]; + + info->patch_id = ((u16) resp[4] << 8) | resp[5]; + info->func = resp[6]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_func_info); + +/** + * si476x_cmd_set_property() - send 'SET_PROPERTY' command to the device + * @core: device to send the command to + * @property: property address + * @value: property value + * + * Function returns 0 on succsess and negative error code on + * failure + */ +int si476x_core_cmd_set_property(struct si476x_core *core, + u16 property, u16 value) +{ + u8 resp[CMD_SET_PROPERTY_NRESP]; + const u8 args[CMD_SET_PROPERTY_NARGS] = { + 0x00, + msb(property), + lsb(property), + msb(value), + lsb(value), + }; + + return si476x_core_send_command(core, CMD_SET_PROPERTY, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_set_property); + +/** + * si476x_cmd_get_property() - send 'GET_PROPERTY' command to the device + * @core: device to send the command to + * @property: property address + * + * Function return the value of property as u16 on success or a + * negative error on failure + */ +int si476x_core_cmd_get_property(struct si476x_core *core, u16 property) +{ + int err; + u8 resp[CMD_GET_PROPERTY_NRESP]; + const u8 args[CMD_GET_PROPERTY_NARGS] = { + 0x00, + msb(property), + lsb(property), + }; + + err = si476x_core_send_command(core, CMD_GET_PROPERTY, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + else + return get_unaligned_be16(resp + 2); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_get_property); + +/** + * si476x_cmd_dig_audio_pin_cfg() - send 'DIG_AUDIO_PIN_CFG' command to + * the device + * @core: device to send the command to + * @dclk: DCLK pin function configuration: + * #SI476X_DCLK_NOOP - do not modify the behaviour + * #SI476X_DCLK_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * #SI476X_DCLK_DAUDIO - set the pin to be a part of digital + * audio interface + * @dfs: DFS pin function configuration: + * #SI476X_DFS_NOOP - do not modify the behaviour + * #SI476X_DFS_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_DFS_DAUDIO - set the pin to be a part of digital + * audio interface + * @dout - DOUT pin function configuration: + * SI476X_DOUT_NOOP - do not modify the behaviour + * SI476X_DOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_DOUT_I2S_OUTPUT - set this pin to be digital out on I2S + * port 1 + * SI476X_DOUT_I2S_INPUT - set this pin to be digital in on I2S + * port 1 + * @xout - XOUT pin function configuration: + * SI476X_XOUT_NOOP - do not modify the behaviour + * SI476X_XOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_XOUT_I2S_INPUT - set this pin to be digital in on I2S + * port 1 + * SI476X_XOUT_MODE_SELECT - set this pin to be the input that + * selects the mode of the I2S audio + * combiner (analog or HD) + * [SI4761/63/65/67 Only] + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *core, + enum si476x_dclk_config dclk, + enum si476x_dfs_config dfs, + enum si476x_dout_config dout, + enum si476x_xout_config xout) +{ + u8 resp[CMD_DIG_AUDIO_PIN_CFG_NRESP]; + const u8 args[CMD_DIG_AUDIO_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(dclk), + PIN_CFG_BYTE(dfs), + PIN_CFG_BYTE(dout), + PIN_CFG_BYTE(xout), + }; + + return si476x_core_send_command(core, CMD_DIG_AUDIO_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_dig_audio_pin_cfg); + +/** + * si476x_cmd_zif_pin_cfg - send 'ZIF_PIN_CFG_COMMAND' + * @core - device to send the command to + * @iqclk - IQCL pin function configuration: + * SI476X_IQCLK_NOOP - do not modify the behaviour + * SI476X_IQCLK_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_IQCLK_IQ - set pin to be a part of I/Q interace + * in master mode + * @iqfs - IQFS pin function configuration: + * SI476X_IQFS_NOOP - do not modify the behaviour + * SI476X_IQFS_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_IQFS_IQ - set pin to be a part of I/Q interace + * in master mode + * @iout - IOUT pin function configuration: + * SI476X_IOUT_NOOP - do not modify the behaviour + * SI476X_IOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_IOUT_OUTPUT - set pin to be I out + * @qout - QOUT pin function configuration: + * SI476X_QOUT_NOOP - do not modify the behaviour + * SI476X_QOUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_QOUT_OUTPUT - set pin to be Q out + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_zif_pin_cfg(struct si476x_core *core, + enum si476x_iqclk_config iqclk, + enum si476x_iqfs_config iqfs, + enum si476x_iout_config iout, + enum si476x_qout_config qout) +{ + u8 resp[CMD_ZIF_PIN_CFG_NRESP]; + const u8 args[CMD_ZIF_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(iqclk), + PIN_CFG_BYTE(iqfs), + PIN_CFG_BYTE(iout), + PIN_CFG_BYTE(qout), + }; + + return si476x_core_send_command(core, CMD_ZIF_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_zif_pin_cfg); + +/** + * si476x_cmd_ic_link_gpo_ctl_pin_cfg - send + * 'IC_LINK_GPIO_CTL_PIN_CFG' comand to the device + * @core - device to send the command to + * @icin - ICIN pin function configuration: + * SI476X_ICIN_NOOP - do not modify the behaviour + * SI476X_ICIN_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICIN_GPO1_HIGH - set pin to be an output, drive it high + * SI476X_ICIN_GPO1_LOW - set pin to be an output, drive it low + * SI476X_ICIN_IC_LINK - set the pin to be a part of Inter-Chip link + * @icip - ICIP pin function configuration: + * SI476X_ICIP_NOOP - do not modify the behaviour + * SI476X_ICIP_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICIP_GPO1_HIGH - set pin to be an output, drive it high + * SI476X_ICIP_GPO1_LOW - set pin to be an output, drive it low + * SI476X_ICIP_IC_LINK - set the pin to be a part of Inter-Chip link + * @icon - ICON pin function configuration: + * SI476X_ICON_NOOP - do not modify the behaviour + * SI476X_ICON_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICON_I2S - set the pin to be a part of audio + * interface in slave mode (DCLK) + * SI476X_ICON_IC_LINK - set the pin to be a part of Inter-Chip link + * @icop - ICOP pin function configuration: + * SI476X_ICOP_NOOP - do not modify the behaviour + * SI476X_ICOP_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_ICOP_I2S - set the pin to be a part of audio + * interface in slave mode (DOUT) + * [Si4761/63/65/67 Only] + * SI476X_ICOP_IC_LINK - set the pin to be a part of Inter-Chip link + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *core, + enum si476x_icin_config icin, + enum si476x_icip_config icip, + enum si476x_icon_config icon, + enum si476x_icop_config icop) +{ + u8 resp[CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP]; + const u8 args[CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(icin), + PIN_CFG_BYTE(icip), + PIN_CFG_BYTE(icon), + PIN_CFG_BYTE(icop), + }; + + return si476x_core_send_command(core, CMD_IC_LINK_GPO_CTL_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_ic_link_gpo_ctl_pin_cfg); + +/** + * si476x_cmd_ana_audio_pin_cfg - send 'ANA_AUDIO_PIN_CFG' to the + * device + * @core - device to send the command to + * @lrout - LROUT pin function configuration: + * SI476X_LROUT_NOOP - do not modify the behaviour + * SI476X_LROUT_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_LROUT_AUDIO - set pin to be audio output + * SI476X_LROUT_MPX - set pin to be MPX output + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *core, + enum si476x_lrout_config lrout) +{ + u8 resp[CMD_ANA_AUDIO_PIN_CFG_NRESP]; + const u8 args[CMD_ANA_AUDIO_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(lrout), + }; + + return si476x_core_send_command(core, CMD_ANA_AUDIO_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_ana_audio_pin_cfg); + + +/** + * si476x_cmd_intb_pin_cfg - send 'INTB_PIN_CFG' command to the device + * @core - device to send the command to + * @intb - INTB pin function configuration: + * SI476X_INTB_NOOP - do not modify the behaviour + * SI476X_INTB_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_INTB_DAUDIO - set pin to be a part of digital + * audio interface in slave mode + * SI476X_INTB_IRQ - set pin to be an interrupt request line + * @a1 - A1 pin function configuration: + * SI476X_A1_NOOP - do not modify the behaviour + * SI476X_A1_TRISTATE - put the pin in tristate condition, + * enable 1MOhm pulldown + * SI476X_A1_IRQ - set pin to be an interrupt request line + * + * Function returns 0 on success and negative error code on failure + */ +static int si476x_core_cmd_intb_pin_cfg_a10(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1) +{ + u8 resp[CMD_INTB_PIN_CFG_A10_NRESP]; + const u8 args[CMD_INTB_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(intb), + PIN_CFG_BYTE(a1), + }; + + return si476x_core_send_command(core, CMD_INTB_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + +static int si476x_core_cmd_intb_pin_cfg_a20(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1) +{ + u8 resp[CMD_INTB_PIN_CFG_A20_NRESP]; + const u8 args[CMD_INTB_PIN_CFG_NARGS] = { + PIN_CFG_BYTE(intb), + PIN_CFG_BYTE(a1), + }; + + return si476x_core_send_command(core, CMD_INTB_PIN_CFG, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + + + +/** + * si476x_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the + * device + * @core - device to send the command to + * @rsqack - if set command clears RSQINT, SNRINT, SNRLINT, RSSIHINT, + * RSSSILINT, BLENDINT, MULTHINT and MULTLINT + * @attune - when set the values in the status report are the values + * that were calculated at tune + * @cancel - abort ongoing seek/tune opertation + * @stcack - clear the STCINT bin in status register + * @report - all signal quality information retured by the command + * (if NULL then the output of the command is ignored) + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_am_rsq_status(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_AM_RSQ_STATUS_NRESP]; + const u8 args[CMD_AM_RSQ_STATUS_NARGS] = { + rsqargs->rsqack << 3 | rsqargs->attune << 2 | + rsqargs->cancel << 1 | rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_AM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (!report) + return err; + + report->snrhint = 0x08 & resp[1]; + report->snrlint = 0x04 & resp[1]; + report->rssihint = 0x02 & resp[1]; + report->rssilint = 0x01 & resp[1]; + + report->bltf = 0x80 & resp[2]; + report->snr_ready = 0x20 & resp[2]; + report->rssiready = 0x08 & resp[2]; + report->afcrl = 0x02 & resp[2]; + report->valid = 0x01 & resp[2]; + + report->readfreq = get_unaligned_be16(resp + 3); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_rsq_status); + +int si476x_core_cmd_fm_acf_status(struct si476x_core *core, + struct si476x_acf_status_report *report) +{ + int err; + u8 resp[CMD_FM_ACF_STATUS_NRESP]; + const u8 args[CMD_FM_ACF_STATUS_NARGS] = { + 0x0, + }; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_FM_ACF_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; + report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; + report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; + report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; + report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; + report->smute = resp[2] & SI476X_ACF_SMUTE; + report->smattn = resp[3] & SI476X_ACF_SMATTN; + report->chbw = resp[4]; + report->hicut = resp[5]; + report->hiblend = resp[6]; + report->pilot = resp[7] & SI476X_ACF_PILOT; + report->stblend = resp[7] & SI476X_ACF_STBLEND; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_acf_status); + +int si476x_core_cmd_am_acf_status(struct si476x_core *core, + struct si476x_acf_status_report *report) +{ + int err; + u8 resp[CMD_AM_ACF_STATUS_NRESP]; + const u8 args[CMD_AM_ACF_STATUS_NARGS] = { + 0x0, + }; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_AM_ACF_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; + report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; + report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; + report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; + report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; + report->smute = resp[2] & SI476X_ACF_SMUTE; + report->smattn = resp[3] & SI476X_ACF_SMATTN; + report->chbw = resp[4]; + report->hicut = resp[5]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_acf_status); + + +/** + * si476x_cmd_fm_seek_start - send 'FM_SEEK_START' command to the + * device + * @core - device to send the command to + * @seekup - if set the direction of the search is 'up' + * @wrap - if set seek wraps when hitting band limit + * + * This function begins search for a valid station. The station is + * considered valid when 'FM_VALID_SNR_THRESHOLD' and + * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria + * are met. +} * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_fm_seek_start(struct si476x_core *core, + bool seekup, bool wrap) +{ + u8 resp[CMD_FM_SEEK_START_NRESP]; + const u8 args[CMD_FM_SEEK_START_NARGS] = { + seekup << 3 | wrap << 2, + }; + + return si476x_cmd_tune_seek_freq(core, CMD_FM_SEEK_START, + args, sizeof(args), + resp, sizeof(resp)); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_seek_start); + +/** + * si476x_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the + * device + * @core - device to send the command to + * @status_only - if set the data is not removed from RDSFIFO, + * RDSFIFOUSED is not decremented and data in all the + * rest RDS data contains the last valid info received + * @mtfifo if set the command clears RDS receive FIFO + * @intack if set the command clards the RDSINT bit. + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_fm_rds_status(struct si476x_core *core, + bool status_only, + bool mtfifo, + bool intack, + struct si476x_rds_status_report *report) +{ + int err; + u8 resp[CMD_FM_RDS_STATUS_NRESP]; + const u8 args[CMD_FM_RDS_STATUS_NARGS] = { + status_only << 2 | mtfifo << 1 | intack, + }; + + err = si476x_core_send_command(core, CMD_FM_RDS_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting RDS status information this command can be + * used to just acknowledge different interrupt flags in those + * cases it is useless to copy and parse received data so user + * can pass NULL, and thus avoid unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->rdstpptyint = 0x10 & resp[1]; + report->rdspiint = 0x08 & resp[1]; + report->rdssyncint = 0x02 & resp[1]; + report->rdsfifoint = 0x01 & resp[1]; + + report->tpptyvalid = 0x10 & resp[2]; + report->pivalid = 0x08 & resp[2]; + report->rdssync = 0x02 & resp[2]; + report->rdsfifolost = 0x01 & resp[2]; + + report->tp = 0x20 & resp[3]; + report->pty = 0x1f & resp[3]; + + report->pi = get_unaligned_be16(resp + 4); + report->rdsfifoused = resp[6]; + + report->ble[V4L2_RDS_BLOCK_A] = 0xc0 & resp[7]; + report->ble[V4L2_RDS_BLOCK_B] = 0x30 & resp[7]; + report->ble[V4L2_RDS_BLOCK_C] = 0x0c & resp[7]; + report->ble[V4L2_RDS_BLOCK_D] = 0x03 & resp[7]; + + report->rds[V4L2_RDS_BLOCK_A].block = V4L2_RDS_BLOCK_A; + report->rds[V4L2_RDS_BLOCK_A].msb = resp[8]; + report->rds[V4L2_RDS_BLOCK_A].lsb = resp[9]; + + report->rds[V4L2_RDS_BLOCK_B].block = V4L2_RDS_BLOCK_B; + report->rds[V4L2_RDS_BLOCK_B].msb = resp[10]; + report->rds[V4L2_RDS_BLOCK_B].lsb = resp[11]; + + report->rds[V4L2_RDS_BLOCK_C].block = V4L2_RDS_BLOCK_C; + report->rds[V4L2_RDS_BLOCK_C].msb = resp[12]; + report->rds[V4L2_RDS_BLOCK_C].lsb = resp[13]; + + report->rds[V4L2_RDS_BLOCK_D].block = V4L2_RDS_BLOCK_D; + report->rds[V4L2_RDS_BLOCK_D].msb = resp[14]; + report->rds[V4L2_RDS_BLOCK_D].lsb = resp[15]; + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_status); + +int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core, + bool clear, + struct si476x_rds_blockcount_report *report) +{ + int err; + u8 resp[CMD_FM_RDS_BLOCKCOUNT_NRESP]; + const u8 args[CMD_FM_RDS_BLOCKCOUNT_NARGS] = { + clear, + }; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_FM_RDS_BLOCKCOUNT, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + + if (!err) { + report->expected = get_unaligned_be16(resp + 2); + report->received = get_unaligned_be16(resp + 4); + report->uncorrectable = get_unaligned_be16(resp + 6); + } + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_blockcount); + +int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core, + enum si476x_phase_diversity_mode mode) +{ + u8 resp[CMD_FM_PHASE_DIVERSITY_NRESP]; + const u8 args[CMD_FM_PHASE_DIVERSITY_NARGS] = { + mode & 0x07, + }; + + return si476x_core_send_command(core, CMD_FM_PHASE_DIVERSITY, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_diversity); +/** + * si476x_core_cmd_fm_phase_div_status() - get the phase diversity + * status + * + * @core: si476x device + * + * NOTE caller must hold core lock + * + * Function returns the value of the status bit in case of success and + * negative error code in case of failre. + */ +int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core) +{ + int err; + u8 resp[CMD_FM_PHASE_DIV_STATUS_NRESP]; + + err = si476x_core_send_command(core, CMD_FM_PHASE_DIV_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + + return (err < 0) ? err : resp[1]; +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_div_status); + + +/** + * si476x_cmd_am_seek_start - send 'FM_SEEK_START' command to the + * device + * @core - device to send the command to + * @seekup - if set the direction of the search is 'up' + * @wrap - if set seek wraps when hitting band limit + * + * This function begins search for a valid station. The station is + * considered valid when 'FM_VALID_SNR_THRESHOLD' and + * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria + * are met. + * + * Function returns 0 on success and negative error code on failure + */ +int si476x_core_cmd_am_seek_start(struct si476x_core *core, + bool seekup, bool wrap) +{ + u8 resp[CMD_AM_SEEK_START_NRESP]; + const u8 args[CMD_AM_SEEK_START_NARGS] = { + seekup << 3 | wrap << 2, + }; + + return si476x_cmd_tune_seek_freq(core, CMD_AM_SEEK_START, + args, sizeof(args), + resp, sizeof(resp)); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_seek_start); + + + +static int si476x_core_cmd_power_up_a10(struct si476x_core *core, + struct si476x_power_up_args *puargs) +{ + u8 resp[CMD_POWER_UP_A10_NRESP]; + const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); + const bool ctsen = (core->client->irq != 0); + const u8 args[CMD_POWER_UP_A10_NARGS] = { + 0xF7, /* Reserved, always 0xF7 */ + 0x3F & puargs->xcload, /* First two bits are reserved to be + * zeros */ + ctsen << 7 | intsel << 6 | 0x07, /* Last five bits + * are reserved to + * be written as 0x7 */ + puargs->func << 4 | puargs->freq, + 0x11, /* Reserved, always 0x11 */ + }; + + return si476x_core_send_command(core, CMD_POWER_UP, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_TIMEOUT_POWER_UP); +} + +static int si476x_core_cmd_power_up_a20(struct si476x_core *core, + struct si476x_power_up_args *puargs) +{ + u8 resp[CMD_POWER_UP_A20_NRESP]; + const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); + const bool ctsen = (core->client->irq != 0); + const u8 args[CMD_POWER_UP_A20_NARGS] = { + puargs->ibias6x << 7 | puargs->xstart, + 0x3F & puargs->xcload, /* First two bits are reserved to be + * zeros */ + ctsen << 7 | intsel << 6 | puargs->fastboot << 5 | + puargs->xbiashc << 3 | puargs->xbias, + puargs->func << 4 | puargs->freq, + 0x10 | puargs->xmode, + }; + + return si476x_core_send_command(core, CMD_POWER_UP, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_TIMEOUT_POWER_UP); +} + +static int si476x_core_cmd_power_down_a10(struct si476x_core *core, + struct si476x_power_down_args *pdargs) +{ + u8 resp[CMD_POWER_DOWN_A10_NRESP]; + + return si476x_core_send_command(core, CMD_POWER_DOWN, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + +static int si476x_core_cmd_power_down_a20(struct si476x_core *core, + struct si476x_power_down_args *pdargs) +{ + u8 resp[CMD_POWER_DOWN_A20_NRESP]; + const u8 args[CMD_POWER_DOWN_A20_NARGS] = { + pdargs->xosc, + }; + return si476x_core_send_command(core, CMD_POWER_DOWN, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); +} + +static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + + const int am_freq = tuneargs->freq; + u8 resp[CMD_AM_TUNE_FREQ_NRESP]; + const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { + (tuneargs->hd << 6), + msb(am_freq), + lsb(am_freq), + }; + + return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, args, + sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + const int am_freq = tuneargs->freq; + u8 resp[CMD_AM_TUNE_FREQ_NRESP]; + const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { + (tuneargs->zifsr << 6) | (tuneargs->injside & 0x03), + msb(am_freq), + lsb(am_freq), + }; + + return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, + args, sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; + const u8 args[CMD_FM_RSQ_STATUS_A10_NARGS] = { + rsqargs->rsqack << 3 | rsqargs->attune << 2 | + rsqargs->cancel << 1 | rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->multhint = 0x80 & resp[1]; + report->multlint = 0x40 & resp[1]; + report->snrhint = 0x08 & resp[1]; + report->snrlint = 0x04 & resp[1]; + report->rssihint = 0x02 & resp[1]; + report->rssilint = 0x01 & resp[1]; + + report->bltf = 0x80 & resp[2]; + report->snr_ready = 0x20 & resp[2]; + report->rssiready = 0x08 & resp[2]; + report->afcrl = 0x02 & resp[2]; + report->valid = 0x01 & resp[2]; + + report->readfreq = get_unaligned_be16(resp + 3); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + report->readantcap = get_unaligned_be16(resp + 13); + report->assi = resp[15]; + report->usn = resp[16]; + + return err; +} + +static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; + const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { + rsqargs->primary << 4 | rsqargs->rsqack << 3 | + rsqargs->attune << 2 | rsqargs->cancel << 1 | + rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->multhint = 0x80 & resp[1]; + report->multlint = 0x40 & resp[1]; + report->snrhint = 0x08 & resp[1]; + report->snrlint = 0x04 & resp[1]; + report->rssihint = 0x02 & resp[1]; + report->rssilint = 0x01 & resp[1]; + + report->bltf = 0x80 & resp[2]; + report->snr_ready = 0x20 & resp[2]; + report->rssiready = 0x08 & resp[2]; + report->afcrl = 0x02 & resp[2]; + report->valid = 0x01 & resp[2]; + + report->readfreq = get_unaligned_be16(resp + 3); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + report->readantcap = get_unaligned_be16(resp + 13); + report->assi = resp[15]; + report->usn = resp[16]; + + return err; +} + + +static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core, + struct si476x_rsq_status_args *rsqargs, + struct si476x_rsq_status_report *report) +{ + int err; + u8 resp[CMD_FM_RSQ_STATUS_A30_NRESP]; + const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { + rsqargs->primary << 4 | rsqargs->rsqack << 3 | + rsqargs->attune << 2 | rsqargs->cancel << 1 | + rsqargs->stcack, + }; + + err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, + args, ARRAY_SIZE(args), + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + /* + * Besides getting received signal quality information this + * command can be used to just acknowledge different interrupt + * flags in those cases it is useless to copy and parse + * received data so user can pass NULL, and thus avoid + * unnecessary copying. + */ + if (err < 0 || report == NULL) + return err; + + report->multhint = 0x80 & resp[1]; + report->multlint = 0x40 & resp[1]; + report->snrhint = 0x08 & resp[1]; + report->snrlint = 0x04 & resp[1]; + report->rssihint = 0x02 & resp[1]; + report->rssilint = 0x01 & resp[1]; + + report->bltf = 0x80 & resp[2]; + report->snr_ready = 0x20 & resp[2]; + report->rssiready = 0x08 & resp[2]; + report->injside = 0x04 & resp[2]; + report->afcrl = 0x02 & resp[2]; + report->valid = 0x01 & resp[2]; + + report->readfreq = get_unaligned_be16(resp + 3); + report->freqoff = resp[5]; + report->rssi = resp[6]; + report->snr = resp[7]; + report->issi = resp[8]; + report->lassi = resp[9]; + report->hassi = resp[10]; + report->mult = resp[11]; + report->dev = resp[12]; + report->readantcap = get_unaligned_be16(resp + 13); + report->assi = resp[15]; + report->usn = resp[16]; + + report->pilotdev = resp[17]; + report->rdsdev = resp[18]; + report->assidev = resp[19]; + report->strongdev = resp[20]; + report->rdspi = get_unaligned_be16(resp + 21); + + return err; +} + +static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + u8 resp[CMD_FM_TUNE_FREQ_NRESP]; + const u8 args[CMD_FM_TUNE_FREQ_A10_NARGS] = { + (tuneargs->hd << 6) | (tuneargs->tunemode << 4) + | (tuneargs->smoothmetrics << 2), + msb(tuneargs->freq), + lsb(tuneargs->freq), + msb(tuneargs->antcap), + lsb(tuneargs->antcap) + }; + + return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, + args, sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs) +{ + u8 resp[CMD_FM_TUNE_FREQ_NRESP]; + const u8 args[CMD_FM_TUNE_FREQ_A20_NARGS] = { + (tuneargs->hd << 6) | (tuneargs->tunemode << 4) + | (tuneargs->smoothmetrics << 2) | (tuneargs->injside), + msb(tuneargs->freq), + lsb(tuneargs->freq), + }; + + return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, + args, sizeof(args), + resp, sizeof(resp)); +} + +static int si476x_core_cmd_agc_status_a20(struct si476x_core *core, + struct si476x_agc_status_report *report) +{ + int err; + u8 resp[CMD_AGC_STATUS_NRESP_A20]; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_AGC_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->mxhi = resp[1] & SI476X_AGC_MXHI; + report->mxlo = resp[1] & SI476X_AGC_MXLO; + report->lnahi = resp[1] & SI476X_AGC_LNAHI; + report->lnalo = resp[1] & SI476X_AGC_LNALO; + report->fmagc1 = resp[2]; + report->fmagc2 = resp[3]; + report->pgagain = resp[4]; + report->fmwblang = resp[5]; + + return err; +} + +static int si476x_core_cmd_agc_status_a10(struct si476x_core *core, + struct si476x_agc_status_report *report) +{ + int err; + u8 resp[CMD_AGC_STATUS_NRESP_A10]; + + if (!report) + return -EINVAL; + + err = si476x_core_send_command(core, CMD_AGC_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + SI476X_DEFAULT_TIMEOUT); + if (err < 0) + return err; + + report->mxhi = resp[1] & SI476X_AGC_MXHI; + report->mxlo = resp[1] & SI476X_AGC_MXLO; + report->lnahi = resp[1] & SI476X_AGC_LNAHI; + report->lnalo = resp[1] & SI476X_AGC_LNALO; + + return err; +} + +typedef int (*tune_freq_func_t) (struct si476x_core *core, + struct si476x_tune_freq_args *tuneargs); + +static struct { + int (*power_up)(struct si476x_core *, + struct si476x_power_up_args *); + int (*power_down)(struct si476x_core *, + struct si476x_power_down_args *); + + tune_freq_func_t fm_tune_freq; + tune_freq_func_t am_tune_freq; + + int (*fm_rsq_status)(struct si476x_core *, + struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); + int (*intb_pin_cfg)(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1); +} si476x_cmds_vtable[] = { + [SI476X_REVISION_A10] = { + .power_up = si476x_core_cmd_power_up_a10, + .power_down = si476x_core_cmd_power_down_a10, + .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a10, + .am_tune_freq = si476x_core_cmd_am_tune_freq_a10, + .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a10, + .agc_status = si476x_core_cmd_agc_status_a10, + .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a10, + }, + [SI476X_REVISION_A20] = { + .power_up = si476x_core_cmd_power_up_a20, + .power_down = si476x_core_cmd_power_down_a20, + .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, + .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, + .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a20, + .agc_status = si476x_core_cmd_agc_status_a20, + .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, + }, + [SI476X_REVISION_A30] = { + .power_up = si476x_core_cmd_power_up_a20, + .power_down = si476x_core_cmd_power_down_a20, + .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, + .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, + .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a30, + .agc_status = si476x_core_cmd_agc_status_a20, + .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, + }, +}; + +int si476x_core_cmd_power_up(struct si476x_core *core, + struct si476x_power_up_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].power_up(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_power_up); + +int si476x_core_cmd_power_down(struct si476x_core *core, + struct si476x_power_down_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].power_down(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_power_down); + +int si476x_core_cmd_fm_tune_freq(struct si476x_core *core, + struct si476x_tune_freq_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].fm_tune_freq(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_tune_freq); + +int si476x_core_cmd_am_tune_freq(struct si476x_core *core, + struct si476x_tune_freq_args *args) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].am_tune_freq(core, args); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_am_tune_freq); + +int si476x_core_cmd_fm_rsq_status(struct si476x_core *core, + struct si476x_rsq_status_args *args, + struct si476x_rsq_status_report *report) + +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].fm_rsq_status(core, args, + report); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rsq_status); + +int si476x_core_cmd_agc_status(struct si476x_core *core, + struct si476x_agc_status_report *report) + +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return si476x_cmds_vtable[core->revision].agc_status(core, report); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_agc_status); + +int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core, + enum si476x_intb_config intb, + enum si476x_a1_config a1) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + + return si476x_cmds_vtable[core->revision].intb_pin_cfg(core, intb, a1); +} +EXPORT_SYMBOL_GPL(si476x_core_cmd_intb_pin_cfg); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("API for command exchange for si476x"); diff --git a/kernel/drivers/mfd/si476x-i2c.c b/kernel/drivers/mfd/si476x-i2c.c new file mode 100644 index 000000000..7f87c62d9 --- /dev/null +++ b/kernel/drivers/mfd/si476x-i2c.c @@ -0,0 +1,886 @@ +/* + * drivers/mfd/si476x-i2c.c -- Core device driver for si476x MFD + * device + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@gmail.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; version 2 of the License. + * + * 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. + * + */ +#include <linux/module.h> + +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/err.h> + +#include <linux/mfd/si476x-core.h> + +#define SI476X_MAX_IO_ERRORS 10 +#define SI476X_DRIVER_RDS_FIFO_DEPTH 128 + +/** + * si476x_core_config_pinmux() - pin function configuration function + * + * @core: Core device structure + * + * Configure the functions of the pins of the radio chip. + * + * The function returns zero in case of succes or negative error code + * otherwise. + */ +static int si476x_core_config_pinmux(struct si476x_core *core) +{ + int err; + dev_dbg(&core->client->dev, "Configuring pinmux\n"); + err = si476x_core_cmd_dig_audio_pin_cfg(core, + core->pinmux.dclk, + core->pinmux.dfs, + core->pinmux.dout, + core->pinmux.xout); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure digital audio pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_zif_pin_cfg(core, + core->pinmux.iqclk, + core->pinmux.iqfs, + core->pinmux.iout, + core->pinmux.qout); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure ZIF pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(core, + core->pinmux.icin, + core->pinmux.icip, + core->pinmux.icon, + core->pinmux.icop); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure IC-Link/GPO pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_ana_audio_pin_cfg(core, + core->pinmux.lrout); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure analog audio pins(err = %d)\n", + err); + return err; + } + + err = si476x_core_cmd_intb_pin_cfg(core, + core->pinmux.intb, + core->pinmux.a1); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure interrupt pins(err = %d)\n", + err); + return err; + } + + return 0; +} + +static inline void si476x_core_schedule_polling_work(struct si476x_core *core) +{ + schedule_delayed_work(&core->status_monitor, + usecs_to_jiffies(SI476X_STATUS_POLL_US)); +} + +/** + * si476x_core_start() - early chip startup function + * @core: Core device structure + * @soft: When set, this flag forces "soft" startup, where "soft" + * power down is the one done by sending appropriate command instead + * of using reset pin of the tuner + * + * Perform required startup sequence to correctly power + * up the chip and perform initial configuration. It does the + * following sequence of actions: + * 1. Claims and enables the power supplies VD and VIO1 required + * for I2C interface of the chip operation. + * 2. Waits for 100us, pulls the reset line up, enables irq, + * waits for another 100us as it is specified by the + * datasheet. + * 3. Sends 'POWER_UP' command to the device with all provided + * information about power-up parameters. + * 4. Configures, pin multiplexor, disables digital audio and + * configures interrupt sources. + * + * The function returns zero in case of succes or negative error code + * otherwise. + */ +int si476x_core_start(struct si476x_core *core, bool soft) +{ + struct i2c_client *client = core->client; + int err; + + if (!soft) { + if (gpio_is_valid(core->gpio_reset)) + gpio_set_value_cansleep(core->gpio_reset, 1); + + if (client->irq) + enable_irq(client->irq); + + udelay(100); + + if (!client->irq) { + atomic_set(&core->is_alive, 1); + si476x_core_schedule_polling_work(core); + } + } else { + if (client->irq) + enable_irq(client->irq); + else { + atomic_set(&core->is_alive, 1); + si476x_core_schedule_polling_work(core); + } + } + + err = si476x_core_cmd_power_up(core, + &core->power_up_parameters); + + if (err < 0) { + dev_err(&core->client->dev, + "Power up failure(err = %d)\n", + err); + goto disable_irq; + } + + if (client->irq) + atomic_set(&core->is_alive, 1); + + err = si476x_core_config_pinmux(core); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure pinmux(err = %d)\n", + err); + goto disable_irq; + } + + if (client->irq) { + err = regmap_write(core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_RDSIEN | + SI476X_STCIEN | + SI476X_CTSIEN); + if (err < 0) { + dev_err(&core->client->dev, + "Failed to configure interrupt sources" + "(err = %d)\n", err); + goto disable_irq; + } + } + + return 0; + +disable_irq: + if (err == -ENODEV) + atomic_set(&core->is_alive, 0); + + if (client->irq) + disable_irq(client->irq); + else + cancel_delayed_work_sync(&core->status_monitor); + + if (gpio_is_valid(core->gpio_reset)) + gpio_set_value_cansleep(core->gpio_reset, 0); + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_start); + +/** + * si476x_core_stop() - chip power-down function + * @core: Core device structure + * @soft: When set, function sends a POWER_DOWN command instead of + * bringing reset line low + * + * Power down the chip by performing following actions: + * 1. Disable IRQ or stop the polling worker + * 2. Send the POWER_DOWN command if the power down is soft or bring + * reset line low if not. + * + * The function returns zero in case of succes or negative error code + * otherwise. + */ +int si476x_core_stop(struct si476x_core *core, bool soft) +{ + int err = 0; + atomic_set(&core->is_alive, 0); + + if (soft) { + /* TODO: This probably shoud be a configurable option, + * so it is possible to have the chips keep their + * oscillators running + */ + struct si476x_power_down_args args = { + .xosc = false, + }; + err = si476x_core_cmd_power_down(core, &args); + } + + /* We couldn't disable those before + * 'si476x_core_cmd_power_down' since we expect to get CTS + * interrupt */ + if (core->client->irq) + disable_irq(core->client->irq); + else + cancel_delayed_work_sync(&core->status_monitor); + + if (!soft) { + if (gpio_is_valid(core->gpio_reset)) + gpio_set_value_cansleep(core->gpio_reset, 0); + } + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_stop); + +/** + * si476x_core_set_power_state() - set the level at which the power is + * supplied for the chip. + * @core: Core device structure + * @next_state: enum si476x_power_state describing power state to + * switch to. + * + * Switch on all the required power supplies + * + * This function returns 0 in case of suvccess and negative error code + * otherwise. + */ +int si476x_core_set_power_state(struct si476x_core *core, + enum si476x_power_state next_state) +{ + /* + It is not clear form the datasheet if it is possible to + work with device if not all power domains are operational. + So for now the power-up policy is "power-up all the things!" + */ + int err = 0; + + if (core->power_state == SI476X_POWER_INCONSISTENT) { + dev_err(&core->client->dev, + "The device in inconsistent power state\n"); + return -EINVAL; + } + + if (next_state != core->power_state) { + switch (next_state) { + case SI476X_POWER_UP_FULL: + err = regulator_bulk_enable(ARRAY_SIZE(core->supplies), + core->supplies); + if (err < 0) { + core->power_state = SI476X_POWER_INCONSISTENT; + break; + } + /* + * Startup timing diagram recommends to have a + * 100 us delay between enabling of the power + * supplies and turning the tuner on. + */ + udelay(100); + + err = si476x_core_start(core, false); + if (err < 0) + goto disable_regulators; + + core->power_state = next_state; + break; + + case SI476X_POWER_DOWN: + core->power_state = next_state; + err = si476x_core_stop(core, false); + if (err < 0) + core->power_state = SI476X_POWER_INCONSISTENT; +disable_regulators: + err = regulator_bulk_disable(ARRAY_SIZE(core->supplies), + core->supplies); + if (err < 0) + core->power_state = SI476X_POWER_INCONSISTENT; + break; + default: + BUG(); + } + } + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_set_power_state); + +/** + * si476x_core_report_drainer_stop() - mark the completion of the RDS + * buffer drain porcess by the worker. + * + * @core: Core device structure + */ +static inline void si476x_core_report_drainer_stop(struct si476x_core *core) +{ + mutex_lock(&core->rds_drainer_status_lock); + core->rds_drainer_is_working = false; + mutex_unlock(&core->rds_drainer_status_lock); +} + +/** + * si476x_core_start_rds_drainer_once() - start RDS drainer worker if + * ther is none working, do nothing otherwise + * + * @core: Datastructure corresponding to the chip. + */ +static inline void si476x_core_start_rds_drainer_once(struct si476x_core *core) +{ + mutex_lock(&core->rds_drainer_status_lock); + if (!core->rds_drainer_is_working) { + core->rds_drainer_is_working = true; + schedule_work(&core->rds_fifo_drainer); + } + mutex_unlock(&core->rds_drainer_status_lock); +} +/** + * si476x_drain_rds_fifo() - RDS buffer drainer. + * @work: struct work_struct being ppassed to the function by the + * kernel. + * + * Drain the contents of the RDS FIFO of + */ +static void si476x_core_drain_rds_fifo(struct work_struct *work) +{ + int err; + + struct si476x_core *core = container_of(work, struct si476x_core, + rds_fifo_drainer); + + struct si476x_rds_status_report report; + + si476x_core_lock(core); + err = si476x_core_cmd_fm_rds_status(core, true, false, false, &report); + if (!err) { + int i = report.rdsfifoused; + dev_dbg(&core->client->dev, + "%d elements in RDS FIFO. Draining.\n", i); + for (; i > 0; --i) { + err = si476x_core_cmd_fm_rds_status(core, false, false, + (i == 1), &report); + if (err < 0) + goto unlock; + + kfifo_in(&core->rds_fifo, report.rds, + sizeof(report.rds)); + dev_dbg(&core->client->dev, "RDS data:\n %*ph\n", + (int)sizeof(report.rds), report.rds); + } + dev_dbg(&core->client->dev, "Drrrrained!\n"); + wake_up_interruptible(&core->rds_read_queue); + } + +unlock: + si476x_core_unlock(core); + si476x_core_report_drainer_stop(core); +} + +/** + * si476x_core_pronounce_dead() + * + * @core: Core device structure + * + * Mark the device as being dead and wake up all potentially waiting + * threads of execution. + * + */ +static void si476x_core_pronounce_dead(struct si476x_core *core) +{ + dev_info(&core->client->dev, "Core device is dead.\n"); + + atomic_set(&core->is_alive, 0); + + /* Wake up al possible waiting processes */ + wake_up_interruptible(&core->rds_read_queue); + + atomic_set(&core->cts, 1); + wake_up(&core->command); + + atomic_set(&core->stc, 1); + wake_up(&core->tuning); +} + +/** + * si476x_core_i2c_xfer() + * + * @core: Core device structure + * @type: Transfer type + * @buf: Transfer buffer for/with data + * @count: Transfer buffer size + * + * Perfrom and I2C transfer(either read or write) and keep a counter + * of I/O errors. If the error counter rises above the threshold + * pronounce device dead. + * + * The function returns zero on succes or negative error code on + * failure. + */ +int si476x_core_i2c_xfer(struct si476x_core *core, + enum si476x_i2c_type type, + char *buf, int count) +{ + static int io_errors_count; + int err; + if (type == SI476X_I2C_SEND) + err = i2c_master_send(core->client, buf, count); + else + err = i2c_master_recv(core->client, buf, count); + + if (err < 0) { + if (io_errors_count++ > SI476X_MAX_IO_ERRORS) + si476x_core_pronounce_dead(core); + } else { + io_errors_count = 0; + } + + return err; +} +EXPORT_SYMBOL_GPL(si476x_core_i2c_xfer); + +/** + * si476x_get_status() + * @core: Core device structure + * + * Get the status byte of the core device by berforming one byte I2C + * read. + * + * The function returns a status value or a negative error code on + * error. + */ +static int si476x_core_get_status(struct si476x_core *core) +{ + u8 response; + int err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, + &response, sizeof(response)); + + return (err < 0) ? err : response; +} + +/** + * si476x_get_and_signal_status() - IRQ dispatcher + * @core: Core device structure + * + * Dispatch the arrived interrupt request based on the value of the + * status byte reported by the tuner. + * + */ +static void si476x_core_get_and_signal_status(struct si476x_core *core) +{ + int status = si476x_core_get_status(core); + if (status < 0) { + dev_err(&core->client->dev, "Failed to get status\n"); + return; + } + + if (status & SI476X_CTS) { + /* Unfortunately completions could not be used for + * signalling CTS since this flag cannot be cleared + * in status byte, and therefore once it becomes true + * multiple calls to 'complete' would cause the + * commands following the current one to be completed + * before they actually are */ + dev_dbg(&core->client->dev, "[interrupt] CTSINT\n"); + atomic_set(&core->cts, 1); + wake_up(&core->command); + } + + if (status & SI476X_FM_RDS_INT) { + dev_dbg(&core->client->dev, "[interrupt] RDSINT\n"); + si476x_core_start_rds_drainer_once(core); + } + + if (status & SI476X_STC_INT) { + dev_dbg(&core->client->dev, "[interrupt] STCINT\n"); + atomic_set(&core->stc, 1); + wake_up(&core->tuning); + } +} + +static void si476x_core_poll_loop(struct work_struct *work) +{ + struct si476x_core *core = SI476X_WORK_TO_CORE(work); + + si476x_core_get_and_signal_status(core); + + if (atomic_read(&core->is_alive)) + si476x_core_schedule_polling_work(core); +} + +static irqreturn_t si476x_core_interrupt(int irq, void *dev) +{ + struct si476x_core *core = dev; + + si476x_core_get_and_signal_status(core); + + return IRQ_HANDLED; +} + +/** + * si476x_firmware_version_to_revision() + * @core: Core device structure + * @major: Firmware major number + * @minor1: Firmware first minor number + * @minor2: Firmware second minor number + * + * Convert a chip's firmware version number into an offset that later + * will be used to as offset in "vtable" of tuner functions + * + * This function returns a positive offset in case of success and a -1 + * in case of failure. + */ +static int si476x_core_fwver_to_revision(struct si476x_core *core, + int func, int major, + int minor1, int minor2) +{ + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + switch (major) { + case 5: + return SI476X_REVISION_A10; + case 8: + return SI476X_REVISION_A20; + case 10: + return SI476X_REVISION_A30; + default: + goto unknown_revision; + } + case SI476X_FUNC_AM_RECEIVER: + switch (major) { + case 5: + return SI476X_REVISION_A10; + case 7: + return SI476X_REVISION_A20; + case 9: + return SI476X_REVISION_A30; + default: + goto unknown_revision; + } + case SI476X_FUNC_WB_RECEIVER: + switch (major) { + case 3: + return SI476X_REVISION_A10; + case 5: + return SI476X_REVISION_A20; + case 7: + return SI476X_REVISION_A30; + default: + goto unknown_revision; + } + case SI476X_FUNC_BOOTLOADER: + default: /* FALLTHROUG */ + BUG(); + return -1; + } + +unknown_revision: + dev_err(&core->client->dev, + "Unsupported version of the firmware: %d.%d.%d, " + "reverting to A10 comptible functions\n", + major, minor1, minor2); + + return SI476X_REVISION_A10; +} + +/** + * si476x_get_revision_info() + * @core: Core device structure + * + * Get the firmware version number of the device. It is done in + * following three steps: + * 1. Power-up the device + * 2. Send the 'FUNC_INFO' command + * 3. Powering the device down. + * + * The function return zero on success and a negative error code on + * failure. + */ +static int si476x_core_get_revision_info(struct si476x_core *core) +{ + int rval; + struct si476x_func_info info; + + si476x_core_lock(core); + rval = si476x_core_set_power_state(core, SI476X_POWER_UP_FULL); + if (rval < 0) + goto exit; + + rval = si476x_core_cmd_func_info(core, &info); + if (rval < 0) + goto power_down; + + core->revision = si476x_core_fwver_to_revision(core, info.func, + info.firmware.major, + info.firmware.minor[0], + info.firmware.minor[1]); +power_down: + si476x_core_set_power_state(core, SI476X_POWER_DOWN); +exit: + si476x_core_unlock(core); + + return rval; +} + +bool si476x_core_has_am(struct si476x_core *core) +{ + return core->chip_id == SI476X_CHIP_SI4761 || + core->chip_id == SI476X_CHIP_SI4764; +} +EXPORT_SYMBOL_GPL(si476x_core_has_am); + +bool si476x_core_has_diversity(struct si476x_core *core) +{ + return core->chip_id == SI476X_CHIP_SI4764; +} +EXPORT_SYMBOL_GPL(si476x_core_has_diversity); + +bool si476x_core_is_a_secondary_tuner(struct si476x_core *core) +{ + return si476x_core_has_diversity(core) && + (core->diversity_mode == SI476X_PHDIV_SECONDARY_ANTENNA || + core->diversity_mode == SI476X_PHDIV_SECONDARY_COMBINING); +} +EXPORT_SYMBOL_GPL(si476x_core_is_a_secondary_tuner); + +bool si476x_core_is_a_primary_tuner(struct si476x_core *core) +{ + return si476x_core_has_diversity(core) && + (core->diversity_mode == SI476X_PHDIV_PRIMARY_ANTENNA || + core->diversity_mode == SI476X_PHDIV_PRIMARY_COMBINING); +} +EXPORT_SYMBOL_GPL(si476x_core_is_a_primary_tuner); + +bool si476x_core_is_in_am_receiver_mode(struct si476x_core *core) +{ + return si476x_core_has_am(core) && + (core->power_up_parameters.func == SI476X_FUNC_AM_RECEIVER); +} +EXPORT_SYMBOL_GPL(si476x_core_is_in_am_receiver_mode); + +bool si476x_core_is_powered_up(struct si476x_core *core) +{ + return core->power_state == SI476X_POWER_UP_FULL; +} +EXPORT_SYMBOL_GPL(si476x_core_is_powered_up); + +static int si476x_core_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rval; + struct si476x_core *core; + struct si476x_platform_data *pdata; + struct mfd_cell *cell; + int cell_num; + + core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL); + if (!core) { + dev_err(&client->dev, + "failed to allocate 'struct si476x_core'\n"); + return -ENOMEM; + } + core->client = client; + + core->regmap = devm_regmap_init_si476x(core); + if (IS_ERR(core->regmap)) { + rval = PTR_ERR(core->regmap); + dev_err(&client->dev, + "Failed to allocate register map: %d\n", + rval); + return rval; + } + + i2c_set_clientdata(client, core); + + atomic_set(&core->is_alive, 0); + core->power_state = SI476X_POWER_DOWN; + + pdata = dev_get_platdata(&client->dev); + if (pdata) { + memcpy(&core->power_up_parameters, + &pdata->power_up_parameters, + sizeof(core->power_up_parameters)); + + core->gpio_reset = -1; + if (gpio_is_valid(pdata->gpio_reset)) { + rval = gpio_request(pdata->gpio_reset, "si476x reset"); + if (rval) { + dev_err(&client->dev, + "Failed to request gpio: %d\n", rval); + return rval; + } + core->gpio_reset = pdata->gpio_reset; + gpio_direction_output(core->gpio_reset, 0); + } + + core->diversity_mode = pdata->diversity_mode; + memcpy(&core->pinmux, &pdata->pinmux, + sizeof(struct si476x_pinmux)); + } else { + dev_err(&client->dev, "No platform data provided\n"); + return -EINVAL; + } + + core->supplies[0].supply = "vd"; + core->supplies[1].supply = "va"; + core->supplies[2].supply = "vio1"; + core->supplies[3].supply = "vio2"; + + rval = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(core->supplies), + core->supplies); + if (rval) { + dev_err(&client->dev, "Failet to gett all of the regulators\n"); + goto free_gpio; + } + + mutex_init(&core->cmd_lock); + init_waitqueue_head(&core->command); + init_waitqueue_head(&core->tuning); + + rval = kfifo_alloc(&core->rds_fifo, + SI476X_DRIVER_RDS_FIFO_DEPTH * + sizeof(struct v4l2_rds_data), + GFP_KERNEL); + if (rval) { + dev_err(&client->dev, "Could not allocate the FIFO\n"); + goto free_gpio; + } + mutex_init(&core->rds_drainer_status_lock); + init_waitqueue_head(&core->rds_read_queue); + INIT_WORK(&core->rds_fifo_drainer, si476x_core_drain_rds_fifo); + + if (client->irq) { + rval = devm_request_threaded_irq(&client->dev, + client->irq, NULL, + si476x_core_interrupt, + IRQF_TRIGGER_FALLING, + client->name, core); + if (rval < 0) { + dev_err(&client->dev, "Could not request IRQ %d\n", + client->irq); + goto free_kfifo; + } + disable_irq(client->irq); + dev_dbg(&client->dev, "IRQ requested.\n"); + + core->rds_fifo_depth = 20; + } else { + INIT_DELAYED_WORK(&core->status_monitor, + si476x_core_poll_loop); + dev_info(&client->dev, + "No IRQ number specified, will use polling\n"); + + core->rds_fifo_depth = 5; + } + + core->chip_id = id->driver_data; + + rval = si476x_core_get_revision_info(core); + if (rval < 0) { + rval = -ENODEV; + goto free_kfifo; + } + + cell_num = 0; + + cell = &core->cells[SI476X_RADIO_CELL]; + cell->name = "si476x-radio"; + cell_num++; + +#ifdef CONFIG_SND_SOC_SI476X + if ((core->chip_id == SI476X_CHIP_SI4761 || + core->chip_id == SI476X_CHIP_SI4764) && + core->pinmux.dclk == SI476X_DCLK_DAUDIO && + core->pinmux.dfs == SI476X_DFS_DAUDIO && + core->pinmux.dout == SI476X_DOUT_I2S_OUTPUT && + core->pinmux.xout == SI476X_XOUT_TRISTATE) { + cell = &core->cells[SI476X_CODEC_CELL]; + cell->name = "si476x-codec"; + cell_num++; + } +#endif + rval = mfd_add_devices(&client->dev, + (client->adapter->nr << 8) + client->addr, + core->cells, cell_num, + NULL, 0, NULL); + if (!rval) + return 0; + +free_kfifo: + kfifo_free(&core->rds_fifo); + +free_gpio: + if (gpio_is_valid(core->gpio_reset)) + gpio_free(core->gpio_reset); + + return rval; +} + +static int si476x_core_remove(struct i2c_client *client) +{ + struct si476x_core *core = i2c_get_clientdata(client); + + si476x_core_pronounce_dead(core); + mfd_remove_devices(&client->dev); + + if (client->irq) + disable_irq(client->irq); + else + cancel_delayed_work_sync(&core->status_monitor); + + kfifo_free(&core->rds_fifo); + + if (gpio_is_valid(core->gpio_reset)) + gpio_free(core->gpio_reset); + + return 0; +} + + +static const struct i2c_device_id si476x_id[] = { + { "si4761", SI476X_CHIP_SI4761 }, + { "si4764", SI476X_CHIP_SI4764 }, + { "si4768", SI476X_CHIP_SI4768 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, si476x_id); + +static struct i2c_driver si476x_core_driver = { + .driver = { + .name = "si476x-core", + .owner = THIS_MODULE, + }, + .probe = si476x_core_probe, + .remove = si476x_core_remove, + .id_table = si476x_id, +}; +module_i2c_driver(si476x_core_driver); + + +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("Si4761/64/68 AM/FM MFD core device driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/si476x-prop.c b/kernel/drivers/mfd/si476x-prop.c new file mode 100644 index 000000000..cfeffa6e1 --- /dev/null +++ b/kernel/drivers/mfd/si476x-prop.c @@ -0,0 +1,241 @@ +/* + * drivers/mfd/si476x-prop.c -- Subroutines to access + * properties of si476x chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@gmail.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; version 2 of the License. + * + * 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. + */ +#include <linux/module.h> + +#include <linux/mfd/si476x-core.h> + +struct si476x_property_range { + u16 low, high; +}; + +static bool si476x_core_element_is_in_array(u16 element, + const u16 array[], + size_t size) +{ + int i; + + for (i = 0; i < size; i++) + if (element == array[i]) + return true; + + return false; +} + +static bool si476x_core_element_is_in_range(u16 element, + const struct si476x_property_range range[], + size_t size) +{ + int i; + + for (i = 0; i < size; i++) + if (element <= range[i].high && element >= range[i].low) + return true; + + return false; +} + +static bool si476x_core_is_valid_property_a10(struct si476x_core *core, + u16 property) +{ + static const u16 valid_properties[] = { + 0x0000, + 0x0500, 0x0501, + 0x0600, + 0x0709, 0x070C, 0x070D, 0x70E, 0x710, + 0x0718, + 0x1207, 0x1208, + 0x2007, + 0x2300, + }; + + static const struct si476x_property_range valid_ranges[] = { + { 0x0200, 0x0203 }, + { 0x0300, 0x0303 }, + { 0x0400, 0x0404 }, + { 0x0700, 0x0707 }, + { 0x1100, 0x1102 }, + { 0x1200, 0x1204 }, + { 0x1300, 0x1306 }, + { 0x2000, 0x2005 }, + { 0x2100, 0x2104 }, + { 0x2106, 0x2106 }, + { 0x2200, 0x220E }, + { 0x3100, 0x3104 }, + { 0x3207, 0x320F }, + { 0x3300, 0x3304 }, + { 0x3500, 0x3517 }, + { 0x3600, 0x3617 }, + { 0x3700, 0x3717 }, + { 0x4000, 0x4003 }, + }; + + return si476x_core_element_is_in_range(property, valid_ranges, + ARRAY_SIZE(valid_ranges)) || + si476x_core_element_is_in_array(property, valid_properties, + ARRAY_SIZE(valid_properties)); +} + +static bool si476x_core_is_valid_property_a20(struct si476x_core *core, + u16 property) +{ + static const u16 valid_properties[] = { + 0x071B, + 0x1006, + 0x2210, + 0x3401, + }; + + static const struct si476x_property_range valid_ranges[] = { + { 0x2215, 0x2219 }, + }; + + return si476x_core_is_valid_property_a10(core, property) || + si476x_core_element_is_in_range(property, valid_ranges, + ARRAY_SIZE(valid_ranges)) || + si476x_core_element_is_in_array(property, valid_properties, + ARRAY_SIZE(valid_properties)); +} + +static bool si476x_core_is_valid_property_a30(struct si476x_core *core, + u16 property) +{ + static const u16 valid_properties[] = { + 0x071C, 0x071D, + 0x1007, 0x1008, + 0x220F, 0x2214, + 0x2301, + 0x3105, 0x3106, + 0x3402, + }; + + static const struct si476x_property_range valid_ranges[] = { + { 0x0405, 0x0411 }, + { 0x2008, 0x200B }, + { 0x2220, 0x2223 }, + { 0x3100, 0x3106 }, + }; + + return si476x_core_is_valid_property_a20(core, property) || + si476x_core_element_is_in_range(property, valid_ranges, + ARRAY_SIZE(valid_ranges)) || + si476x_core_element_is_in_array(property, valid_properties, + ARRAY_SIZE(valid_properties)); +} + +typedef bool (*valid_property_pred_t) (struct si476x_core *, u16); + +static bool si476x_core_is_valid_property(struct si476x_core *core, + u16 property) +{ + static const valid_property_pred_t is_valid_property[] = { + [SI476X_REVISION_A10] = si476x_core_is_valid_property_a10, + [SI476X_REVISION_A20] = si476x_core_is_valid_property_a20, + [SI476X_REVISION_A30] = si476x_core_is_valid_property_a30, + }; + + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + return is_valid_property[core->revision](core, property); +} + + +static bool si476x_core_is_readonly_property(struct si476x_core *core, + u16 property) +{ + BUG_ON(core->revision > SI476X_REVISION_A30 || + core->revision == -1); + + switch (core->revision) { + case SI476X_REVISION_A10: + return (property == 0x3200); + case SI476X_REVISION_A20: + return (property == 0x1006 || + property == 0x2210 || + property == 0x3200); + case SI476X_REVISION_A30: + return false; + } + + return false; +} + +static bool si476x_core_regmap_readable_register(struct device *dev, + unsigned int reg) +{ + struct i2c_client *client = to_i2c_client(dev); + struct si476x_core *core = i2c_get_clientdata(client); + + return si476x_core_is_valid_property(core, (u16) reg); + +} + +static bool si476x_core_regmap_writable_register(struct device *dev, + unsigned int reg) +{ + struct i2c_client *client = to_i2c_client(dev); + struct si476x_core *core = i2c_get_clientdata(client); + + return si476x_core_is_valid_property(core, (u16) reg) && + !si476x_core_is_readonly_property(core, (u16) reg); +} + + +static int si476x_core_regmap_write(void *context, unsigned int reg, + unsigned int val) +{ + return si476x_core_cmd_set_property(context, reg, val); +} + +static int si476x_core_regmap_read(void *context, unsigned int reg, + unsigned *val) +{ + struct si476x_core *core = context; + int err; + + err = si476x_core_cmd_get_property(core, reg); + if (err < 0) + return err; + + *val = err; + + return 0; +} + + +static const struct regmap_config si476x_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = 0x4003, + + .writeable_reg = si476x_core_regmap_writable_register, + .readable_reg = si476x_core_regmap_readable_register, + + .reg_read = si476x_core_regmap_read, + .reg_write = si476x_core_regmap_write, + + .cache_type = REGCACHE_RBTREE, +}; + +struct regmap *devm_regmap_init_si476x(struct si476x_core *core) +{ + return devm_regmap_init(&core->client->dev, NULL, + core, &si476x_regmap_config); +} +EXPORT_SYMBOL_GPL(devm_regmap_init_si476x); diff --git a/kernel/drivers/mfd/sky81452.c b/kernel/drivers/mfd/sky81452.c new file mode 100644 index 000000000..b0c9b0415 --- /dev/null +++ b/kernel/drivers/mfd/sky81452.c @@ -0,0 +1,108 @@ +/* + * sky81452.c SKY81452 MFD driver + * + * Copyright 2014 Skyworks Solutions Inc. + * Author : Gyungoh Yoo <jack.yoo@skyworksinc.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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/mfd/core.h> +#include <linux/mfd/sky81452.h> + +static const struct regmap_config sky81452_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int sky81452_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + const struct sky81452_platform_data *pdata = dev_get_platdata(dev); + struct mfd_cell cells[2]; + struct regmap *regmap; + int ret; + + if (!pdata) { + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + } + + regmap = devm_regmap_init_i2c(client, &sky81452_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to initialize.err=%ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + i2c_set_clientdata(client, regmap); + + memset(cells, 0, sizeof(cells)); + cells[0].name = "sky81452-backlight"; + cells[0].of_compatible = "skyworks,sky81452-backlight"; + cells[0].platform_data = pdata->bl_pdata; + cells[0].pdata_size = sizeof(*pdata->bl_pdata); + cells[1].name = "sky81452-regulator"; + cells[1].platform_data = pdata->regulator_init_data; + cells[1].pdata_size = sizeof(*pdata->regulator_init_data); + + ret = mfd_add_devices(dev, -1, cells, ARRAY_SIZE(cells), NULL, 0, NULL); + if (ret) + dev_err(dev, "failed to add child devices. err=%d\n", ret); + + return ret; +} + +static int sky81452_remove(struct i2c_client *client) +{ + mfd_remove_devices(&client->dev); + return 0; +} + +static const struct i2c_device_id sky81452_ids[] = { + { "sky81452" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sky81452_ids); + +#ifdef CONFIG_OF +static const struct of_device_id sky81452_of_match[] = { + { .compatible = "skyworks,sky81452", }, + { } +}; +MODULE_DEVICE_TABLE(of, sky81452_of_match); +#endif + +static struct i2c_driver sky81452_driver = { + .driver = { + .name = "sky81452", + .of_match_table = of_match_ptr(sky81452_of_match), + }, + .probe = sky81452_probe, + .remove = sky81452_remove, + .id_table = sky81452_ids, +}; + +module_i2c_driver(sky81452_driver); + +MODULE_DESCRIPTION("Skyworks SKY81452 MFD driver"); +MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@skyworksinc.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/sm501.c b/kernel/drivers/mfd/sm501.c new file mode 100644 index 000000000..91077efc8 --- /dev/null +++ b/kernel/drivers/mfd/sm501.c @@ -0,0 +1,1751 @@ +/* linux/drivers/mfd/sm501.c + * + * Copyright (C) 2006 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * Vincent Sanders <vince@simtec.co.uk> + * + * 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. + * + * SM501 MFD driver +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/i2c-gpio.h> +#include <linux/slab.h> + +#include <linux/sm501.h> +#include <linux/sm501-regs.h> +#include <linux/serial_8250.h> + +#include <linux/io.h> + +struct sm501_device { + struct list_head list; + struct platform_device pdev; +}; + +struct sm501_gpio; + +#ifdef CONFIG_MFD_SM501_GPIO +#include <linux/gpio.h> + +struct sm501_gpio_chip { + struct gpio_chip gpio; + struct sm501_gpio *ourgpio; /* to get back to parent. */ + void __iomem *regbase; + void __iomem *control; /* address of control reg. */ +}; + +struct sm501_gpio { + struct sm501_gpio_chip low; + struct sm501_gpio_chip high; + spinlock_t lock; + + unsigned int registered : 1; + void __iomem *regs; + struct resource *regs_res; +}; +#else +struct sm501_gpio { + /* no gpio support, empty definition for sm501_devdata. */ +}; +#endif + +struct sm501_devdata { + spinlock_t reg_lock; + struct mutex clock_lock; + struct list_head devices; + struct sm501_gpio gpio; + + struct device *dev; + struct resource *io_res; + struct resource *mem_res; + struct resource *regs_claim; + struct sm501_platdata *platdata; + + + unsigned int in_suspend; + unsigned long pm_misc; + + int unit_power[20]; + unsigned int pdev_id; + unsigned int irq; + void __iomem *regs; + unsigned int rev; +}; + + +#define MHZ (1000 * 1000) + +#ifdef DEBUG +static const unsigned int div_tab[] = { + [0] = 1, + [1] = 2, + [2] = 4, + [3] = 8, + [4] = 16, + [5] = 32, + [6] = 64, + [7] = 128, + [8] = 3, + [9] = 6, + [10] = 12, + [11] = 24, + [12] = 48, + [13] = 96, + [14] = 192, + [15] = 384, + [16] = 5, + [17] = 10, + [18] = 20, + [19] = 40, + [20] = 80, + [21] = 160, + [22] = 320, + [23] = 604, +}; + +static unsigned long decode_div(unsigned long pll2, unsigned long val, + unsigned int lshft, unsigned int selbit, + unsigned long mask) +{ + if (val & selbit) + pll2 = 288 * MHZ; + + return pll2 / div_tab[(val >> lshft) & mask]; +} + +#define fmt_freq(x) ((x) / MHZ), ((x) % MHZ), (x) + +/* sm501_dump_clk + * + * Print out the current clock configuration for the device +*/ + +static void sm501_dump_clk(struct sm501_devdata *sm) +{ + unsigned long misct = smc501_readl(sm->regs + SM501_MISC_TIMING); + unsigned long pm0 = smc501_readl(sm->regs + SM501_POWER_MODE_0_CLOCK); + unsigned long pm1 = smc501_readl(sm->regs + SM501_POWER_MODE_1_CLOCK); + unsigned long pmc = smc501_readl(sm->regs + SM501_POWER_MODE_CONTROL); + unsigned long sdclk0, sdclk1; + unsigned long pll2 = 0; + + switch (misct & 0x30) { + case 0x00: + pll2 = 336 * MHZ; + break; + case 0x10: + pll2 = 288 * MHZ; + break; + case 0x20: + pll2 = 240 * MHZ; + break; + case 0x30: + pll2 = 192 * MHZ; + break; + } + + sdclk0 = (misct & (1<<12)) ? pll2 : 288 * MHZ; + sdclk0 /= div_tab[((misct >> 8) & 0xf)]; + + sdclk1 = (misct & (1<<20)) ? pll2 : 288 * MHZ; + sdclk1 /= div_tab[((misct >> 16) & 0xf)]; + + dev_dbg(sm->dev, "MISCT=%08lx, PM0=%08lx, PM1=%08lx\n", + misct, pm0, pm1); + + dev_dbg(sm->dev, "PLL2 = %ld.%ld MHz (%ld), SDCLK0=%08lx, SDCLK1=%08lx\n", + fmt_freq(pll2), sdclk0, sdclk1); + + dev_dbg(sm->dev, "SDRAM: PM0=%ld, PM1=%ld\n", sdclk0, sdclk1); + + dev_dbg(sm->dev, "PM0[%c]: " + "P2 %ld.%ld MHz (%ld), V2 %ld.%ld (%ld), " + "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", + (pmc & 3 ) == 0 ? '*' : '-', + fmt_freq(decode_div(pll2, pm0, 24, 1<<29, 31)), + fmt_freq(decode_div(pll2, pm0, 16, 1<<20, 15)), + fmt_freq(decode_div(pll2, pm0, 8, 1<<12, 15)), + fmt_freq(decode_div(pll2, pm0, 0, 1<<4, 15))); + + dev_dbg(sm->dev, "PM1[%c]: " + "P2 %ld.%ld MHz (%ld), V2 %ld.%ld (%ld), " + "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", + (pmc & 3 ) == 1 ? '*' : '-', + fmt_freq(decode_div(pll2, pm1, 24, 1<<29, 31)), + fmt_freq(decode_div(pll2, pm1, 16, 1<<20, 15)), + fmt_freq(decode_div(pll2, pm1, 8, 1<<12, 15)), + fmt_freq(decode_div(pll2, pm1, 0, 1<<4, 15))); +} + +static void sm501_dump_regs(struct sm501_devdata *sm) +{ + void __iomem *regs = sm->regs; + + dev_info(sm->dev, "System Control %08x\n", + smc501_readl(regs + SM501_SYSTEM_CONTROL)); + dev_info(sm->dev, "Misc Control %08x\n", + smc501_readl(regs + SM501_MISC_CONTROL)); + dev_info(sm->dev, "GPIO Control Low %08x\n", + smc501_readl(regs + SM501_GPIO31_0_CONTROL)); + dev_info(sm->dev, "GPIO Control Hi %08x\n", + smc501_readl(regs + SM501_GPIO63_32_CONTROL)); + dev_info(sm->dev, "DRAM Control %08x\n", + smc501_readl(regs + SM501_DRAM_CONTROL)); + dev_info(sm->dev, "Arbitration Ctrl %08x\n", + smc501_readl(regs + SM501_ARBTRTN_CONTROL)); + dev_info(sm->dev, "Misc Timing %08x\n", + smc501_readl(regs + SM501_MISC_TIMING)); +} + +static void sm501_dump_gate(struct sm501_devdata *sm) +{ + dev_info(sm->dev, "CurrentGate %08x\n", + smc501_readl(sm->regs + SM501_CURRENT_GATE)); + dev_info(sm->dev, "CurrentClock %08x\n", + smc501_readl(sm->regs + SM501_CURRENT_CLOCK)); + dev_info(sm->dev, "PowerModeControl %08x\n", + smc501_readl(sm->regs + SM501_POWER_MODE_CONTROL)); +} + +#else +static inline void sm501_dump_gate(struct sm501_devdata *sm) { } +static inline void sm501_dump_regs(struct sm501_devdata *sm) { } +static inline void sm501_dump_clk(struct sm501_devdata *sm) { } +#endif + +/* sm501_sync_regs + * + * ensure the +*/ + +static void sm501_sync_regs(struct sm501_devdata *sm) +{ + smc501_readl(sm->regs); +} + +static inline void sm501_mdelay(struct sm501_devdata *sm, unsigned int delay) +{ + /* during suspend/resume, we are currently not allowed to sleep, + * so change to using mdelay() instead of msleep() if we + * are in one of these paths */ + + if (sm->in_suspend) + mdelay(delay); + else + msleep(delay); +} + +/* sm501_misc_control + * + * alters the miscellaneous control parameters +*/ + +int sm501_misc_control(struct device *dev, + unsigned long set, unsigned long clear) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long misc; + unsigned long save; + unsigned long to; + + spin_lock_irqsave(&sm->reg_lock, save); + + misc = smc501_readl(sm->regs + SM501_MISC_CONTROL); + to = (misc & ~clear) | set; + + if (to != misc) { + smc501_writel(to, sm->regs + SM501_MISC_CONTROL); + sm501_sync_regs(sm); + + dev_dbg(sm->dev, "MISC_CONTROL %08lx\n", misc); + } + + spin_unlock_irqrestore(&sm->reg_lock, save); + return to; +} + +EXPORT_SYMBOL_GPL(sm501_misc_control); + +/* sm501_modify_reg + * + * Modify a register in the SM501 which may be shared with other + * drivers. +*/ + +unsigned long sm501_modify_reg(struct device *dev, + unsigned long reg, + unsigned long set, + unsigned long clear) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long data; + unsigned long save; + + spin_lock_irqsave(&sm->reg_lock, save); + + data = smc501_readl(sm->regs + reg); + data |= set; + data &= ~clear; + + smc501_writel(data, sm->regs + reg); + sm501_sync_regs(sm); + + spin_unlock_irqrestore(&sm->reg_lock, save); + + return data; +} + +EXPORT_SYMBOL_GPL(sm501_modify_reg); + +/* sm501_unit_power + * + * alters the power active gate to set specific units on or off + */ + +int sm501_unit_power(struct device *dev, unsigned int unit, unsigned int to) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long mode; + unsigned long gate; + unsigned long clock; + + mutex_lock(&sm->clock_lock); + + mode = smc501_readl(sm->regs + SM501_POWER_MODE_CONTROL); + gate = smc501_readl(sm->regs + SM501_CURRENT_GATE); + clock = smc501_readl(sm->regs + SM501_CURRENT_CLOCK); + + mode &= 3; /* get current power mode */ + + if (unit >= ARRAY_SIZE(sm->unit_power)) { + dev_err(dev, "%s: bad unit %d\n", __func__, unit); + goto already; + } + + dev_dbg(sm->dev, "%s: unit %d, cur %d, to %d\n", __func__, unit, + sm->unit_power[unit], to); + + if (to == 0 && sm->unit_power[unit] == 0) { + dev_err(sm->dev, "unit %d is already shutdown\n", unit); + goto already; + } + + sm->unit_power[unit] += to ? 1 : -1; + to = sm->unit_power[unit] ? 1 : 0; + + if (to) { + if (gate & (1 << unit)) + goto already; + gate |= (1 << unit); + } else { + if (!(gate & (1 << unit))) + goto already; + gate &= ~(1 << unit); + } + + switch (mode) { + case 1: + smc501_writel(gate, sm->regs + SM501_POWER_MODE_0_GATE); + smc501_writel(clock, sm->regs + SM501_POWER_MODE_0_CLOCK); + mode = 0; + break; + case 2: + case 0: + smc501_writel(gate, sm->regs + SM501_POWER_MODE_1_GATE); + smc501_writel(clock, sm->regs + SM501_POWER_MODE_1_CLOCK); + mode = 1; + break; + + default: + gate = -1; + goto already; + } + + smc501_writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); + sm501_sync_regs(sm); + + dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", + gate, clock, mode); + + sm501_mdelay(sm, 16); + + already: + mutex_unlock(&sm->clock_lock); + return gate; +} + +EXPORT_SYMBOL_GPL(sm501_unit_power); + +/* clock value structure. */ +struct sm501_clock { + unsigned long mclk; + int divider; + int shift; + unsigned int m, n, k; +}; + +/* sm501_calc_clock + * + * Calculates the nearest discrete clock frequency that + * can be achieved with the specified input clock. + * the maximum divisor is 3 or 5 + */ + +static int sm501_calc_clock(unsigned long freq, + struct sm501_clock *clock, + int max_div, + unsigned long mclk, + long *best_diff) +{ + int ret = 0; + int divider; + int shift; + long diff; + + /* try dividers 1 and 3 for CRT and for panel, + try divider 5 for panel only.*/ + + for (divider = 1; divider <= max_div; divider += 2) { + /* try all 8 shift values.*/ + for (shift = 0; shift < 8; shift++) { + /* Calculate difference to requested clock */ + diff = DIV_ROUND_CLOSEST(mclk, divider << shift) - freq; + if (diff < 0) + diff = -diff; + + /* If it is less than the current, use it */ + if (diff < *best_diff) { + *best_diff = diff; + + clock->mclk = mclk; + clock->divider = divider; + clock->shift = shift; + ret = 1; + } + } + } + + return ret; +} + +/* sm501_calc_pll + * + * Calculates the nearest discrete clock frequency that can be + * achieved using the programmable PLL. + * the maximum divisor is 3 or 5 + */ + +static unsigned long sm501_calc_pll(unsigned long freq, + struct sm501_clock *clock, + int max_div) +{ + unsigned long mclk; + unsigned int m, n, k; + long best_diff = 999999999; + + /* + * The SM502 datasheet doesn't specify the min/max values for M and N. + * N = 1 at least doesn't work in practice. + */ + for (m = 2; m <= 255; m++) { + for (n = 2; n <= 127; n++) { + for (k = 0; k <= 1; k++) { + mclk = (24000000UL * m / n) >> k; + + if (sm501_calc_clock(freq, clock, max_div, + mclk, &best_diff)) { + clock->m = m; + clock->n = n; + clock->k = k; + } + } + } + } + + /* Return best clock. */ + return clock->mclk / (clock->divider << clock->shift); +} + +/* sm501_select_clock + * + * Calculates the nearest discrete clock frequency that can be + * achieved using the 288MHz and 336MHz PLLs. + * the maximum divisor is 3 or 5 + */ + +static unsigned long sm501_select_clock(unsigned long freq, + struct sm501_clock *clock, + int max_div) +{ + unsigned long mclk; + long best_diff = 999999999; + + /* Try 288MHz and 336MHz clocks. */ + for (mclk = 288000000; mclk <= 336000000; mclk += 48000000) { + sm501_calc_clock(freq, clock, max_div, mclk, &best_diff); + } + + /* Return best clock. */ + return clock->mclk / (clock->divider << clock->shift); +} + +/* sm501_set_clock + * + * set one of the four clock sources to the closest available frequency to + * the one specified +*/ + +unsigned long sm501_set_clock(struct device *dev, + int clksrc, + unsigned long req_freq) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long mode = smc501_readl(sm->regs + SM501_POWER_MODE_CONTROL); + unsigned long gate = smc501_readl(sm->regs + SM501_CURRENT_GATE); + unsigned long clock = smc501_readl(sm->regs + SM501_CURRENT_CLOCK); + unsigned int pll_reg = 0; + unsigned long sm501_freq; /* the actual frequency achieved */ + u64 reg; + + struct sm501_clock to; + + /* find achivable discrete frequency and setup register value + * accordingly, V2XCLK, MCLK and M1XCLK are the same P2XCLK + * has an extra bit for the divider */ + + switch (clksrc) { + case SM501_CLOCK_P2XCLK: + /* This clock is divided in half so to achieve the + * requested frequency the value must be multiplied by + * 2. This clock also has an additional pre divisor */ + + if (sm->rev >= 0xC0) { + /* SM502 -> use the programmable PLL */ + sm501_freq = (sm501_calc_pll(2 * req_freq, + &to, 5) / 2); + reg = to.shift & 0x07;/* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + else if (to.divider == 5) + reg |= 0x10; /* /5 divider required */ + reg |= 0x40; /* select the programmable PLL */ + pll_reg = 0x20000 | (to.k << 15) | (to.n << 8) | to.m; + } else { + sm501_freq = (sm501_select_clock(2 * req_freq, + &to, 5) / 2); + reg = to.shift & 0x07;/* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + else if (to.divider == 5) + reg |= 0x10; /* /5 divider required */ + if (to.mclk != 288000000) + reg |= 0x20; /* which mclk pll is source */ + } + break; + + case SM501_CLOCK_V2XCLK: + /* This clock is divided in half so to achieve the + * requested frequency the value must be multiplied by 2. */ + + sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); + reg=to.shift & 0x07; /* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + if (to.mclk != 288000000) + reg |= 0x10; /* which mclk pll is source */ + break; + + case SM501_CLOCK_MCLK: + case SM501_CLOCK_M1XCLK: + /* These clocks are the same and not further divided */ + + sm501_freq = sm501_select_clock( req_freq, &to, 3); + reg=to.shift & 0x07; /* bottom 3 bits are shift */ + if (to.divider == 3) + reg |= 0x08; /* /3 divider required */ + if (to.mclk != 288000000) + reg |= 0x10; /* which mclk pll is source */ + break; + + default: + return 0; /* this is bad */ + } + + mutex_lock(&sm->clock_lock); + + mode = smc501_readl(sm->regs + SM501_POWER_MODE_CONTROL); + gate = smc501_readl(sm->regs + SM501_CURRENT_GATE); + clock = smc501_readl(sm->regs + SM501_CURRENT_CLOCK); + + clock = clock & ~(0xFF << clksrc); + clock |= reg<<clksrc; + + mode &= 3; /* find current mode */ + + switch (mode) { + case 1: + smc501_writel(gate, sm->regs + SM501_POWER_MODE_0_GATE); + smc501_writel(clock, sm->regs + SM501_POWER_MODE_0_CLOCK); + mode = 0; + break; + case 2: + case 0: + smc501_writel(gate, sm->regs + SM501_POWER_MODE_1_GATE); + smc501_writel(clock, sm->regs + SM501_POWER_MODE_1_CLOCK); + mode = 1; + break; + + default: + mutex_unlock(&sm->clock_lock); + return -1; + } + + smc501_writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); + + if (pll_reg) + smc501_writel(pll_reg, + sm->regs + SM501_PROGRAMMABLE_PLL_CONTROL); + + sm501_sync_regs(sm); + + dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", + gate, clock, mode); + + sm501_mdelay(sm, 16); + mutex_unlock(&sm->clock_lock); + + sm501_dump_clk(sm); + + return sm501_freq; +} + +EXPORT_SYMBOL_GPL(sm501_set_clock); + +/* sm501_find_clock + * + * finds the closest available frequency for a given clock +*/ + +unsigned long sm501_find_clock(struct device *dev, + int clksrc, + unsigned long req_freq) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev); + unsigned long sm501_freq; /* the frequency achieveable by the 501 */ + struct sm501_clock to; + + switch (clksrc) { + case SM501_CLOCK_P2XCLK: + if (sm->rev >= 0xC0) { + /* SM502 -> use the programmable PLL */ + sm501_freq = (sm501_calc_pll(2 * req_freq, + &to, 5) / 2); + } else { + sm501_freq = (sm501_select_clock(2 * req_freq, + &to, 5) / 2); + } + break; + + case SM501_CLOCK_V2XCLK: + sm501_freq = (sm501_select_clock(2 * req_freq, &to, 3) / 2); + break; + + case SM501_CLOCK_MCLK: + case SM501_CLOCK_M1XCLK: + sm501_freq = sm501_select_clock(req_freq, &to, 3); + break; + + default: + sm501_freq = 0; /* error */ + } + + return sm501_freq; +} + +EXPORT_SYMBOL_GPL(sm501_find_clock); + +static struct sm501_device *to_sm_device(struct platform_device *pdev) +{ + return container_of(pdev, struct sm501_device, pdev); +} + +/* sm501_device_release + * + * A release function for the platform devices we create to allow us to + * free any items we allocated +*/ + +static void sm501_device_release(struct device *dev) +{ + kfree(to_sm_device(to_platform_device(dev))); +} + +/* sm501_create_subdev + * + * Create a skeleton platform device with resources for passing to a + * sub-driver +*/ + +static struct platform_device * +sm501_create_subdev(struct sm501_devdata *sm, char *name, + unsigned int res_count, unsigned int platform_data_size) +{ + struct sm501_device *smdev; + + smdev = kzalloc(sizeof(struct sm501_device) + + (sizeof(struct resource) * res_count) + + platform_data_size, GFP_KERNEL); + if (!smdev) + return NULL; + + smdev->pdev.dev.release = sm501_device_release; + + smdev->pdev.name = name; + smdev->pdev.id = sm->pdev_id; + smdev->pdev.dev.parent = sm->dev; + + if (res_count) { + smdev->pdev.resource = (struct resource *)(smdev+1); + smdev->pdev.num_resources = res_count; + } + if (platform_data_size) + smdev->pdev.dev.platform_data = (void *)(smdev+1); + + return &smdev->pdev; +} + +/* sm501_register_device + * + * Register a platform device created with sm501_create_subdev() +*/ + +static int sm501_register_device(struct sm501_devdata *sm, + struct platform_device *pdev) +{ + struct sm501_device *smdev = to_sm_device(pdev); + int ptr; + int ret; + + for (ptr = 0; ptr < pdev->num_resources; ptr++) { + printk(KERN_DEBUG "%s[%d] %pR\n", + pdev->name, ptr, &pdev->resource[ptr]); + } + + ret = platform_device_register(pdev); + + if (ret >= 0) { + dev_dbg(sm->dev, "registered %s\n", pdev->name); + list_add_tail(&smdev->list, &sm->devices); + } else + dev_err(sm->dev, "error registering %s (%d)\n", + pdev->name, ret); + + return ret; +} + +/* sm501_create_subio + * + * Fill in an IO resource for a sub device +*/ + +static void sm501_create_subio(struct sm501_devdata *sm, + struct resource *res, + resource_size_t offs, + resource_size_t size) +{ + res->flags = IORESOURCE_MEM; + res->parent = sm->io_res; + res->start = sm->io_res->start + offs; + res->end = res->start + size - 1; +} + +/* sm501_create_mem + * + * Fill in an MEM resource for a sub device +*/ + +static void sm501_create_mem(struct sm501_devdata *sm, + struct resource *res, + resource_size_t *offs, + resource_size_t size) +{ + *offs -= size; /* adjust memory size */ + + res->flags = IORESOURCE_MEM; + res->parent = sm->mem_res; + res->start = sm->mem_res->start + *offs; + res->end = res->start + size - 1; +} + +/* sm501_create_irq + * + * Fill in an IRQ resource for a sub device +*/ + +static void sm501_create_irq(struct sm501_devdata *sm, + struct resource *res) +{ + res->flags = IORESOURCE_IRQ; + res->parent = NULL; + res->start = res->end = sm->irq; +} + +static int sm501_register_usbhost(struct sm501_devdata *sm, + resource_size_t *mem_avail) +{ + struct platform_device *pdev; + + pdev = sm501_create_subdev(sm, "sm501-usb", 3, 0); + if (!pdev) + return -ENOMEM; + + sm501_create_subio(sm, &pdev->resource[0], 0x40000, 0x20000); + sm501_create_mem(sm, &pdev->resource[1], mem_avail, 256*1024); + sm501_create_irq(sm, &pdev->resource[2]); + + return sm501_register_device(sm, pdev); +} + +static void sm501_setup_uart_data(struct sm501_devdata *sm, + struct plat_serial8250_port *uart_data, + unsigned int offset) +{ + uart_data->membase = sm->regs + offset; + uart_data->mapbase = sm->io_res->start + offset; + uart_data->iotype = UPIO_MEM; + uart_data->irq = sm->irq; + uart_data->flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_SHARE_IRQ; + uart_data->regshift = 2; + uart_data->uartclk = (9600 * 16); +} + +static int sm501_register_uart(struct sm501_devdata *sm, int devices) +{ + struct platform_device *pdev; + struct plat_serial8250_port *uart_data; + + pdev = sm501_create_subdev(sm, "serial8250", 0, + sizeof(struct plat_serial8250_port) * 3); + if (!pdev) + return -ENOMEM; + + uart_data = dev_get_platdata(&pdev->dev); + + if (devices & SM501_USE_UART0) { + sm501_setup_uart_data(sm, uart_data++, 0x30000); + sm501_unit_power(sm->dev, SM501_GATE_UART0, 1); + sm501_modify_reg(sm->dev, SM501_IRQ_MASK, 1 << 12, 0); + sm501_modify_reg(sm->dev, SM501_GPIO63_32_CONTROL, 0x01e0, 0); + } + if (devices & SM501_USE_UART1) { + sm501_setup_uart_data(sm, uart_data++, 0x30020); + sm501_unit_power(sm->dev, SM501_GATE_UART1, 1); + sm501_modify_reg(sm->dev, SM501_IRQ_MASK, 1 << 13, 0); + sm501_modify_reg(sm->dev, SM501_GPIO63_32_CONTROL, 0x1e00, 0); + } + + pdev->id = PLAT8250_DEV_SM501; + + return sm501_register_device(sm, pdev); +} + +static int sm501_register_display(struct sm501_devdata *sm, + resource_size_t *mem_avail) +{ + struct platform_device *pdev; + + pdev = sm501_create_subdev(sm, "sm501-fb", 4, 0); + if (!pdev) + return -ENOMEM; + + sm501_create_subio(sm, &pdev->resource[0], 0x80000, 0x10000); + sm501_create_subio(sm, &pdev->resource[1], 0x100000, 0x50000); + sm501_create_mem(sm, &pdev->resource[2], mem_avail, *mem_avail); + sm501_create_irq(sm, &pdev->resource[3]); + + return sm501_register_device(sm, pdev); +} + +#ifdef CONFIG_MFD_SM501_GPIO + +static inline struct sm501_gpio_chip *to_sm501_gpio(struct gpio_chip *gc) +{ + return container_of(gc, struct sm501_gpio_chip, gpio); +} + +static inline struct sm501_devdata *sm501_gpio_to_dev(struct sm501_gpio *gpio) +{ + return container_of(gpio, struct sm501_devdata, gpio); +} + +static int sm501_gpio_get(struct gpio_chip *chip, unsigned offset) + +{ + struct sm501_gpio_chip *smgpio = to_sm501_gpio(chip); + unsigned long result; + + result = smc501_readl(smgpio->regbase + SM501_GPIO_DATA_LOW); + result >>= offset; + + return result & 1UL; +} + +static void sm501_gpio_ensure_gpio(struct sm501_gpio_chip *smchip, + unsigned long bit) +{ + unsigned long ctrl; + + /* check and modify if this pin is not set as gpio. */ + + if (smc501_readl(smchip->control) & bit) { + dev_info(sm501_gpio_to_dev(smchip->ourgpio)->dev, + "changing mode of gpio, bit %08lx\n", bit); + + ctrl = smc501_readl(smchip->control); + ctrl &= ~bit; + smc501_writel(ctrl, smchip->control); + + sm501_sync_regs(sm501_gpio_to_dev(smchip->ourgpio)); + } +} + +static void sm501_gpio_set(struct gpio_chip *chip, unsigned offset, int value) + +{ + struct sm501_gpio_chip *smchip = to_sm501_gpio(chip); + struct sm501_gpio *smgpio = smchip->ourgpio; + unsigned long bit = 1 << offset; + void __iomem *regs = smchip->regbase; + unsigned long save; + unsigned long val; + + dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n", + __func__, chip, offset); + + spin_lock_irqsave(&smgpio->lock, save); + + val = smc501_readl(regs + SM501_GPIO_DATA_LOW) & ~bit; + if (value) + val |= bit; + smc501_writel(val, regs); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + sm501_gpio_ensure_gpio(smchip, bit); + + spin_unlock_irqrestore(&smgpio->lock, save); +} + +static int sm501_gpio_input(struct gpio_chip *chip, unsigned offset) +{ + struct sm501_gpio_chip *smchip = to_sm501_gpio(chip); + struct sm501_gpio *smgpio = smchip->ourgpio; + void __iomem *regs = smchip->regbase; + unsigned long bit = 1 << offset; + unsigned long save; + unsigned long ddr; + + dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d)\n", + __func__, chip, offset); + + spin_lock_irqsave(&smgpio->lock, save); + + ddr = smc501_readl(regs + SM501_GPIO_DDR_LOW); + smc501_writel(ddr & ~bit, regs + SM501_GPIO_DDR_LOW); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + sm501_gpio_ensure_gpio(smchip, bit); + + spin_unlock_irqrestore(&smgpio->lock, save); + + return 0; +} + +static int sm501_gpio_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct sm501_gpio_chip *smchip = to_sm501_gpio(chip); + struct sm501_gpio *smgpio = smchip->ourgpio; + unsigned long bit = 1 << offset; + void __iomem *regs = smchip->regbase; + unsigned long save; + unsigned long val; + unsigned long ddr; + + dev_dbg(sm501_gpio_to_dev(smgpio)->dev, "%s(%p,%d,%d)\n", + __func__, chip, offset, value); + + spin_lock_irqsave(&smgpio->lock, save); + + val = smc501_readl(regs + SM501_GPIO_DATA_LOW); + if (value) + val |= bit; + else + val &= ~bit; + smc501_writel(val, regs); + + ddr = smc501_readl(regs + SM501_GPIO_DDR_LOW); + smc501_writel(ddr | bit, regs + SM501_GPIO_DDR_LOW); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + smc501_writel(val, regs + SM501_GPIO_DATA_LOW); + + sm501_sync_regs(sm501_gpio_to_dev(smgpio)); + spin_unlock_irqrestore(&smgpio->lock, save); + + return 0; +} + +static struct gpio_chip gpio_chip_template = { + .ngpio = 32, + .direction_input = sm501_gpio_input, + .direction_output = sm501_gpio_output, + .set = sm501_gpio_set, + .get = sm501_gpio_get, +}; + +static int sm501_gpio_register_chip(struct sm501_devdata *sm, + struct sm501_gpio *gpio, + struct sm501_gpio_chip *chip) +{ + struct sm501_platdata *pdata = sm->platdata; + struct gpio_chip *gchip = &chip->gpio; + int base = pdata->gpio_base; + + chip->gpio = gpio_chip_template; + + if (chip == &gpio->high) { + if (base > 0) + base += 32; + chip->regbase = gpio->regs + SM501_GPIO_DATA_HIGH; + chip->control = sm->regs + SM501_GPIO63_32_CONTROL; + gchip->label = "SM501-HIGH"; + } else { + chip->regbase = gpio->regs + SM501_GPIO_DATA_LOW; + chip->control = sm->regs + SM501_GPIO31_0_CONTROL; + gchip->label = "SM501-LOW"; + } + + gchip->base = base; + chip->ourgpio = gpio; + + return gpiochip_add(gchip); +} + +static int sm501_register_gpio(struct sm501_devdata *sm) +{ + struct sm501_gpio *gpio = &sm->gpio; + resource_size_t iobase = sm->io_res->start + SM501_GPIO; + int ret; + + dev_dbg(sm->dev, "registering gpio block %08llx\n", + (unsigned long long)iobase); + + spin_lock_init(&gpio->lock); + + gpio->regs_res = request_mem_region(iobase, 0x20, "sm501-gpio"); + if (gpio->regs_res == NULL) { + dev_err(sm->dev, "gpio: failed to request region\n"); + return -ENXIO; + } + + gpio->regs = ioremap(iobase, 0x20); + if (gpio->regs == NULL) { + dev_err(sm->dev, "gpio: failed to remap registers\n"); + ret = -ENXIO; + goto err_claimed; + } + + /* Register both our chips. */ + + ret = sm501_gpio_register_chip(sm, gpio, &gpio->low); + if (ret) { + dev_err(sm->dev, "failed to add low chip\n"); + goto err_mapped; + } + + ret = sm501_gpio_register_chip(sm, gpio, &gpio->high); + if (ret) { + dev_err(sm->dev, "failed to add high chip\n"); + goto err_low_chip; + } + + gpio->registered = 1; + + return 0; + + err_low_chip: + gpiochip_remove(&gpio->low.gpio); + + err_mapped: + iounmap(gpio->regs); + + err_claimed: + release_resource(gpio->regs_res); + kfree(gpio->regs_res); + + return ret; +} + +static void sm501_gpio_remove(struct sm501_devdata *sm) +{ + struct sm501_gpio *gpio = &sm->gpio; + + if (!sm->gpio.registered) + return; + + gpiochip_remove(&gpio->low.gpio); + gpiochip_remove(&gpio->high.gpio); + + iounmap(gpio->regs); + release_resource(gpio->regs_res); + kfree(gpio->regs_res); +} + +static inline int sm501_gpio_pin2nr(struct sm501_devdata *sm, unsigned int pin) +{ + struct sm501_gpio *gpio = &sm->gpio; + int base = (pin < 32) ? gpio->low.gpio.base : gpio->high.gpio.base; + + return (pin % 32) + base; +} + +static inline int sm501_gpio_isregistered(struct sm501_devdata *sm) +{ + return sm->gpio.registered; +} +#else +static inline int sm501_register_gpio(struct sm501_devdata *sm) +{ + return 0; +} + +static inline void sm501_gpio_remove(struct sm501_devdata *sm) +{ +} + +static inline int sm501_gpio_pin2nr(struct sm501_devdata *sm, unsigned int pin) +{ + return -1; +} + +static inline int sm501_gpio_isregistered(struct sm501_devdata *sm) +{ + return 0; +} +#endif + +static int sm501_register_gpio_i2c_instance(struct sm501_devdata *sm, + struct sm501_platdata_gpio_i2c *iic) +{ + struct i2c_gpio_platform_data *icd; + struct platform_device *pdev; + + pdev = sm501_create_subdev(sm, "i2c-gpio", 0, + sizeof(struct i2c_gpio_platform_data)); + if (!pdev) + return -ENOMEM; + + icd = dev_get_platdata(&pdev->dev); + + /* We keep the pin_sda and pin_scl fields relative in case the + * same platform data is passed to >1 SM501. + */ + + icd->sda_pin = sm501_gpio_pin2nr(sm, iic->pin_sda); + icd->scl_pin = sm501_gpio_pin2nr(sm, iic->pin_scl); + icd->timeout = iic->timeout; + icd->udelay = iic->udelay; + + /* note, we can't use either of the pin numbers, as the i2c-gpio + * driver uses the platform.id field to generate the bus number + * to register with the i2c core; The i2c core doesn't have enough + * entries to deal with anything we currently use. + */ + + pdev->id = iic->bus_num; + + dev_info(sm->dev, "registering i2c-%d: sda=%d (%d), scl=%d (%d)\n", + iic->bus_num, + icd->sda_pin, iic->pin_sda, icd->scl_pin, iic->pin_scl); + + return sm501_register_device(sm, pdev); +} + +static int sm501_register_gpio_i2c(struct sm501_devdata *sm, + struct sm501_platdata *pdata) +{ + struct sm501_platdata_gpio_i2c *iic = pdata->gpio_i2c; + int index; + int ret; + + for (index = 0; index < pdata->gpio_i2c_nr; index++, iic++) { + ret = sm501_register_gpio_i2c_instance(sm, iic); + if (ret < 0) + return ret; + } + + return 0; +} + +/* sm501_dbg_regs + * + * Debug attribute to attach to parent device to show core registers +*/ + +static ssize_t sm501_dbg_regs(struct device *dev, + struct device_attribute *attr, char *buff) +{ + struct sm501_devdata *sm = dev_get_drvdata(dev) ; + unsigned int reg; + char *ptr = buff; + int ret; + + for (reg = 0x00; reg < 0x70; reg += 4) { + ret = sprintf(ptr, "%08x = %08x\n", + reg, smc501_readl(sm->regs + reg)); + ptr += ret; + } + + return ptr - buff; +} + + +static DEVICE_ATTR(dbg_regs, 0444, sm501_dbg_regs, NULL); + +/* sm501_init_reg + * + * Helper function for the init code to setup a register + * + * clear the bits which are set in r->mask, and then set + * the bits set in r->set. +*/ + +static inline void sm501_init_reg(struct sm501_devdata *sm, + unsigned long reg, + struct sm501_reg_init *r) +{ + unsigned long tmp; + + tmp = smc501_readl(sm->regs + reg); + tmp &= ~r->mask; + tmp |= r->set; + smc501_writel(tmp, sm->regs + reg); +} + +/* sm501_init_regs + * + * Setup core register values +*/ + +static void sm501_init_regs(struct sm501_devdata *sm, + struct sm501_initdata *init) +{ + sm501_misc_control(sm->dev, + init->misc_control.set, + init->misc_control.mask); + + sm501_init_reg(sm, SM501_MISC_TIMING, &init->misc_timing); + sm501_init_reg(sm, SM501_GPIO31_0_CONTROL, &init->gpio_low); + sm501_init_reg(sm, SM501_GPIO63_32_CONTROL, &init->gpio_high); + + if (init->m1xclk) { + dev_info(sm->dev, "setting M1XCLK to %ld\n", init->m1xclk); + sm501_set_clock(sm->dev, SM501_CLOCK_M1XCLK, init->m1xclk); + } + + if (init->mclk) { + dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk); + sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk); + } + +} + +/* Check the PLL sources for the M1CLK and M1XCLK + * + * If the M1CLK and M1XCLKs are not sourced from the same PLL, then + * there is a risk (see errata AB-5) that the SM501 will cease proper + * function. If this happens, then it is likely the SM501 will + * hang the system. +*/ + +static int sm501_check_clocks(struct sm501_devdata *sm) +{ + unsigned long pwrmode = smc501_readl(sm->regs + SM501_CURRENT_CLOCK); + unsigned long msrc = (pwrmode & SM501_POWERMODE_M_SRC); + unsigned long m1src = (pwrmode & SM501_POWERMODE_M1_SRC); + + return ((msrc == 0 && m1src != 0) || (msrc != 0 && m1src == 0)); +} + +static unsigned int sm501_mem_local[] = { + [0] = 4*1024*1024, + [1] = 8*1024*1024, + [2] = 16*1024*1024, + [3] = 32*1024*1024, + [4] = 64*1024*1024, + [5] = 2*1024*1024, +}; + +/* sm501_init_dev + * + * Common init code for an SM501 +*/ + +static int sm501_init_dev(struct sm501_devdata *sm) +{ + struct sm501_initdata *idata; + struct sm501_platdata *pdata; + resource_size_t mem_avail; + unsigned long dramctrl; + unsigned long devid; + int ret; + + mutex_init(&sm->clock_lock); + spin_lock_init(&sm->reg_lock); + + INIT_LIST_HEAD(&sm->devices); + + devid = smc501_readl(sm->regs + SM501_DEVICEID); + + if ((devid & SM501_DEVICEID_IDMASK) != SM501_DEVICEID_SM501) { + dev_err(sm->dev, "incorrect device id %08lx\n", devid); + return -EINVAL; + } + + /* disable irqs */ + smc501_writel(0, sm->regs + SM501_IRQ_MASK); + + dramctrl = smc501_readl(sm->regs + SM501_DRAM_CONTROL); + mem_avail = sm501_mem_local[(dramctrl >> 13) & 0x7]; + + dev_info(sm->dev, "SM501 At %p: Version %08lx, %ld Mb, IRQ %d\n", + sm->regs, devid, (unsigned long)mem_avail >> 20, sm->irq); + + sm->rev = devid & SM501_DEVICEID_REVMASK; + + sm501_dump_gate(sm); + + ret = device_create_file(sm->dev, &dev_attr_dbg_regs); + if (ret) + dev_err(sm->dev, "failed to create debug regs file\n"); + + sm501_dump_clk(sm); + + /* check to see if we have some device initialisation */ + + pdata = sm->platdata; + idata = pdata ? pdata->init : NULL; + + if (idata) { + sm501_init_regs(sm, idata); + + if (idata->devices & SM501_USE_USB_HOST) + sm501_register_usbhost(sm, &mem_avail); + if (idata->devices & (SM501_USE_UART0 | SM501_USE_UART1)) + sm501_register_uart(sm, idata->devices); + if (idata->devices & SM501_USE_GPIO) + sm501_register_gpio(sm); + } + + if (pdata && pdata->gpio_i2c != NULL && pdata->gpio_i2c_nr > 0) { + if (!sm501_gpio_isregistered(sm)) + dev_err(sm->dev, "no gpio available for i2c gpio.\n"); + else + sm501_register_gpio_i2c(sm, pdata); + } + + ret = sm501_check_clocks(sm); + if (ret) { + dev_err(sm->dev, "M1X and M clocks sourced from different " + "PLLs\n"); + return -EINVAL; + } + + /* always create a framebuffer */ + sm501_register_display(sm, &mem_avail); + + return 0; +} + +static int sm501_plat_probe(struct platform_device *dev) +{ + struct sm501_devdata *sm; + int ret; + + sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL); + if (sm == NULL) { + dev_err(&dev->dev, "no memory for device data\n"); + ret = -ENOMEM; + goto err1; + } + + sm->dev = &dev->dev; + sm->pdev_id = dev->id; + sm->platdata = dev_get_platdata(&dev->dev); + + ret = platform_get_irq(dev, 0); + if (ret < 0) { + dev_err(&dev->dev, "failed to get irq resource\n"); + goto err_res; + } + sm->irq = ret; + + sm->io_res = platform_get_resource(dev, IORESOURCE_MEM, 1); + sm->mem_res = platform_get_resource(dev, IORESOURCE_MEM, 0); + + if (sm->io_res == NULL || sm->mem_res == NULL) { + dev_err(&dev->dev, "failed to get IO resource\n"); + ret = -ENOENT; + goto err_res; + } + + sm->regs_claim = request_mem_region(sm->io_res->start, + 0x100, "sm501"); + + if (sm->regs_claim == NULL) { + dev_err(&dev->dev, "cannot claim registers\n"); + ret = -EBUSY; + goto err_res; + } + + platform_set_drvdata(dev, sm); + + sm->regs = ioremap(sm->io_res->start, resource_size(sm->io_res)); + + if (sm->regs == NULL) { + dev_err(&dev->dev, "cannot remap registers\n"); + ret = -EIO; + goto err_claim; + } + + return sm501_init_dev(sm); + + err_claim: + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + err_res: + kfree(sm); + err1: + return ret; + +} + +#ifdef CONFIG_PM + +/* power management support */ + +static void sm501_set_power(struct sm501_devdata *sm, int on) +{ + struct sm501_platdata *pd = sm->platdata; + + if (pd == NULL) + return; + + if (pd->get_power) { + if (pd->get_power(sm->dev) == on) { + dev_dbg(sm->dev, "is already %d\n", on); + return; + } + } + + if (pd->set_power) { + dev_dbg(sm->dev, "setting power to %d\n", on); + + pd->set_power(sm->dev, on); + sm501_mdelay(sm, 10); + } +} + +static int sm501_plat_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sm501_devdata *sm = platform_get_drvdata(pdev); + + sm->in_suspend = 1; + sm->pm_misc = smc501_readl(sm->regs + SM501_MISC_CONTROL); + + sm501_dump_regs(sm); + + if (sm->platdata) { + if (sm->platdata->flags & SM501_FLAG_SUSPEND_OFF) + sm501_set_power(sm, 0); + } + + return 0; +} + +static int sm501_plat_resume(struct platform_device *pdev) +{ + struct sm501_devdata *sm = platform_get_drvdata(pdev); + + sm501_set_power(sm, 1); + + sm501_dump_regs(sm); + sm501_dump_gate(sm); + sm501_dump_clk(sm); + + /* check to see if we are in the same state as when suspended */ + + if (smc501_readl(sm->regs + SM501_MISC_CONTROL) != sm->pm_misc) { + dev_info(sm->dev, "SM501_MISC_CONTROL changed over sleep\n"); + smc501_writel(sm->pm_misc, sm->regs + SM501_MISC_CONTROL); + + /* our suspend causes the controller state to change, + * either by something attempting setup, power loss, + * or an external reset event on power change */ + + if (sm->platdata && sm->platdata->init) { + sm501_init_regs(sm, sm->platdata->init); + } + } + + /* dump our state from resume */ + + sm501_dump_regs(sm); + sm501_dump_clk(sm); + + sm->in_suspend = 0; + + return 0; +} +#else +#define sm501_plat_suspend NULL +#define sm501_plat_resume NULL +#endif + +/* Initialisation data for PCI devices */ + +static struct sm501_initdata sm501_pci_initdata = { + .gpio_high = { + .set = 0x3F000000, /* 24bit panel */ + .mask = 0x0, + }, + .misc_timing = { + .set = 0x010100, /* SDRAM timing */ + .mask = 0x1F1F00, + }, + .misc_control = { + .set = SM501_MISC_PNL_24BIT, + .mask = 0, + }, + + .devices = SM501_USE_ALL, + + /* Errata AB-3 says that 72MHz is the fastest available + * for 33MHZ PCI with proper bus-mastering operation */ + + .mclk = 72 * MHZ, + .m1xclk = 144 * MHZ, +}; + +static struct sm501_platdata_fbsub sm501_pdata_fbsub = { + .flags = (SM501FB_FLAG_USE_INIT_MODE | + SM501FB_FLAG_USE_HWCURSOR | + SM501FB_FLAG_USE_HWACCEL | + SM501FB_FLAG_DISABLE_AT_EXIT), +}; + +static struct sm501_platdata_fb sm501_fb_pdata = { + .fb_route = SM501_FB_OWN, + .fb_crt = &sm501_pdata_fbsub, + .fb_pnl = &sm501_pdata_fbsub, +}; + +static struct sm501_platdata sm501_pci_platdata = { + .init = &sm501_pci_initdata, + .fb = &sm501_fb_pdata, + .gpio_base = -1, +}; + +static int sm501_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct sm501_devdata *sm; + int err; + + sm = kzalloc(sizeof(struct sm501_devdata), GFP_KERNEL); + if (sm == NULL) { + dev_err(&dev->dev, "no memory for device data\n"); + err = -ENOMEM; + goto err1; + } + + /* set a default set of platform data */ + dev->dev.platform_data = sm->platdata = &sm501_pci_platdata; + + /* set a hopefully unique id for our child platform devices */ + sm->pdev_id = 32 + dev->devfn; + + pci_set_drvdata(dev, sm); + + err = pci_enable_device(dev); + if (err) { + dev_err(&dev->dev, "cannot enable device\n"); + goto err2; + } + + sm->dev = &dev->dev; + sm->irq = dev->irq; + +#ifdef __BIG_ENDIAN + /* if the system is big-endian, we most probably have a + * translation in the IO layer making the PCI bus little endian + * so make the framebuffer swapped pixels */ + + sm501_fb_pdata.flags |= SM501_FBPD_SWAP_FB_ENDIAN; +#endif + + /* check our resources */ + + if (!(pci_resource_flags(dev, 0) & IORESOURCE_MEM)) { + dev_err(&dev->dev, "region #0 is not memory?\n"); + err = -EINVAL; + goto err3; + } + + if (!(pci_resource_flags(dev, 1) & IORESOURCE_MEM)) { + dev_err(&dev->dev, "region #1 is not memory?\n"); + err = -EINVAL; + goto err3; + } + + /* make our resources ready for sharing */ + + sm->io_res = &dev->resource[1]; + sm->mem_res = &dev->resource[0]; + + sm->regs_claim = request_mem_region(sm->io_res->start, + 0x100, "sm501"); + if (sm->regs_claim == NULL) { + dev_err(&dev->dev, "cannot claim registers\n"); + err= -EBUSY; + goto err3; + } + + sm->regs = pci_ioremap_bar(dev, 1); + + if (sm->regs == NULL) { + dev_err(&dev->dev, "cannot remap registers\n"); + err = -EIO; + goto err4; + } + + sm501_init_dev(sm); + return 0; + + err4: + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + err3: + pci_disable_device(dev); + err2: + kfree(sm); + err1: + return err; +} + +static void sm501_remove_sub(struct sm501_devdata *sm, + struct sm501_device *smdev) +{ + list_del(&smdev->list); + platform_device_unregister(&smdev->pdev); +} + +static void sm501_dev_remove(struct sm501_devdata *sm) +{ + struct sm501_device *smdev, *tmp; + + list_for_each_entry_safe(smdev, tmp, &sm->devices, list) + sm501_remove_sub(sm, smdev); + + device_remove_file(sm->dev, &dev_attr_dbg_regs); + + sm501_gpio_remove(sm); +} + +static void sm501_pci_remove(struct pci_dev *dev) +{ + struct sm501_devdata *sm = pci_get_drvdata(dev); + + sm501_dev_remove(sm); + iounmap(sm->regs); + + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + + pci_disable_device(dev); +} + +static int sm501_plat_remove(struct platform_device *dev) +{ + struct sm501_devdata *sm = platform_get_drvdata(dev); + + sm501_dev_remove(sm); + iounmap(sm->regs); + + release_resource(sm->regs_claim); + kfree(sm->regs_claim); + + return 0; +} + +static const struct pci_device_id sm501_pci_tbl[] = { + { 0x126f, 0x0501, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, sm501_pci_tbl); + +static struct pci_driver sm501_pci_driver = { + .name = "sm501", + .id_table = sm501_pci_tbl, + .probe = sm501_pci_probe, + .remove = sm501_pci_remove, +}; + +MODULE_ALIAS("platform:sm501"); + +static const struct of_device_id of_sm501_match_tbl[] = { + { .compatible = "smi,sm501", }, + { /* end */ } +}; + +static struct platform_driver sm501_plat_driver = { + .driver = { + .name = "sm501", + .of_match_table = of_sm501_match_tbl, + }, + .probe = sm501_plat_probe, + .remove = sm501_plat_remove, + .suspend = sm501_plat_suspend, + .resume = sm501_plat_resume, +}; + +static int __init sm501_base_init(void) +{ + platform_driver_register(&sm501_plat_driver); + return pci_register_driver(&sm501_pci_driver); +} + +static void __exit sm501_base_exit(void) +{ + platform_driver_unregister(&sm501_plat_driver); + pci_unregister_driver(&sm501_pci_driver); +} + +module_init(sm501_base_init); +module_exit(sm501_base_exit); + +MODULE_DESCRIPTION("SM501 Core Driver"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Vincent Sanders"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/smsc-ece1099.c b/kernel/drivers/mfd/smsc-ece1099.c new file mode 100644 index 000000000..03246880d --- /dev/null +++ b/kernel/drivers/mfd/smsc-ece1099.c @@ -0,0 +1,112 @@ +/* + * TI SMSC MFD Driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Sourav Poddar <sourav.poddar@ti.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; GPL v2. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/regmap.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/mfd/smsc.h> +#include <linux/of_platform.h> + +static const struct regmap_config smsc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SMSC_VEN_ID_H, + .cache_type = REGCACHE_RBTREE, +}; + +static int smsc_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct smsc *smsc; + int devid, rev, venid_l, venid_h; + int ret = 0; + + smsc = devm_kzalloc(&i2c->dev, sizeof(struct smsc), + GFP_KERNEL); + if (!smsc) { + dev_err(&i2c->dev, "smsc mfd driver memory allocation failed\n"); + return -ENOMEM; + } + + smsc->regmap = devm_regmap_init_i2c(i2c, &smsc_regmap_config); + if (IS_ERR(smsc->regmap)) { + ret = PTR_ERR(smsc->regmap); + goto err; + } + + i2c_set_clientdata(i2c, smsc); + smsc->dev = &i2c->dev; + +#ifdef CONFIG_OF + of_property_read_u32(i2c->dev.of_node, "clock", &smsc->clk); +#endif + + regmap_read(smsc->regmap, SMSC_DEV_ID, &devid); + regmap_read(smsc->regmap, SMSC_DEV_REV, &rev); + regmap_read(smsc->regmap, SMSC_VEN_ID_L, &venid_l); + regmap_read(smsc->regmap, SMSC_VEN_ID_H, &venid_h); + + dev_info(&i2c->dev, "SMSCxxx devid: %02x rev: %02x venid: %02x\n", + devid, rev, (venid_h << 8) | venid_l); + + ret = regmap_write(smsc->regmap, SMSC_CLK_CTRL, smsc->clk); + if (ret) + goto err; + +#ifdef CONFIG_OF + if (i2c->dev.of_node) + ret = of_platform_populate(i2c->dev.of_node, + NULL, NULL, &i2c->dev); +#endif + +err: + return ret; +} + +static int smsc_i2c_remove(struct i2c_client *i2c) +{ + struct smsc *smsc = i2c_get_clientdata(i2c); + + mfd_remove_devices(smsc->dev); + + return 0; +} + +static const struct i2c_device_id smsc_i2c_id[] = { + { "smscece1099", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smsc_i2c_id); + +static struct i2c_driver smsc_i2c_driver = { + .driver = { + .name = "smsc", + .owner = THIS_MODULE, + }, + .probe = smsc_i2c_probe, + .remove = smsc_i2c_remove, + .id_table = smsc_i2c_id, +}; + +module_i2c_driver(smsc_i2c_driver); + +MODULE_AUTHOR("Sourav Poddar <sourav.poddar@ti.com>"); +MODULE_DESCRIPTION("SMSC chip multi-function driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/ssbi.c b/kernel/drivers/mfd/ssbi.c new file mode 100644 index 000000000..27986f641 --- /dev/null +++ b/kernel/drivers/mfd/ssbi.c @@ -0,0 +1,338 @@ +/* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2010, Google Inc. + * + * Original authors: Code Aurora Forum + * + * Author: Dima Zavin <dima@android.com> + * - Largely rewritten from original to not be an i2c driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/ssbi.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> + +/* SSBI 2.0 controller registers */ +#define SSBI2_CMD 0x0008 +#define SSBI2_RD 0x0010 +#define SSBI2_STATUS 0x0014 +#define SSBI2_MODE2 0x001C + +/* SSBI_CMD fields */ +#define SSBI_CMD_RDWRN (1 << 24) + +/* SSBI_STATUS fields */ +#define SSBI_STATUS_RD_READY (1 << 2) +#define SSBI_STATUS_READY (1 << 1) +#define SSBI_STATUS_MCHN_BUSY (1 << 0) + +/* SSBI_MODE2 fields */ +#define SSBI_MODE2_REG_ADDR_15_8_SHFT 0x04 +#define SSBI_MODE2_REG_ADDR_15_8_MASK (0x7f << SSBI_MODE2_REG_ADDR_15_8_SHFT) + +#define SET_SSBI_MODE2_REG_ADDR_15_8(MD, AD) \ + (((MD) & 0x0F) | ((((AD) >> 8) << SSBI_MODE2_REG_ADDR_15_8_SHFT) & \ + SSBI_MODE2_REG_ADDR_15_8_MASK)) + +/* SSBI PMIC Arbiter command registers */ +#define SSBI_PA_CMD 0x0000 +#define SSBI_PA_RD_STATUS 0x0004 + +/* SSBI_PA_CMD fields */ +#define SSBI_PA_CMD_RDWRN (1 << 24) +#define SSBI_PA_CMD_ADDR_MASK 0x7fff /* REG_ADDR_7_0, REG_ADDR_8_14*/ + +/* SSBI_PA_RD_STATUS fields */ +#define SSBI_PA_RD_STATUS_TRANS_DONE (1 << 27) +#define SSBI_PA_RD_STATUS_TRANS_DENIED (1 << 26) + +#define SSBI_TIMEOUT_US 100 + +enum ssbi_controller_type { + MSM_SBI_CTRL_SSBI = 0, + MSM_SBI_CTRL_SSBI2, + MSM_SBI_CTRL_PMIC_ARBITER, +}; + +struct ssbi { + struct device *slave; + void __iomem *base; + spinlock_t lock; + enum ssbi_controller_type controller_type; + int (*read)(struct ssbi *, u16 addr, u8 *buf, int len); + int (*write)(struct ssbi *, u16 addr, const u8 *buf, int len); +}; + +#define to_ssbi(dev) platform_get_drvdata(to_platform_device(dev)) + +static inline u32 ssbi_readl(struct ssbi *ssbi, u32 reg) +{ + return readl(ssbi->base + reg); +} + +static inline void ssbi_writel(struct ssbi *ssbi, u32 val, u32 reg) +{ + writel(val, ssbi->base + reg); +} + +/* + * Via private exchange with one of the original authors, the hardware + * should generally finish a transaction in about 5us. The worst + * case, is when using the arbiter and both other CPUs have just + * started trying to use the SSBI bus will result in a time of about + * 20us. It should never take longer than this. + * + * As such, this wait merely spins, with a udelay. + */ +static int ssbi_wait_mask(struct ssbi *ssbi, u32 set_mask, u32 clr_mask) +{ + u32 timeout = SSBI_TIMEOUT_US; + u32 val; + + while (timeout--) { + val = ssbi_readl(ssbi, SSBI2_STATUS); + if (((val & set_mask) == set_mask) && ((val & clr_mask) == 0)) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +static int +ssbi_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); + int ret = 0; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); + ssbi_writel(ssbi, mode2, SSBI2_MODE2); + } + + while (len) { + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); + if (ret) + goto err; + + ssbi_writel(ssbi, cmd, SSBI2_CMD); + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_RD_READY, 0); + if (ret) + goto err; + *buf++ = ssbi_readl(ssbi, SSBI2_RD) & 0xff; + len--; + } + +err: + return ret; +} + +static int +ssbi_write_bytes(struct ssbi *ssbi, u16 addr, const u8 *buf, int len) +{ + int ret = 0; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); + ssbi_writel(ssbi, mode2, SSBI2_MODE2); + } + + while (len) { + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); + if (ret) + goto err; + + ssbi_writel(ssbi, ((addr & 0xff) << 16) | *buf, SSBI2_CMD); + ret = ssbi_wait_mask(ssbi, 0, SSBI_STATUS_MCHN_BUSY); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +/* + * See ssbi_wait_mask for an explanation of the time and the + * busywait. + */ +static inline int +ssbi_pa_transfer(struct ssbi *ssbi, u32 cmd, u8 *data) +{ + u32 timeout = SSBI_TIMEOUT_US; + u32 rd_status = 0; + + ssbi_writel(ssbi, cmd, SSBI_PA_CMD); + + while (timeout--) { + rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DENIED) + return -EPERM; + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DONE) { + if (data) + *data = rd_status & 0xff; + return 0; + } + udelay(1); + } + + return -ETIMEDOUT; +} + +static int +ssbi_pa_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd; + int ret = 0; + + cmd = SSBI_PA_CMD_RDWRN | (addr & SSBI_PA_CMD_ADDR_MASK) << 8; + + while (len) { + ret = ssbi_pa_transfer(ssbi, cmd, buf); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +static int +ssbi_pa_write_bytes(struct ssbi *ssbi, u16 addr, const u8 *buf, int len) +{ + u32 cmd; + int ret = 0; + + while (len) { + cmd = (addr & SSBI_PA_CMD_ADDR_MASK) << 8 | *buf; + ret = ssbi_pa_transfer(ssbi, cmd, NULL); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +int ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) +{ + struct ssbi *ssbi = to_ssbi(dev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ssbi->lock, flags); + ret = ssbi->read(ssbi, addr, buf, len); + spin_unlock_irqrestore(&ssbi->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(ssbi_read); + +int ssbi_write(struct device *dev, u16 addr, const u8 *buf, int len) +{ + struct ssbi *ssbi = to_ssbi(dev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ssbi->lock, flags); + ret = ssbi->write(ssbi, addr, buf, len); + spin_unlock_irqrestore(&ssbi->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(ssbi_write); + +static int ssbi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *mem_res; + struct ssbi *ssbi; + const char *type; + + ssbi = devm_kzalloc(&pdev->dev, sizeof(*ssbi), GFP_KERNEL); + if (!ssbi) + return -ENOMEM; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ssbi->base = devm_ioremap_resource(&pdev->dev, mem_res); + if (IS_ERR(ssbi->base)) + return PTR_ERR(ssbi->base); + + platform_set_drvdata(pdev, ssbi); + + type = of_get_property(np, "qcom,controller-type", NULL); + if (type == NULL) { + dev_err(&pdev->dev, "Missing qcom,controller-type property\n"); + return -EINVAL; + } + dev_info(&pdev->dev, "SSBI controller type: '%s'\n", type); + if (strcmp(type, "ssbi") == 0) + ssbi->controller_type = MSM_SBI_CTRL_SSBI; + else if (strcmp(type, "ssbi2") == 0) + ssbi->controller_type = MSM_SBI_CTRL_SSBI2; + else if (strcmp(type, "pmic-arbiter") == 0) + ssbi->controller_type = MSM_SBI_CTRL_PMIC_ARBITER; + else { + dev_err(&pdev->dev, "Unknown qcom,controller-type\n"); + return -EINVAL; + } + + if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { + ssbi->read = ssbi_pa_read_bytes; + ssbi->write = ssbi_pa_write_bytes; + } else { + ssbi->read = ssbi_read_bytes; + ssbi->write = ssbi_write_bytes; + } + + spin_lock_init(&ssbi->lock); + + return of_platform_populate(np, NULL, NULL, &pdev->dev); +} + +static const struct of_device_id ssbi_match_table[] = { + { .compatible = "qcom,ssbi" }, + {} +}; +MODULE_DEVICE_TABLE(of, ssbi_match_table); + +static struct platform_driver ssbi_driver = { + .probe = ssbi_probe, + .driver = { + .name = "ssbi", + .of_match_table = ssbi_match_table, + }, +}; +module_platform_driver(ssbi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:ssbi"); +MODULE_AUTHOR("Dima Zavin <dima@android.com>"); diff --git a/kernel/drivers/mfd/sta2x11-mfd.c b/kernel/drivers/mfd/sta2x11-mfd.c new file mode 100644 index 000000000..b3e5c6f45 --- /dev/null +++ b/kernel/drivers/mfd/sta2x11-mfd.c @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2009-2011 Wind River Systems, Inc. + * Copyright (c) 2011 ST Microelectronics (Alessandro Rubini, Davide Ciminaghi) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/sta2x11-mfd.h> +#include <linux/regmap.h> + +#include <asm/sta2x11.h> + +static inline int __reg_within_range(unsigned int r, + unsigned int start, + unsigned int end) +{ + return ((r >= start) && (r <= end)); +} + +/* This describes STA2X11 MFD chip for us, we may have several */ +struct sta2x11_mfd { + struct sta2x11_instance *instance; + struct regmap *regmap[sta2x11_n_mfd_plat_devs]; + spinlock_t lock[sta2x11_n_mfd_plat_devs]; + struct list_head list; + void __iomem *regs[sta2x11_n_mfd_plat_devs]; +}; + +static LIST_HEAD(sta2x11_mfd_list); + +/* Three functions to act on the list */ +static struct sta2x11_mfd *sta2x11_mfd_find(struct pci_dev *pdev) +{ + struct sta2x11_instance *instance; + struct sta2x11_mfd *mfd; + + if (!pdev && !list_empty(&sta2x11_mfd_list)) { + pr_warning("%s: Unspecified device, " + "using first instance\n", __func__); + return list_entry(sta2x11_mfd_list.next, + struct sta2x11_mfd, list); + } + + instance = sta2x11_get_instance(pdev); + if (!instance) + return NULL; + list_for_each_entry(mfd, &sta2x11_mfd_list, list) { + if (mfd->instance == instance) + return mfd; + } + return NULL; +} + +static int sta2x11_mfd_add(struct pci_dev *pdev, gfp_t flags) +{ + int i; + struct sta2x11_mfd *mfd = sta2x11_mfd_find(pdev); + struct sta2x11_instance *instance; + + if (mfd) + return -EBUSY; + instance = sta2x11_get_instance(pdev); + if (!instance) + return -EINVAL; + mfd = kzalloc(sizeof(*mfd), flags); + if (!mfd) + return -ENOMEM; + INIT_LIST_HEAD(&mfd->list); + for (i = 0; i < ARRAY_SIZE(mfd->lock); i++) + spin_lock_init(&mfd->lock[i]); + mfd->instance = instance; + list_add(&mfd->list, &sta2x11_mfd_list); + return 0; +} + +/* This function is exported and is not expected to fail */ +u32 __sta2x11_mfd_mask(struct pci_dev *pdev, u32 reg, u32 mask, u32 val, + enum sta2x11_mfd_plat_dev index) +{ + struct sta2x11_mfd *mfd = sta2x11_mfd_find(pdev); + u32 r; + unsigned long flags; + void __iomem *regs; + + if (!mfd) { + dev_warn(&pdev->dev, ": can't access sctl regs\n"); + return 0; + } + + regs = mfd->regs[index]; + if (!regs) { + dev_warn(&pdev->dev, ": system ctl not initialized\n"); + return 0; + } + spin_lock_irqsave(&mfd->lock[index], flags); + r = readl(regs + reg); + r &= ~mask; + r |= val; + if (mask) + writel(r, regs + reg); + spin_unlock_irqrestore(&mfd->lock[index], flags); + return r; +} +EXPORT_SYMBOL(__sta2x11_mfd_mask); + +int sta2x11_mfd_get_regs_data(struct platform_device *dev, + enum sta2x11_mfd_plat_dev index, + void __iomem **regs, + spinlock_t **lock) +{ + struct pci_dev *pdev = *(struct pci_dev **)dev_get_platdata(&dev->dev); + struct sta2x11_mfd *mfd; + + if (!pdev) + return -ENODEV; + mfd = sta2x11_mfd_find(pdev); + if (!mfd) + return -ENODEV; + if (index >= sta2x11_n_mfd_plat_devs) + return -ENODEV; + *regs = mfd->regs[index]; + *lock = &mfd->lock[index]; + pr_debug("%s %d *regs = %p\n", __func__, __LINE__, *regs); + return *regs ? 0 : -ENODEV; +} +EXPORT_SYMBOL(sta2x11_mfd_get_regs_data); + +/* + * Special sta2x11-mfd regmap lock/unlock functions + */ + +static void sta2x11_regmap_lock(void *__lock) +{ + spinlock_t *lock = __lock; + spin_lock(lock); +} + +static void sta2x11_regmap_unlock(void *__lock) +{ + spinlock_t *lock = __lock; + spin_unlock(lock); +} + +/* OTP (one time programmable registers do not require locking */ +static void sta2x11_regmap_nolock(void *__lock) +{ +} + +static const char *sta2x11_mfd_names[sta2x11_n_mfd_plat_devs] = { + [sta2x11_sctl] = STA2X11_MFD_SCTL_NAME, + [sta2x11_apbreg] = STA2X11_MFD_APBREG_NAME, + [sta2x11_apb_soc_regs] = STA2X11_MFD_APB_SOC_REGS_NAME, + [sta2x11_scr] = STA2X11_MFD_SCR_NAME, +}; + +static bool sta2x11_sctl_writeable_reg(struct device *dev, unsigned int reg) +{ + return !__reg_within_range(reg, SCTL_SCPCIECSBRST, SCTL_SCRSTSTA); +} + +static struct regmap_config sta2x11_sctl_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .lock = sta2x11_regmap_lock, + .unlock = sta2x11_regmap_unlock, + .max_register = SCTL_SCRSTSTA, + .writeable_reg = sta2x11_sctl_writeable_reg, +}; + +static bool sta2x11_scr_readable_reg(struct device *dev, unsigned int reg) +{ + return (reg == STA2X11_SECR_CR) || + __reg_within_range(reg, STA2X11_SECR_FVR0, STA2X11_SECR_FVR1); +} + +static bool sta2x11_scr_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static struct regmap_config sta2x11_scr_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .lock = sta2x11_regmap_nolock, + .unlock = sta2x11_regmap_nolock, + .max_register = STA2X11_SECR_FVR1, + .readable_reg = sta2x11_scr_readable_reg, + .writeable_reg = sta2x11_scr_writeable_reg, +}; + +static bool sta2x11_apbreg_readable_reg(struct device *dev, unsigned int reg) +{ + /* Two blocks (CAN and MLB, SARAC) 0x100 bytes apart */ + if (reg >= APBREG_BSR_SARAC) + reg -= APBREG_BSR_SARAC; + switch (reg) { + case APBREG_BSR: + case APBREG_PAER: + case APBREG_PWAC: + case APBREG_PRAC: + case APBREG_PCG: + case APBREG_PUR: + case APBREG_EMU_PCG: + return true; + default: + return false; + } +} + +static bool sta2x11_apbreg_writeable_reg(struct device *dev, unsigned int reg) +{ + if (reg >= APBREG_BSR_SARAC) + reg -= APBREG_BSR_SARAC; + if (!sta2x11_apbreg_readable_reg(dev, reg)) + return false; + return reg != APBREG_PAER; +} + +static struct regmap_config sta2x11_apbreg_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .lock = sta2x11_regmap_lock, + .unlock = sta2x11_regmap_unlock, + .max_register = APBREG_EMU_PCG_SARAC, + .readable_reg = sta2x11_apbreg_readable_reg, + .writeable_reg = sta2x11_apbreg_writeable_reg, +}; + +static bool sta2x11_apb_soc_regs_readable_reg(struct device *dev, + unsigned int reg) +{ + return reg <= PCIE_SoC_INT_ROUTER_STATUS3_REG || + __reg_within_range(reg, DMA_IP_CTRL_REG, SPARE3_RESERVED) || + __reg_within_range(reg, MASTER_LOCK_REG, + SYSTEM_CONFIG_STATUS_REG) || + reg == MSP_CLK_CTRL_REG || + __reg_within_range(reg, COMPENSATION_REG1, TEST_CTL_REG); +} + +static bool sta2x11_apb_soc_regs_writeable_reg(struct device *dev, + unsigned int reg) +{ + if (!sta2x11_apb_soc_regs_readable_reg(dev, reg)) + return false; + switch (reg) { + case PCIE_COMMON_CLOCK_CONFIG_0_4_0: + case SYSTEM_CONFIG_STATUS_REG: + case COMPENSATION_REG1: + case PCIE_SoC_INT_ROUTER_STATUS0_REG...PCIE_SoC_INT_ROUTER_STATUS3_REG: + case PCIE_PM_STATUS_0_PORT_0_4...PCIE_PM_STATUS_7_0_EP4: + return false; + default: + return true; + } +} + +static struct regmap_config sta2x11_apb_soc_regs_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .lock = sta2x11_regmap_lock, + .unlock = sta2x11_regmap_unlock, + .max_register = TEST_CTL_REG, + .readable_reg = sta2x11_apb_soc_regs_readable_reg, + .writeable_reg = sta2x11_apb_soc_regs_writeable_reg, +}; + +static struct regmap_config * +sta2x11_mfd_regmap_configs[sta2x11_n_mfd_plat_devs] = { + [sta2x11_sctl] = &sta2x11_sctl_regmap_config, + [sta2x11_apbreg] = &sta2x11_apbreg_regmap_config, + [sta2x11_apb_soc_regs] = &sta2x11_apb_soc_regs_regmap_config, + [sta2x11_scr] = &sta2x11_scr_regmap_config, +}; + +/* Probe for the four platform devices */ + +static int sta2x11_mfd_platform_probe(struct platform_device *dev, + enum sta2x11_mfd_plat_dev index) +{ + struct pci_dev **pdev; + struct sta2x11_mfd *mfd; + struct resource *res; + const char *name = sta2x11_mfd_names[index]; + struct regmap_config *regmap_config = sta2x11_mfd_regmap_configs[index]; + + pdev = dev_get_platdata(&dev->dev); + mfd = sta2x11_mfd_find(*pdev); + if (!mfd) + return -ENODEV; + if (!regmap_config) + return -ENODEV; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) + return -ENOMEM; + + if (!request_mem_region(res->start, resource_size(res), name)) + return -EBUSY; + + mfd->regs[index] = ioremap(res->start, resource_size(res)); + if (!mfd->regs[index]) { + release_mem_region(res->start, resource_size(res)); + return -ENOMEM; + } + regmap_config->lock_arg = &mfd->lock; + /* + No caching, registers could be reached both via regmap and via + void __iomem * + */ + regmap_config->cache_type = REGCACHE_NONE; + mfd->regmap[index] = devm_regmap_init_mmio(&dev->dev, mfd->regs[index], + regmap_config); + WARN_ON(IS_ERR(mfd->regmap[index])); + + return 0; +} + +static int sta2x11_sctl_probe(struct platform_device *dev) +{ + return sta2x11_mfd_platform_probe(dev, sta2x11_sctl); +} + +static int sta2x11_apbreg_probe(struct platform_device *dev) +{ + return sta2x11_mfd_platform_probe(dev, sta2x11_apbreg); +} + +static int sta2x11_apb_soc_regs_probe(struct platform_device *dev) +{ + return sta2x11_mfd_platform_probe(dev, sta2x11_apb_soc_regs); +} + +static int sta2x11_scr_probe(struct platform_device *dev) +{ + return sta2x11_mfd_platform_probe(dev, sta2x11_scr); +} + +/* The three platform drivers */ +static struct platform_driver sta2x11_sctl_platform_driver = { + .driver = { + .name = STA2X11_MFD_SCTL_NAME, + }, + .probe = sta2x11_sctl_probe, +}; + +static int __init sta2x11_sctl_init(void) +{ + pr_info("%s\n", __func__); + return platform_driver_register(&sta2x11_sctl_platform_driver); +} + +static struct platform_driver sta2x11_platform_driver = { + .driver = { + .name = STA2X11_MFD_APBREG_NAME, + }, + .probe = sta2x11_apbreg_probe, +}; + +static int __init sta2x11_apbreg_init(void) +{ + pr_info("%s\n", __func__); + return platform_driver_register(&sta2x11_platform_driver); +} + +static struct platform_driver sta2x11_apb_soc_regs_platform_driver = { + .driver = { + .name = STA2X11_MFD_APB_SOC_REGS_NAME, + }, + .probe = sta2x11_apb_soc_regs_probe, +}; + +static int __init sta2x11_apb_soc_regs_init(void) +{ + pr_info("%s\n", __func__); + return platform_driver_register(&sta2x11_apb_soc_regs_platform_driver); +} + +static struct platform_driver sta2x11_scr_platform_driver = { + .driver = { + .name = STA2X11_MFD_SCR_NAME, + }, + .probe = sta2x11_scr_probe, +}; + +static int __init sta2x11_scr_init(void) +{ + pr_info("%s\n", __func__); + return platform_driver_register(&sta2x11_scr_platform_driver); +} + + +/* + * What follows are the PCI devices that host the above pdevs. + * Each logic block is 4kB and they are all consecutive: we use this info. + */ + +/* Mfd 0 device */ + +/* Mfd 0, Bar 0 */ +enum mfd0_bar0_cells { + STA2X11_GPIO_0 = 0, + STA2X11_GPIO_1, + STA2X11_GPIO_2, + STA2X11_GPIO_3, + STA2X11_SCTL, + STA2X11_SCR, + STA2X11_TIME, +}; +/* Mfd 0 , Bar 1 */ +enum mfd0_bar1_cells { + STA2X11_APBREG = 0, +}; +#define CELL_4K(_name, _cell) { \ + .name = _name, \ + .start = _cell * 4096, .end = _cell * 4096 + 4095, \ + .flags = IORESOURCE_MEM, \ + } + +static const struct resource gpio_resources[] = { + { + /* 4 consecutive cells, 1 driver */ + .name = STA2X11_MFD_GPIO_NAME, + .start = 0, + .end = (4 * 4096) - 1, + .flags = IORESOURCE_MEM, + } +}; +static const struct resource sctl_resources[] = { + CELL_4K(STA2X11_MFD_SCTL_NAME, STA2X11_SCTL), +}; +static const struct resource scr_resources[] = { + CELL_4K(STA2X11_MFD_SCR_NAME, STA2X11_SCR), +}; +static const struct resource time_resources[] = { + CELL_4K(STA2X11_MFD_TIME_NAME, STA2X11_TIME), +}; + +static const struct resource apbreg_resources[] = { + CELL_4K(STA2X11_MFD_APBREG_NAME, STA2X11_APBREG), +}; + +#define DEV(_name, _r) \ + { .name = _name, .num_resources = ARRAY_SIZE(_r), .resources = _r, } + +static struct mfd_cell sta2x11_mfd0_bar0[] = { + /* offset 0: we add pdata later */ + DEV(STA2X11_MFD_GPIO_NAME, gpio_resources), + DEV(STA2X11_MFD_SCTL_NAME, sctl_resources), + DEV(STA2X11_MFD_SCR_NAME, scr_resources), + DEV(STA2X11_MFD_TIME_NAME, time_resources), +}; + +static struct mfd_cell sta2x11_mfd0_bar1[] = { + DEV(STA2X11_MFD_APBREG_NAME, apbreg_resources), +}; + +/* Mfd 1 devices */ + +/* Mfd 1, Bar 0 */ +enum mfd1_bar0_cells { + STA2X11_VIC = 0, +}; + +/* Mfd 1, Bar 1 */ +enum mfd1_bar1_cells { + STA2X11_APB_SOC_REGS = 0, +}; + +static const struct resource vic_resources[] = { + CELL_4K(STA2X11_MFD_VIC_NAME, STA2X11_VIC), +}; + +static const struct resource apb_soc_regs_resources[] = { + CELL_4K(STA2X11_MFD_APB_SOC_REGS_NAME, STA2X11_APB_SOC_REGS), +}; + +static struct mfd_cell sta2x11_mfd1_bar0[] = { + DEV(STA2X11_MFD_VIC_NAME, vic_resources), +}; + +static struct mfd_cell sta2x11_mfd1_bar1[] = { + DEV(STA2X11_MFD_APB_SOC_REGS_NAME, apb_soc_regs_resources), +}; + + +static int sta2x11_mfd_suspend(struct pci_dev *pdev, pm_message_t state) +{ + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + + return 0; +} + +static int sta2x11_mfd_resume(struct pci_dev *pdev) +{ + int err; + + pci_set_power_state(pdev, PCI_D0); + err = pci_enable_device(pdev); + if (err) + return err; + pci_restore_state(pdev); + + return 0; +} + +struct sta2x11_mfd_bar_setup_data { + struct mfd_cell *cells; + int ncells; +}; + +struct sta2x11_mfd_setup_data { + struct sta2x11_mfd_bar_setup_data bars[2]; +}; + +#define STA2X11_MFD0 0 +#define STA2X11_MFD1 1 + +static struct sta2x11_mfd_setup_data mfd_setup_data[] = { + /* Mfd 0: gpio, sctl, scr, timers / apbregs */ + [STA2X11_MFD0] = { + .bars = { + [0] = { + .cells = sta2x11_mfd0_bar0, + .ncells = ARRAY_SIZE(sta2x11_mfd0_bar0), + }, + [1] = { + .cells = sta2x11_mfd0_bar1, + .ncells = ARRAY_SIZE(sta2x11_mfd0_bar1), + }, + }, + }, + /* Mfd 1: vic / apb-soc-regs */ + [STA2X11_MFD1] = { + .bars = { + [0] = { + .cells = sta2x11_mfd1_bar0, + .ncells = ARRAY_SIZE(sta2x11_mfd1_bar0), + }, + [1] = { + .cells = sta2x11_mfd1_bar1, + .ncells = ARRAY_SIZE(sta2x11_mfd1_bar1), + }, + }, + }, +}; + +static void sta2x11_mfd_setup(struct pci_dev *pdev, + struct sta2x11_mfd_setup_data *sd) +{ + int i, j; + for (i = 0; i < ARRAY_SIZE(sd->bars); i++) + for (j = 0; j < sd->bars[i].ncells; j++) { + sd->bars[i].cells[j].pdata_size = sizeof(pdev); + sd->bars[i].cells[j].platform_data = &pdev; + } +} + +static int sta2x11_mfd_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + int err, i; + struct sta2x11_mfd_setup_data *setup_data; + + dev_info(&pdev->dev, "%s\n", __func__); + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Can't enable device.\n"); + return err; + } + + err = pci_enable_msi(pdev); + if (err) + dev_info(&pdev->dev, "Enable msi failed\n"); + + setup_data = pci_id->device == PCI_DEVICE_ID_STMICRO_GPIO ? + &mfd_setup_data[STA2X11_MFD0] : + &mfd_setup_data[STA2X11_MFD1]; + + /* platform data is the pci device for all of them */ + sta2x11_mfd_setup(pdev, setup_data); + + /* Record this pdev before mfd_add_devices: their probe looks for it */ + if (!sta2x11_mfd_find(pdev)) + sta2x11_mfd_add(pdev, GFP_ATOMIC); + + /* Just 2 bars for all mfd's at present */ + for (i = 0; i < 2; i++) { + err = mfd_add_devices(&pdev->dev, -1, + setup_data->bars[i].cells, + setup_data->bars[i].ncells, + &pdev->resource[i], + 0, NULL); + if (err) { + dev_err(&pdev->dev, + "mfd_add_devices[%d] failed: %d\n", i, err); + goto err_disable; + } + } + + return 0; + +err_disable: + mfd_remove_devices(&pdev->dev); + pci_disable_device(pdev); + pci_disable_msi(pdev); + return err; +} + +static const struct pci_device_id sta2x11_mfd_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_STMICRO, PCI_DEVICE_ID_STMICRO_GPIO)}, + {PCI_DEVICE(PCI_VENDOR_ID_STMICRO, PCI_DEVICE_ID_STMICRO_VIC)}, + {0,}, +}; + +static struct pci_driver sta2x11_mfd_driver = { + .name = "sta2x11-mfd", + .id_table = sta2x11_mfd_tbl, + .probe = sta2x11_mfd_probe, + .suspend = sta2x11_mfd_suspend, + .resume = sta2x11_mfd_resume, +}; + +static int __init sta2x11_mfd_init(void) +{ + pr_info("%s\n", __func__); + return pci_register_driver(&sta2x11_mfd_driver); +} + +/* + * All of this must be ready before "normal" devices like MMCI appear. + * But MFD (the pci device) can't be too early. The following choice + * prepares platform drivers very early and probe the PCI device later, + * but before other PCI devices. + */ +subsys_initcall(sta2x11_apbreg_init); +subsys_initcall(sta2x11_sctl_init); +subsys_initcall(sta2x11_apb_soc_regs_init); +subsys_initcall(sta2x11_scr_init); +rootfs_initcall(sta2x11_mfd_init); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Wind River"); +MODULE_DESCRIPTION("STA2x11 mfd for GPIO, SCTL and APBREG"); +MODULE_DEVICE_TABLE(pci, sta2x11_mfd_tbl); diff --git a/kernel/drivers/mfd/stmpe-i2c.c b/kernel/drivers/mfd/stmpe-i2c.c new file mode 100644 index 000000000..5c054031c --- /dev/null +++ b/kernel/drivers/mfd/stmpe-i2c.c @@ -0,0 +1,140 @@ +/* + * ST Microelectronics MFD: stmpe's i2c client specific driver + * + * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) ST Microelectronics SA 2011 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + * Author: Viresh Kumar <viresh.linux@gmail.com> for ST Microelectronics + */ + +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/of_device.h> +#include "stmpe.h" + +static int i2c_reg_read(struct stmpe *stmpe, u8 reg) +{ + struct i2c_client *i2c = stmpe->client; + + return i2c_smbus_read_byte_data(i2c, reg); +} + +static int i2c_reg_write(struct stmpe *stmpe, u8 reg, u8 val) +{ + struct i2c_client *i2c = stmpe->client; + + return i2c_smbus_write_byte_data(i2c, reg, val); +} + +static int i2c_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values) +{ + struct i2c_client *i2c = stmpe->client; + + return i2c_smbus_read_i2c_block_data(i2c, reg, length, values); +} + +static int i2c_block_write(struct stmpe *stmpe, u8 reg, u8 length, + const u8 *values) +{ + struct i2c_client *i2c = stmpe->client; + + return i2c_smbus_write_i2c_block_data(i2c, reg, length, values); +} + +static struct stmpe_client_info i2c_ci = { + .read_byte = i2c_reg_read, + .write_byte = i2c_reg_write, + .read_block = i2c_block_read, + .write_block = i2c_block_write, +}; + +static const struct of_device_id stmpe_of_match[] = { + { .compatible = "st,stmpe610", .data = (void *)STMPE610, }, + { .compatible = "st,stmpe801", .data = (void *)STMPE801, }, + { .compatible = "st,stmpe811", .data = (void *)STMPE811, }, + { .compatible = "st,stmpe1601", .data = (void *)STMPE1601, }, + { .compatible = "st,stmpe1801", .data = (void *)STMPE1801, }, + { .compatible = "st,stmpe2401", .data = (void *)STMPE2401, }, + { .compatible = "st,stmpe2403", .data = (void *)STMPE2403, }, + {}, +}; +MODULE_DEVICE_TABLE(of, stmpe_of_match); + +static int +stmpe_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + enum stmpe_partnum partnum; + const struct of_device_id *of_id; + + i2c_ci.data = (void *)id; + i2c_ci.irq = i2c->irq; + i2c_ci.client = i2c; + i2c_ci.dev = &i2c->dev; + + of_id = of_match_device(stmpe_of_match, &i2c->dev); + if (!of_id) { + /* + * This happens when the I2C ID matches the node name + * but no real compatible string has been given. + */ + dev_info(&i2c->dev, "matching on node name, compatible is preferred\n"); + partnum = id->driver_data; + } else + partnum = (enum stmpe_partnum)of_id->data; + + return stmpe_probe(&i2c_ci, partnum); +} + +static int stmpe_i2c_remove(struct i2c_client *i2c) +{ + struct stmpe *stmpe = dev_get_drvdata(&i2c->dev); + + return stmpe_remove(stmpe); +} + +static const struct i2c_device_id stmpe_i2c_id[] = { + { "stmpe610", STMPE610 }, + { "stmpe801", STMPE801 }, + { "stmpe811", STMPE811 }, + { "stmpe1601", STMPE1601 }, + { "stmpe1801", STMPE1801 }, + { "stmpe2401", STMPE2401 }, + { "stmpe2403", STMPE2403 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stmpe_id); + +static struct i2c_driver stmpe_i2c_driver = { + .driver = { + .name = "stmpe-i2c", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &stmpe_dev_pm_ops, +#endif + .of_match_table = stmpe_of_match, + }, + .probe = stmpe_i2c_probe, + .remove = stmpe_i2c_remove, + .id_table = stmpe_i2c_id, +}; + +static int __init stmpe_init(void) +{ + return i2c_add_driver(&stmpe_i2c_driver); +} +subsys_initcall(stmpe_init); + +static void __exit stmpe_exit(void) +{ + i2c_del_driver(&stmpe_i2c_driver); +} +module_exit(stmpe_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("STMPE MFD I2C Interface Driver"); +MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>"); diff --git a/kernel/drivers/mfd/stmpe-spi.c b/kernel/drivers/mfd/stmpe-spi.c new file mode 100644 index 000000000..a81badbaa --- /dev/null +++ b/kernel/drivers/mfd/stmpe-spi.c @@ -0,0 +1,149 @@ +/* + * ST Microelectronics MFD: stmpe's spi client specific driver + * + * Copyright (C) ST Microelectronics SA 2011 + * + * License Terms: GNU General Public License, version 2 + * Author: Viresh Kumar <viresh.linux@gmail.com> for ST Microelectronics + */ + +#include <linux/spi/spi.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include "stmpe.h" + +#define READ_CMD (1 << 7) + +static int spi_reg_read(struct stmpe *stmpe, u8 reg) +{ + struct spi_device *spi = stmpe->client; + int status = spi_w8r16(spi, reg | READ_CMD); + + return (status < 0) ? status : status >> 8; +} + +static int spi_reg_write(struct stmpe *stmpe, u8 reg, u8 val) +{ + struct spi_device *spi = stmpe->client; + u16 cmd = (val << 8) | reg; + + return spi_write(spi, (const u8 *)&cmd, 2); +} + +static int spi_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values) +{ + int ret, i; + + for (i = 0; i < length; i++) { + ret = spi_reg_read(stmpe, reg + i); + if (ret < 0) + return ret; + *(values + i) = ret; + } + + return 0; +} + +static int spi_block_write(struct stmpe *stmpe, u8 reg, u8 length, + const u8 *values) +{ + int ret = 0, i; + + for (i = length; i > 0; i--, reg++) { + ret = spi_reg_write(stmpe, reg, *(values + i - 1)); + if (ret < 0) + return ret; + } + + return ret; +} + +static void spi_init(struct stmpe *stmpe) +{ + struct spi_device *spi = stmpe->client; + + spi->bits_per_word = 8; + + /* This register is only present for stmpe811 */ + if (stmpe->variant->id_val == 0x0811) + spi_reg_write(stmpe, STMPE811_REG_SPI_CFG, spi->mode); + + if (spi_setup(spi) < 0) + dev_dbg(&spi->dev, "spi_setup failed\n"); +} + +static struct stmpe_client_info spi_ci = { + .read_byte = spi_reg_read, + .write_byte = spi_reg_write, + .read_block = spi_block_read, + .write_block = spi_block_write, + .init = spi_init, +}; + +static int +stmpe_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + /* don't exceed max specified rate - 1MHz - Limitation of STMPE */ + if (spi->max_speed_hz > 1000000) { + dev_dbg(&spi->dev, "f(sample) %d KHz?\n", + (spi->max_speed_hz/1000)); + return -EINVAL; + } + + spi_ci.irq = spi->irq; + spi_ci.client = spi; + spi_ci.dev = &spi->dev; + + return stmpe_probe(&spi_ci, id->driver_data); +} + +static int stmpe_spi_remove(struct spi_device *spi) +{ + struct stmpe *stmpe = spi_get_drvdata(spi); + + return stmpe_remove(stmpe); +} + +static const struct spi_device_id stmpe_spi_id[] = { + { "stmpe610", STMPE610 }, + { "stmpe801", STMPE801 }, + { "stmpe811", STMPE811 }, + { "stmpe1601", STMPE1601 }, + { "stmpe2401", STMPE2401 }, + { "stmpe2403", STMPE2403 }, + { } +}; +MODULE_DEVICE_TABLE(spi, stmpe_id); + +static struct spi_driver stmpe_spi_driver = { + .driver = { + .name = "stmpe-spi", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &stmpe_dev_pm_ops, +#endif + }, + .probe = stmpe_spi_probe, + .remove = stmpe_spi_remove, + .id_table = stmpe_spi_id, +}; + +static int __init stmpe_init(void) +{ + return spi_register_driver(&stmpe_spi_driver); +} +subsys_initcall(stmpe_init); + +static void __exit stmpe_exit(void) +{ + spi_unregister_driver(&stmpe_spi_driver); +} +module_exit(stmpe_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("STMPE MFD SPI Interface Driver"); +MODULE_AUTHOR("Viresh Kumar <viresh.linux@gmail.com>"); diff --git a/kernel/drivers/mfd/stmpe.c b/kernel/drivers/mfd/stmpe.c new file mode 100644 index 000000000..2d7fae94c --- /dev/null +++ b/kernel/drivers/mfd/stmpe.c @@ -0,0 +1,1308 @@ +/* + * ST Microelectronics MFD: stmpe's driver + * + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/mfd/core.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include "stmpe.h" + +static int __stmpe_enable(struct stmpe *stmpe, unsigned int blocks) +{ + return stmpe->variant->enable(stmpe, blocks, true); +} + +static int __stmpe_disable(struct stmpe *stmpe, unsigned int blocks) +{ + return stmpe->variant->enable(stmpe, blocks, false); +} + +static int __stmpe_reg_read(struct stmpe *stmpe, u8 reg) +{ + int ret; + + ret = stmpe->ci->read_byte(stmpe, reg); + if (ret < 0) + dev_err(stmpe->dev, "failed to read reg %#x: %d\n", reg, ret); + + dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret); + + return ret; +} + +static int __stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val) +{ + int ret; + + dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val); + + ret = stmpe->ci->write_byte(stmpe, reg, val); + if (ret < 0) + dev_err(stmpe->dev, "failed to write reg %#x: %d\n", reg, ret); + + return ret; +} + +static int __stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val) +{ + int ret; + + ret = __stmpe_reg_read(stmpe, reg); + if (ret < 0) + return ret; + + ret &= ~mask; + ret |= val; + + return __stmpe_reg_write(stmpe, reg, ret); +} + +static int __stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, + u8 *values) +{ + int ret; + + ret = stmpe->ci->read_block(stmpe, reg, length, values); + if (ret < 0) + dev_err(stmpe->dev, "failed to read regs %#x: %d\n", reg, ret); + + dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret); + stmpe_dump_bytes("stmpe rd: ", values, length); + + return ret; +} + +static int __stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length); + stmpe_dump_bytes("stmpe wr: ", values, length); + + ret = stmpe->ci->write_block(stmpe, reg, length, values); + if (ret < 0) + dev_err(stmpe->dev, "failed to write regs %#x: %d\n", reg, ret); + + return ret; +} + +/** + * stmpe_enable - enable blocks on an STMPE device + * @stmpe: Device to work on + * @blocks: Mask of blocks (enum stmpe_block values) to enable + */ +int stmpe_enable(struct stmpe *stmpe, unsigned int blocks) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_enable(stmpe, blocks); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_enable); + +/** + * stmpe_disable - disable blocks on an STMPE device + * @stmpe: Device to work on + * @blocks: Mask of blocks (enum stmpe_block values) to enable + */ +int stmpe_disable(struct stmpe *stmpe, unsigned int blocks) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_disable(stmpe, blocks); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_disable); + +/** + * stmpe_reg_read() - read a single STMPE register + * @stmpe: Device to read from + * @reg: Register to read + */ +int stmpe_reg_read(struct stmpe *stmpe, u8 reg) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_reg_read(stmpe, reg); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_reg_read); + +/** + * stmpe_reg_write() - write a single STMPE register + * @stmpe: Device to write to + * @reg: Register to write + * @val: Value to write + */ +int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_reg_write(stmpe, reg, val); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_reg_write); + +/** + * stmpe_set_bits() - set the value of a bitfield in a STMPE register + * @stmpe: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @val: Value to set + */ +int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_set_bits(stmpe, reg, mask, val); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_set_bits); + +/** + * stmpe_block_read() - read multiple STMPE registers + * @stmpe: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_block_read(stmpe, reg, length, values); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_block_read); + +/** + * stmpe_block_write() - write multiple STMPE registers + * @stmpe: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + mutex_lock(&stmpe->lock); + ret = __stmpe_block_write(stmpe, reg, length, values); + mutex_unlock(&stmpe->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_block_write); + +/** + * 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 + * + * @pins is assumed to have a bit set for each of the bits whose alternate + * function is to be changed, numbered according to the GPIOXY numbers. + * + * If the GPIO module is not enabled, this function automatically enables it in + * order to perform the change. + */ +int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block) +{ + struct stmpe_variant_info *variant = stmpe->variant; + u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB]; + int af_bits = variant->af_bits; + int numregs = DIV_ROUND_UP(stmpe->num_gpios * af_bits, 8); + int mask = (1 << af_bits) - 1; + u8 regs[8]; + int af, afperreg, ret; + + if (!variant->get_altfunc) + return 0; + + afperreg = 8 / af_bits; + mutex_lock(&stmpe->lock); + + ret = __stmpe_enable(stmpe, STMPE_BLOCK_GPIO); + if (ret < 0) + goto out; + + ret = __stmpe_block_read(stmpe, regaddr, numregs, regs); + if (ret < 0) + goto out; + + af = variant->get_altfunc(stmpe, block); + + while (pins) { + int pin = __ffs(pins); + int regoffset = numregs - (pin / afperreg) - 1; + int pos = (pin % afperreg) * (8 / afperreg); + + regs[regoffset] &= ~(mask << pos); + regs[regoffset] |= af << pos; + + pins &= ~(1 << pin); + } + + ret = __stmpe_block_write(stmpe, regaddr, numregs, regs); + +out: + mutex_unlock(&stmpe->lock); + return ret; +} +EXPORT_SYMBOL_GPL(stmpe_set_altfunc); + +/* + * GPIO (all variants) + */ + +static struct resource stmpe_gpio_resources[] = { + /* Start and end filled dynamically */ + { + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell stmpe_gpio_cell = { + .name = "stmpe-gpio", + .of_compatible = "st,stmpe-gpio", + .resources = stmpe_gpio_resources, + .num_resources = ARRAY_SIZE(stmpe_gpio_resources), +}; + +static const struct mfd_cell stmpe_gpio_cell_noirq = { + .name = "stmpe-gpio", + .of_compatible = "st,stmpe-gpio", + /* gpio cell resources consist of an irq only so no resources here */ +}; + +/* + * Keypad (1601, 2401, 2403) + */ + +static struct resource stmpe_keypad_resources[] = { + { + .name = "KEYPAD", + .flags = IORESOURCE_IRQ, + }, + { + .name = "KEYPAD_OVER", + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell stmpe_keypad_cell = { + .name = "stmpe-keypad", + .of_compatible = "st,stmpe-keypad", + .resources = stmpe_keypad_resources, + .num_resources = ARRAY_SIZE(stmpe_keypad_resources), +}; + +/* + * STMPE801 + */ +static const u8 stmpe801_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE801_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE801_REG_SYS_CTRL, + [STMPE_IDX_GPMR_LSB] = STMPE801_REG_GPIO_MP_STA, + [STMPE_IDX_GPSR_LSB] = STMPE801_REG_GPIO_SET_PIN, + [STMPE_IDX_GPCR_LSB] = STMPE801_REG_GPIO_SET_PIN, + [STMPE_IDX_GPDR_LSB] = STMPE801_REG_GPIO_DIR, + [STMPE_IDX_IEGPIOR_LSB] = STMPE801_REG_GPIO_INT_EN, + [STMPE_IDX_ISGPIOR_MSB] = STMPE801_REG_GPIO_INT_STA, + +}; + +static struct stmpe_variant_block stmpe801_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = 0, + .block = STMPE_BLOCK_GPIO, + }, +}; + +static struct stmpe_variant_block stmpe801_blocks_noirq[] = { + { + .cell = &stmpe_gpio_cell_noirq, + .block = STMPE_BLOCK_GPIO, + }, +}; + +static int stmpe801_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + if (blocks & STMPE_BLOCK_GPIO) + return 0; + else + return -EINVAL; +} + +static struct stmpe_variant_info stmpe801 = { + .name = "stmpe801", + .id_val = STMPE801_ID, + .id_mask = 0xffff, + .num_gpios = 8, + .regs = stmpe801_regs, + .blocks = stmpe801_blocks, + .num_blocks = ARRAY_SIZE(stmpe801_blocks), + .num_irqs = STMPE801_NR_INTERNAL_IRQS, + .enable = stmpe801_enable, +}; + +static struct stmpe_variant_info stmpe801_noirq = { + .name = "stmpe801", + .id_val = STMPE801_ID, + .id_mask = 0xffff, + .num_gpios = 8, + .regs = stmpe801_regs, + .blocks = stmpe801_blocks_noirq, + .num_blocks = ARRAY_SIZE(stmpe801_blocks_noirq), + .enable = stmpe801_enable, +}; + +/* + * Touchscreen (STMPE811 or STMPE610) + */ + +static struct resource stmpe_ts_resources[] = { + { + .name = "TOUCH_DET", + .flags = IORESOURCE_IRQ, + }, + { + .name = "FIFO_TH", + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell stmpe_ts_cell = { + .name = "stmpe-ts", + .of_compatible = "st,stmpe-ts", + .resources = stmpe_ts_resources, + .num_resources = ARRAY_SIZE(stmpe_ts_resources), +}; + +/* + * STMPE811 or STMPE610 + */ + +static const u8 stmpe811_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE811_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE811_REG_INT_CTRL, + [STMPE_IDX_IER_LSB] = STMPE811_REG_INT_EN, + [STMPE_IDX_ISR_MSB] = STMPE811_REG_INT_STA, + [STMPE_IDX_GPMR_LSB] = STMPE811_REG_GPIO_MP_STA, + [STMPE_IDX_GPSR_LSB] = STMPE811_REG_GPIO_SET_PIN, + [STMPE_IDX_GPCR_LSB] = STMPE811_REG_GPIO_CLR_PIN, + [STMPE_IDX_GPDR_LSB] = STMPE811_REG_GPIO_DIR, + [STMPE_IDX_GPRER_LSB] = STMPE811_REG_GPIO_RE, + [STMPE_IDX_GPFER_LSB] = STMPE811_REG_GPIO_FE, + [STMPE_IDX_GPAFR_U_MSB] = STMPE811_REG_GPIO_AF, + [STMPE_IDX_IEGPIOR_LSB] = STMPE811_REG_GPIO_INT_EN, + [STMPE_IDX_ISGPIOR_MSB] = STMPE811_REG_GPIO_INT_STA, + [STMPE_IDX_GPEDR_MSB] = STMPE811_REG_GPIO_ED, +}; + +static struct stmpe_variant_block stmpe811_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE811_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_ts_cell, + .irq = STMPE811_IRQ_TOUCH_DET, + .block = STMPE_BLOCK_TOUCHSCREEN, + }, +}; + +static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE811_SYS_CTRL2_GPIO_OFF; + + if (blocks & STMPE_BLOCK_ADC) + mask |= STMPE811_SYS_CTRL2_ADC_OFF; + + if (blocks & STMPE_BLOCK_TOUCHSCREEN) + mask |= STMPE811_SYS_CTRL2_TSC_OFF; + + return __stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask, + enable ? 0 : mask); +} + +static int stmpe811_get_altfunc(struct stmpe *stmpe, enum stmpe_block block) +{ + /* 0 for touchscreen, 1 for GPIO */ + return block != STMPE_BLOCK_TOUCHSCREEN; +} + +static struct stmpe_variant_info stmpe811 = { + .name = "stmpe811", + .id_val = 0x0811, + .id_mask = 0xffff, + .num_gpios = 8, + .af_bits = 1, + .regs = stmpe811_regs, + .blocks = stmpe811_blocks, + .num_blocks = ARRAY_SIZE(stmpe811_blocks), + .num_irqs = STMPE811_NR_INTERNAL_IRQS, + .enable = stmpe811_enable, + .get_altfunc = stmpe811_get_altfunc, +}; + +/* Similar to 811, except number of gpios */ +static struct stmpe_variant_info stmpe610 = { + .name = "stmpe610", + .id_val = 0x0811, + .id_mask = 0xffff, + .num_gpios = 6, + .af_bits = 1, + .regs = stmpe811_regs, + .blocks = stmpe811_blocks, + .num_blocks = ARRAY_SIZE(stmpe811_blocks), + .num_irqs = STMPE811_NR_INTERNAL_IRQS, + .enable = stmpe811_enable, + .get_altfunc = stmpe811_get_altfunc, +}; + +/* + * STMPE1601 + */ + +static const u8 stmpe1601_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE1601_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE1601_REG_ICR_LSB, + [STMPE_IDX_IER_LSB] = STMPE1601_REG_IER_LSB, + [STMPE_IDX_ISR_MSB] = STMPE1601_REG_ISR_MSB, + [STMPE_IDX_GPMR_LSB] = STMPE1601_REG_GPIO_MP_LSB, + [STMPE_IDX_GPSR_LSB] = STMPE1601_REG_GPIO_SET_LSB, + [STMPE_IDX_GPCR_LSB] = STMPE1601_REG_GPIO_CLR_LSB, + [STMPE_IDX_GPDR_LSB] = STMPE1601_REG_GPIO_SET_DIR_LSB, + [STMPE_IDX_GPRER_LSB] = STMPE1601_REG_GPIO_RE_LSB, + [STMPE_IDX_GPFER_LSB] = STMPE1601_REG_GPIO_FE_LSB, + [STMPE_IDX_GPPUR_LSB] = STMPE1601_REG_GPIO_PU_LSB, + [STMPE_IDX_GPAFR_U_MSB] = STMPE1601_REG_GPIO_AF_U_MSB, + [STMPE_IDX_IEGPIOR_LSB] = STMPE1601_REG_INT_EN_GPIO_MASK_LSB, + [STMPE_IDX_ISGPIOR_MSB] = STMPE1601_REG_INT_STA_GPIO_MSB, + [STMPE_IDX_GPEDR_MSB] = STMPE1601_REG_GPIO_ED_MSB, +}; + +static struct stmpe_variant_block stmpe1601_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE1601_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_keypad_cell, + .irq = STMPE1601_IRQ_KEYPAD, + .block = STMPE_BLOCK_KEYPAD, + }, +}; + +/* supported autosleep timeout delay (in msecs) */ +static const int stmpe_autosleep_delay[] = { + 4, 16, 32, 64, 128, 256, 512, 1024, +}; + +static int stmpe_round_timeout(int timeout) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(stmpe_autosleep_delay); i++) { + if (stmpe_autosleep_delay[i] >= timeout) + return i; + } + + /* + * requests for delays longer than supported should not return the + * longest supported delay + */ + return -EINVAL; +} + +static int stmpe_autosleep(struct stmpe *stmpe, int autosleep_timeout) +{ + int ret; + + if (!stmpe->variant->enable_autosleep) + return -ENOSYS; + + mutex_lock(&stmpe->lock); + ret = stmpe->variant->enable_autosleep(stmpe, autosleep_timeout); + mutex_unlock(&stmpe->lock); + + return ret; +} + +/* + * Both stmpe 1601/2403 support same layout for autosleep + */ +static int stmpe1601_autosleep(struct stmpe *stmpe, + int autosleep_timeout) +{ + int ret, timeout; + + /* choose the best available timeout */ + timeout = stmpe_round_timeout(autosleep_timeout); + if (timeout < 0) { + dev_err(stmpe->dev, "invalid timeout\n"); + return timeout; + } + + ret = __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL2, + STMPE1601_AUTOSLEEP_TIMEOUT_MASK, + timeout); + if (ret < 0) + return ret; + + return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL2, + STPME1601_AUTOSLEEP_ENABLE, + STPME1601_AUTOSLEEP_ENABLE); +} + +static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE1601_SYS_CTRL_ENABLE_GPIO; + else + mask &= ~STMPE1601_SYS_CTRL_ENABLE_GPIO; + + if (blocks & STMPE_BLOCK_KEYPAD) + mask |= STMPE1601_SYS_CTRL_ENABLE_KPC; + else + mask &= ~STMPE1601_SYS_CTRL_ENABLE_KPC; + + if (blocks & STMPE_BLOCK_PWM) + mask |= STMPE1601_SYS_CTRL_ENABLE_SPWM; + else + mask &= ~STMPE1601_SYS_CTRL_ENABLE_SPWM; + + return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask, + enable ? mask : 0); +} + +static int stmpe1601_get_altfunc(struct stmpe *stmpe, enum stmpe_block block) +{ + switch (block) { + case STMPE_BLOCK_PWM: + return 2; + + case STMPE_BLOCK_KEYPAD: + return 1; + + case STMPE_BLOCK_GPIO: + default: + return 0; + } +} + +static struct stmpe_variant_info stmpe1601 = { + .name = "stmpe1601", + .id_val = 0x0210, + .id_mask = 0xfff0, /* at least 0x0210 and 0x0212 */ + .num_gpios = 16, + .af_bits = 2, + .regs = stmpe1601_regs, + .blocks = stmpe1601_blocks, + .num_blocks = ARRAY_SIZE(stmpe1601_blocks), + .num_irqs = STMPE1601_NR_INTERNAL_IRQS, + .enable = stmpe1601_enable, + .get_altfunc = stmpe1601_get_altfunc, + .enable_autosleep = stmpe1601_autosleep, +}; + +/* + * STMPE1801 + */ +static const u8 stmpe1801_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE1801_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE1801_REG_INT_CTRL_LOW, + [STMPE_IDX_IER_LSB] = STMPE1801_REG_INT_EN_MASK_LOW, + [STMPE_IDX_ISR_LSB] = STMPE1801_REG_INT_STA_LOW, + [STMPE_IDX_GPMR_LSB] = STMPE1801_REG_GPIO_MP_LOW, + [STMPE_IDX_GPSR_LSB] = STMPE1801_REG_GPIO_SET_LOW, + [STMPE_IDX_GPCR_LSB] = STMPE1801_REG_GPIO_CLR_LOW, + [STMPE_IDX_GPDR_LSB] = STMPE1801_REG_GPIO_SET_DIR_LOW, + [STMPE_IDX_GPRER_LSB] = STMPE1801_REG_GPIO_RE_LOW, + [STMPE_IDX_GPFER_LSB] = STMPE1801_REG_GPIO_FE_LOW, + [STMPE_IDX_GPPUR_LSB] = STMPE1801_REG_GPIO_PULL_UP_LOW, + [STMPE_IDX_IEGPIOR_LSB] = STMPE1801_REG_INT_EN_GPIO_MASK_LOW, + [STMPE_IDX_ISGPIOR_LSB] = STMPE1801_REG_INT_STA_GPIO_LOW, +}; + +static struct stmpe_variant_block stmpe1801_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE1801_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_keypad_cell, + .irq = STMPE1801_IRQ_KEYPAD, + .block = STMPE_BLOCK_KEYPAD, + }, +}; + +static int stmpe1801_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE1801_MSK_INT_EN_GPIO; + + if (blocks & STMPE_BLOCK_KEYPAD) + mask |= STMPE1801_MSK_INT_EN_KPC; + + return __stmpe_set_bits(stmpe, STMPE1801_REG_INT_EN_MASK_LOW, mask, + enable ? mask : 0); +} + +static int stmpe1801_reset(struct stmpe *stmpe) +{ + unsigned long timeout; + int ret = 0; + + ret = __stmpe_set_bits(stmpe, STMPE1801_REG_SYS_CTRL, + STMPE1801_MSK_SYS_CTRL_RESET, STMPE1801_MSK_SYS_CTRL_RESET); + if (ret < 0) + return ret; + + timeout = jiffies + msecs_to_jiffies(100); + while (time_before(jiffies, timeout)) { + ret = __stmpe_reg_read(stmpe, STMPE1801_REG_SYS_CTRL); + if (ret < 0) + return ret; + if (!(ret & STMPE1801_MSK_SYS_CTRL_RESET)) + return 0; + usleep_range(100, 200); + } + return -EIO; +} + +static struct stmpe_variant_info stmpe1801 = { + .name = "stmpe1801", + .id_val = STMPE1801_ID, + .id_mask = 0xfff0, + .num_gpios = 18, + .af_bits = 0, + .regs = stmpe1801_regs, + .blocks = stmpe1801_blocks, + .num_blocks = ARRAY_SIZE(stmpe1801_blocks), + .num_irqs = STMPE1801_NR_INTERNAL_IRQS, + .enable = stmpe1801_enable, + /* stmpe1801 do not have any gpio alternate function */ + .get_altfunc = NULL, +}; + +/* + * STMPE24XX + */ + +static const u8 stmpe24xx_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE24XX_REG_CHIP_ID, + [STMPE_IDX_ICR_LSB] = STMPE24XX_REG_ICR_LSB, + [STMPE_IDX_IER_LSB] = STMPE24XX_REG_IER_LSB, + [STMPE_IDX_ISR_MSB] = STMPE24XX_REG_ISR_MSB, + [STMPE_IDX_GPMR_LSB] = STMPE24XX_REG_GPMR_LSB, + [STMPE_IDX_GPSR_LSB] = STMPE24XX_REG_GPSR_LSB, + [STMPE_IDX_GPCR_LSB] = STMPE24XX_REG_GPCR_LSB, + [STMPE_IDX_GPDR_LSB] = STMPE24XX_REG_GPDR_LSB, + [STMPE_IDX_GPRER_LSB] = STMPE24XX_REG_GPRER_LSB, + [STMPE_IDX_GPFER_LSB] = STMPE24XX_REG_GPFER_LSB, + [STMPE_IDX_GPPUR_LSB] = STMPE24XX_REG_GPPUR_LSB, + [STMPE_IDX_GPPDR_LSB] = STMPE24XX_REG_GPPDR_LSB, + [STMPE_IDX_GPAFR_U_MSB] = STMPE24XX_REG_GPAFR_U_MSB, + [STMPE_IDX_IEGPIOR_LSB] = STMPE24XX_REG_IEGPIOR_LSB, + [STMPE_IDX_ISGPIOR_MSB] = STMPE24XX_REG_ISGPIOR_MSB, + [STMPE_IDX_GPEDR_MSB] = STMPE24XX_REG_GPEDR_MSB, +}; + +static struct stmpe_variant_block stmpe24xx_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = STMPE24XX_IRQ_GPIOC, + .block = STMPE_BLOCK_GPIO, + }, + { + .cell = &stmpe_keypad_cell, + .irq = STMPE24XX_IRQ_KEYPAD, + .block = STMPE_BLOCK_KEYPAD, + }, +}; + +static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + unsigned int mask = 0; + + if (blocks & STMPE_BLOCK_GPIO) + mask |= STMPE24XX_SYS_CTRL_ENABLE_GPIO; + + if (blocks & STMPE_BLOCK_KEYPAD) + mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC; + + return __stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask, + enable ? mask : 0); +} + +static int stmpe24xx_get_altfunc(struct stmpe *stmpe, enum stmpe_block block) +{ + switch (block) { + case STMPE_BLOCK_ROTATOR: + return 2; + + case STMPE_BLOCK_KEYPAD: + return 1; + + case STMPE_BLOCK_GPIO: + default: + return 0; + } +} + +static struct stmpe_variant_info stmpe2401 = { + .name = "stmpe2401", + .id_val = 0x0101, + .id_mask = 0xffff, + .num_gpios = 24, + .af_bits = 2, + .regs = stmpe24xx_regs, + .blocks = stmpe24xx_blocks, + .num_blocks = ARRAY_SIZE(stmpe24xx_blocks), + .num_irqs = STMPE24XX_NR_INTERNAL_IRQS, + .enable = stmpe24xx_enable, + .get_altfunc = stmpe24xx_get_altfunc, +}; + +static struct stmpe_variant_info stmpe2403 = { + .name = "stmpe2403", + .id_val = 0x0120, + .id_mask = 0xffff, + .num_gpios = 24, + .af_bits = 2, + .regs = stmpe24xx_regs, + .blocks = stmpe24xx_blocks, + .num_blocks = ARRAY_SIZE(stmpe24xx_blocks), + .num_irqs = STMPE24XX_NR_INTERNAL_IRQS, + .enable = stmpe24xx_enable, + .get_altfunc = stmpe24xx_get_altfunc, + .enable_autosleep = stmpe1601_autosleep, /* same as stmpe1601 */ +}; + +static struct stmpe_variant_info *stmpe_variant_info[STMPE_NBR_PARTS] = { + [STMPE610] = &stmpe610, + [STMPE801] = &stmpe801, + [STMPE811] = &stmpe811, + [STMPE1601] = &stmpe1601, + [STMPE1801] = &stmpe1801, + [STMPE2401] = &stmpe2401, + [STMPE2403] = &stmpe2403, +}; + +/* + * These devices can be connected in a 'no-irq' configuration - the irq pin + * is not used and the device cannot interrupt the CPU. Here we only list + * devices which support this configuration - the driver will fail probing + * for any devices not listed here which are configured in this way. + */ +static struct stmpe_variant_info *stmpe_noirq_variant_info[STMPE_NBR_PARTS] = { + [STMPE801] = &stmpe801_noirq, +}; + +static irqreturn_t stmpe_irq(int irq, void *data) +{ + struct stmpe *stmpe = data; + struct stmpe_variant_info *variant = stmpe->variant; + int num = DIV_ROUND_UP(variant->num_irqs, 8); + u8 israddr; + u8 isr[3]; + int ret; + int i; + + if (variant->id_val == STMPE801_ID) { + int base = irq_create_mapping(stmpe->domain, 0); + + handle_nested_irq(base); + return IRQ_HANDLED; + } + + if (variant->id_val == STMPE1801_ID) + israddr = stmpe->regs[STMPE_IDX_ISR_LSB]; + else + israddr = stmpe->regs[STMPE_IDX_ISR_MSB]; + + ret = stmpe_block_read(stmpe, israddr, num, isr); + if (ret < 0) + return IRQ_NONE; + + for (i = 0; i < num; i++) { + int bank = num - i - 1; + u8 status = isr[i]; + u8 clear; + + status &= stmpe->ier[bank]; + if (!status) + continue; + + clear = status; + while (status) { + int bit = __ffs(status); + int line = bank * 8 + bit; + int nestedirq = irq_create_mapping(stmpe->domain, line); + + handle_nested_irq(nestedirq); + status &= ~(1 << bit); + } + + stmpe_reg_write(stmpe, israddr + i, clear); + } + + return IRQ_HANDLED; +} + +static void stmpe_irq_lock(struct irq_data *data) +{ + struct stmpe *stmpe = irq_data_get_irq_chip_data(data); + + mutex_lock(&stmpe->irq_lock); +} + +static void stmpe_irq_sync_unlock(struct irq_data *data) +{ + struct stmpe *stmpe = irq_data_get_irq_chip_data(data); + struct stmpe_variant_info *variant = stmpe->variant; + int num = DIV_ROUND_UP(variant->num_irqs, 8); + int i; + + for (i = 0; i < num; i++) { + u8 new = stmpe->ier[i]; + u8 old = stmpe->oldier[i]; + + if (new == old) + continue; + + stmpe->oldier[i] = new; + stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new); + } + + mutex_unlock(&stmpe->irq_lock); +} + +static void stmpe_irq_mask(struct irq_data *data) +{ + struct stmpe *stmpe = irq_data_get_irq_chip_data(data); + int offset = data->hwirq; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + stmpe->ier[regoffset] &= ~mask; +} + +static void stmpe_irq_unmask(struct irq_data *data) +{ + struct stmpe *stmpe = irq_data_get_irq_chip_data(data); + int offset = data->hwirq; + int regoffset = offset / 8; + int mask = 1 << (offset % 8); + + stmpe->ier[regoffset] |= mask; +} + +static struct irq_chip stmpe_irq_chip = { + .name = "stmpe", + .irq_bus_lock = stmpe_irq_lock, + .irq_bus_sync_unlock = stmpe_irq_sync_unlock, + .irq_mask = stmpe_irq_mask, + .irq_unmask = stmpe_irq_unmask, +}; + +static int stmpe_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct stmpe *stmpe = d->host_data; + struct irq_chip *chip = NULL; + + if (stmpe->variant->id_val != STMPE801_ID) + chip = &stmpe_irq_chip; + + irq_set_chip_data(virq, stmpe); + irq_set_chip_and_handler(virq, chip, handle_edge_irq); + irq_set_nested_thread(virq, 1); +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static void stmpe_irq_unmap(struct irq_domain *d, unsigned int virq) +{ +#ifdef CONFIG_ARM + set_irq_flags(virq, 0); +#endif + irq_set_chip_and_handler(virq, NULL, NULL); + irq_set_chip_data(virq, NULL); +} + +static struct irq_domain_ops stmpe_irq_ops = { + .map = stmpe_irq_map, + .unmap = stmpe_irq_unmap, + .xlate = irq_domain_xlate_twocell, +}; + +static int stmpe_irq_init(struct stmpe *stmpe, struct device_node *np) +{ + int base = 0; + int num_irqs = stmpe->variant->num_irqs; + + stmpe->domain = irq_domain_add_simple(np, num_irqs, base, + &stmpe_irq_ops, stmpe); + if (!stmpe->domain) { + dev_err(stmpe->dev, "Failed to create irqdomain\n"); + return -ENOSYS; + } + + return 0; +} + +static int stmpe_chip_init(struct stmpe *stmpe) +{ + unsigned int irq_trigger = stmpe->pdata->irq_trigger; + int autosleep_timeout = stmpe->pdata->autosleep_timeout; + struct stmpe_variant_info *variant = stmpe->variant; + u8 icr = 0; + unsigned int id; + u8 data[2]; + int ret; + + ret = stmpe_block_read(stmpe, stmpe->regs[STMPE_IDX_CHIP_ID], + ARRAY_SIZE(data), data); + if (ret < 0) + return ret; + + id = (data[0] << 8) | data[1]; + if ((id & variant->id_mask) != variant->id_val) { + dev_err(stmpe->dev, "unknown chip id: %#x\n", id); + return -EINVAL; + } + + dev_info(stmpe->dev, "%s detected, chip id: %#x\n", variant->name, id); + + /* Disable all modules -- subdrivers should enable what they need. */ + ret = stmpe_disable(stmpe, ~0); + if (ret) + return ret; + + if (id == STMPE1801_ID) { + ret = stmpe1801_reset(stmpe); + if (ret < 0) + return ret; + } + + if (stmpe->irq >= 0) { + if (id == STMPE801_ID) + icr = STMPE801_REG_SYS_CTRL_INT_EN; + else + icr = STMPE_ICR_LSB_GIM; + + /* STMPE801 doesn't support Edge interrupts */ + if (id != STMPE801_ID) { + if (irq_trigger == IRQF_TRIGGER_FALLING || + irq_trigger == IRQF_TRIGGER_RISING) + icr |= STMPE_ICR_LSB_EDGE; + } + + if (irq_trigger == IRQF_TRIGGER_RISING || + irq_trigger == IRQF_TRIGGER_HIGH) { + if (id == STMPE801_ID) + icr |= STMPE801_REG_SYS_CTRL_INT_HI; + else + icr |= STMPE_ICR_LSB_HIGH; + } + } + + if (stmpe->pdata->autosleep) { + ret = stmpe_autosleep(stmpe, autosleep_timeout); + if (ret) + return ret; + } + + return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr); +} + +static int stmpe_add_device(struct stmpe *stmpe, const struct mfd_cell *cell) +{ + return mfd_add_devices(stmpe->dev, stmpe->pdata->id, cell, 1, + NULL, 0, stmpe->domain); +} + +static int stmpe_devices_init(struct stmpe *stmpe) +{ + struct stmpe_variant_info *variant = stmpe->variant; + unsigned int platform_blocks = stmpe->pdata->blocks; + int ret = -EINVAL; + int i, j; + + for (i = 0; i < variant->num_blocks; i++) { + struct stmpe_variant_block *block = &variant->blocks[i]; + + if (!(platform_blocks & block->block)) + continue; + + for (j = 0; j < block->cell->num_resources; j++) { + struct resource *res = + (struct resource *) &block->cell->resources[j]; + + /* Dynamically fill in a variant's IRQ. */ + if (res->flags & IORESOURCE_IRQ) + res->start = res->end = block->irq + j; + } + + platform_blocks &= ~block->block; + ret = stmpe_add_device(stmpe, block->cell); + if (ret) + return ret; + } + + if (platform_blocks) + dev_warn(stmpe->dev, + "platform wants blocks (%#x) not present on variant", + platform_blocks); + + return ret; +} + +static void stmpe_of_probe(struct stmpe_platform_data *pdata, + struct device_node *np) +{ + struct device_node *child; + + pdata->id = of_alias_get_id(np, "stmpe-i2c"); + if (pdata->id < 0) + pdata->id = -1; + + pdata->irq_gpio = of_get_named_gpio_flags(np, "irq-gpio", 0, + &pdata->irq_trigger); + if (gpio_is_valid(pdata->irq_gpio)) + pdata->irq_over_gpio = 1; + else + pdata->irq_trigger = IRQF_TRIGGER_NONE; + + of_property_read_u32(np, "st,autosleep-timeout", + &pdata->autosleep_timeout); + + pdata->autosleep = (pdata->autosleep_timeout) ? true : false; + + for_each_child_of_node(np, child) { + if (!strcmp(child->name, "stmpe_gpio")) { + pdata->blocks |= STMPE_BLOCK_GPIO; + } else if (!strcmp(child->name, "stmpe_keypad")) { + pdata->blocks |= STMPE_BLOCK_KEYPAD; + } else if (!strcmp(child->name, "stmpe_touchscreen")) { + pdata->blocks |= STMPE_BLOCK_TOUCHSCREEN; + } else if (!strcmp(child->name, "stmpe_adc")) { + pdata->blocks |= STMPE_BLOCK_ADC; + } else if (!strcmp(child->name, "stmpe_pwm")) { + pdata->blocks |= STMPE_BLOCK_PWM; + } else if (!strcmp(child->name, "stmpe_rotator")) { + pdata->blocks |= STMPE_BLOCK_ROTATOR; + } + } +} + +/* Called from client specific probe routines */ +int stmpe_probe(struct stmpe_client_info *ci, enum stmpe_partnum partnum) +{ + struct stmpe_platform_data *pdata = dev_get_platdata(ci->dev); + struct device_node *np = ci->dev->of_node; + struct stmpe *stmpe; + int ret; + + if (!pdata) { + if (!np) + return -EINVAL; + + pdata = devm_kzalloc(ci->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + stmpe_of_probe(pdata, np); + + if (of_find_property(np, "interrupts", NULL) == NULL) + ci->irq = -1; + } + + stmpe = devm_kzalloc(ci->dev, sizeof(struct stmpe), GFP_KERNEL); + if (!stmpe) + return -ENOMEM; + + mutex_init(&stmpe->irq_lock); + mutex_init(&stmpe->lock); + + stmpe->dev = ci->dev; + stmpe->client = ci->client; + stmpe->pdata = pdata; + stmpe->ci = ci; + stmpe->partnum = partnum; + stmpe->variant = stmpe_variant_info[partnum]; + stmpe->regs = stmpe->variant->regs; + stmpe->num_gpios = stmpe->variant->num_gpios; + stmpe->vcc = devm_regulator_get_optional(ci->dev, "vcc"); + if (!IS_ERR(stmpe->vcc)) { + ret = regulator_enable(stmpe->vcc); + if (ret) + dev_warn(ci->dev, "failed to enable VCC supply\n"); + } + stmpe->vio = devm_regulator_get_optional(ci->dev, "vio"); + if (!IS_ERR(stmpe->vio)) { + ret = regulator_enable(stmpe->vio); + if (ret) + dev_warn(ci->dev, "failed to enable VIO supply\n"); + } + dev_set_drvdata(stmpe->dev, stmpe); + + if (ci->init) + ci->init(stmpe); + + if (pdata->irq_over_gpio) { + ret = devm_gpio_request_one(ci->dev, pdata->irq_gpio, + GPIOF_DIR_IN, "stmpe"); + if (ret) { + dev_err(stmpe->dev, "failed to request IRQ GPIO: %d\n", + ret); + return ret; + } + + stmpe->irq = gpio_to_irq(pdata->irq_gpio); + } else { + stmpe->irq = ci->irq; + } + + if (stmpe->irq < 0) { + /* use alternate variant info for no-irq mode, if supported */ + dev_info(stmpe->dev, + "%s configured in no-irq mode by platform data\n", + stmpe->variant->name); + if (!stmpe_noirq_variant_info[stmpe->partnum]) { + dev_err(stmpe->dev, + "%s does not support no-irq mode!\n", + stmpe->variant->name); + return -ENODEV; + } + stmpe->variant = stmpe_noirq_variant_info[stmpe->partnum]; + } else if (pdata->irq_trigger == IRQF_TRIGGER_NONE) { + pdata->irq_trigger = irq_get_trigger_type(stmpe->irq); + } + + ret = stmpe_chip_init(stmpe); + if (ret) + return ret; + + if (stmpe->irq >= 0) { + ret = stmpe_irq_init(stmpe, np); + if (ret) + return ret; + + ret = devm_request_threaded_irq(ci->dev, stmpe->irq, NULL, + stmpe_irq, pdata->irq_trigger | IRQF_ONESHOT, + "stmpe", stmpe); + if (ret) { + dev_err(stmpe->dev, "failed to request IRQ: %d\n", + ret); + return ret; + } + } + + ret = stmpe_devices_init(stmpe); + if (!ret) + return 0; + + dev_err(stmpe->dev, "failed to add children\n"); + mfd_remove_devices(stmpe->dev); + + return ret; +} + +int stmpe_remove(struct stmpe *stmpe) +{ + if (!IS_ERR(stmpe->vio)) + regulator_disable(stmpe->vio); + if (!IS_ERR(stmpe->vcc)) + regulator_disable(stmpe->vcc); + + mfd_remove_devices(stmpe->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int stmpe_suspend(struct device *dev) +{ + struct stmpe *stmpe = dev_get_drvdata(dev); + + if (stmpe->irq >= 0 && device_may_wakeup(dev)) + enable_irq_wake(stmpe->irq); + + return 0; +} + +static int stmpe_resume(struct device *dev) +{ + struct stmpe *stmpe = dev_get_drvdata(dev); + + if (stmpe->irq >= 0 && device_may_wakeup(dev)) + disable_irq_wake(stmpe->irq); + + return 0; +} + +const struct dev_pm_ops stmpe_dev_pm_ops = { + .suspend = stmpe_suspend, + .resume = stmpe_resume, +}; +#endif diff --git a/kernel/drivers/mfd/stmpe.h b/kernel/drivers/mfd/stmpe.h new file mode 100644 index 000000000..84adb46b3 --- /dev/null +++ b/kernel/drivers/mfd/stmpe.h @@ -0,0 +1,289 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#ifndef __STMPE_H +#define __STMPE_H + +#include <linux/device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/stmpe.h> +#include <linux/printk.h> +#include <linux/types.h> + +extern const struct dev_pm_ops stmpe_dev_pm_ops; + +#ifdef STMPE_DUMP_BYTES +static inline void stmpe_dump_bytes(const char *str, const void *buf, + size_t len) +{ + print_hex_dump_bytes(str, DUMP_PREFIX_OFFSET, buf, len); +} +#else +static inline void stmpe_dump_bytes(const char *str, const void *buf, + size_t len) +{ +} +#endif + +/** + * struct stmpe_variant_block - information about block + * @cell: base mfd cell + * @irq: interrupt number to be added to each IORESOURCE_IRQ + * in the cell + * @block: block id; used for identification with platform data and for + * enable and altfunc callbacks + */ +struct stmpe_variant_block { + const struct mfd_cell *cell; + int irq; + enum stmpe_block block; +}; + +/** + * struct stmpe_variant_info - variant-specific information + * @name: part name + * @id_val: content of CHIPID register + * @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 + * @enable: callback to enable the specified blocks. + * Called with the I/O lock held. + * @get_altfunc: callback to get the alternate function number for the + * specific block + * @enable_autosleep: callback to configure autosleep with specified timeout + */ +struct stmpe_variant_info { + const char *name; + u16 id_val; + u16 id_mask; + int num_gpios; + int af_bits; + const u8 *regs; + struct stmpe_variant_block *blocks; + int num_blocks; + int num_irqs; + int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable); + int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block); + int (*enable_autosleep)(struct stmpe *stmpe, int autosleep_timeout); +}; + +/** + * struct stmpe_client_info - i2c or spi specific routines/info + * @data: client specific data + * @read_byte: read single byte + * @write_byte: write single byte + * @read_block: read block or multiple bytes + * @write_block: write block or multiple bytes + * @init: client init routine, called during probe + */ +struct stmpe_client_info { + void *data; + int irq; + void *client; + struct device *dev; + int (*read_byte)(struct stmpe *stmpe, u8 reg); + int (*write_byte)(struct stmpe *stmpe, u8 reg, u8 val); + int (*read_block)(struct stmpe *stmpe, u8 reg, u8 len, u8 *values); + int (*write_block)(struct stmpe *stmpe, u8 reg, u8 len, + const u8 *values); + void (*init)(struct stmpe *stmpe); +}; + +int stmpe_probe(struct stmpe_client_info *ci, enum stmpe_partnum partnum); +int stmpe_remove(struct stmpe *stmpe); + +#define STMPE_ICR_LSB_HIGH (1 << 2) +#define STMPE_ICR_LSB_EDGE (1 << 1) +#define STMPE_ICR_LSB_GIM (1 << 0) + +/* + * STMPE801 + */ +#define STMPE801_ID 0x0108 +#define STMPE801_NR_INTERNAL_IRQS 1 + +#define STMPE801_REG_CHIP_ID 0x00 +#define STMPE801_REG_VERSION_ID 0x02 +#define STMPE801_REG_SYS_CTRL 0x04 +#define STMPE801_REG_GPIO_INT_EN 0x08 +#define STMPE801_REG_GPIO_INT_STA 0x09 +#define STMPE801_REG_GPIO_MP_STA 0x10 +#define STMPE801_REG_GPIO_SET_PIN 0x11 +#define STMPE801_REG_GPIO_DIR 0x12 + +#define STMPE801_REG_SYS_CTRL_RESET (1 << 7) +#define STMPE801_REG_SYS_CTRL_INT_EN (1 << 2) +#define STMPE801_REG_SYS_CTRL_INT_HI (1 << 0) + +/* + * STMPE811 + */ + +#define STMPE811_IRQ_TOUCH_DET 0 +#define STMPE811_IRQ_FIFO_TH 1 +#define STMPE811_IRQ_FIFO_OFLOW 2 +#define STMPE811_IRQ_FIFO_FULL 3 +#define STMPE811_IRQ_FIFO_EMPTY 4 +#define STMPE811_IRQ_TEMP_SENS 5 +#define STMPE811_IRQ_ADC 6 +#define STMPE811_IRQ_GPIOC 7 +#define STMPE811_NR_INTERNAL_IRQS 8 + +#define STMPE811_REG_CHIP_ID 0x00 +#define STMPE811_REG_SYS_CTRL2 0x04 +#define STMPE811_REG_SPI_CFG 0x08 +#define STMPE811_REG_INT_CTRL 0x09 +#define STMPE811_REG_INT_EN 0x0A +#define STMPE811_REG_INT_STA 0x0B +#define STMPE811_REG_GPIO_INT_EN 0x0C +#define STMPE811_REG_GPIO_INT_STA 0x0D +#define STMPE811_REG_GPIO_SET_PIN 0x10 +#define STMPE811_REG_GPIO_CLR_PIN 0x11 +#define STMPE811_REG_GPIO_MP_STA 0x12 +#define STMPE811_REG_GPIO_DIR 0x13 +#define STMPE811_REG_GPIO_ED 0x14 +#define STMPE811_REG_GPIO_RE 0x15 +#define STMPE811_REG_GPIO_FE 0x16 +#define STMPE811_REG_GPIO_AF 0x17 + +#define STMPE811_SYS_CTRL2_ADC_OFF (1 << 0) +#define STMPE811_SYS_CTRL2_TSC_OFF (1 << 1) +#define STMPE811_SYS_CTRL2_GPIO_OFF (1 << 2) +#define STMPE811_SYS_CTRL2_TS_OFF (1 << 3) + +/* + * STMPE1601 + */ + +#define STMPE1601_IRQ_GPIOC 8 +#define STMPE1601_IRQ_PWM3 7 +#define STMPE1601_IRQ_PWM2 6 +#define STMPE1601_IRQ_PWM1 5 +#define STMPE1601_IRQ_PWM0 4 +#define STMPE1601_IRQ_KEYPAD_OVER 2 +#define STMPE1601_IRQ_KEYPAD 1 +#define STMPE1601_IRQ_WAKEUP 0 +#define STMPE1601_NR_INTERNAL_IRQS 9 + +#define STMPE1601_REG_SYS_CTRL 0x02 +#define STMPE1601_REG_SYS_CTRL2 0x03 +#define STMPE1601_REG_ICR_LSB 0x11 +#define STMPE1601_REG_IER_LSB 0x13 +#define STMPE1601_REG_ISR_MSB 0x14 +#define STMPE1601_REG_CHIP_ID 0x80 +#define STMPE1601_REG_INT_EN_GPIO_MASK_LSB 0x17 +#define STMPE1601_REG_INT_STA_GPIO_MSB 0x18 +#define STMPE1601_REG_GPIO_MP_LSB 0x87 +#define STMPE1601_REG_GPIO_SET_LSB 0x83 +#define STMPE1601_REG_GPIO_CLR_LSB 0x85 +#define STMPE1601_REG_GPIO_SET_DIR_LSB 0x89 +#define STMPE1601_REG_GPIO_ED_MSB 0x8A +#define STMPE1601_REG_GPIO_RE_LSB 0x8D +#define STMPE1601_REG_GPIO_FE_LSB 0x8F +#define STMPE1601_REG_GPIO_PU_LSB 0x91 +#define STMPE1601_REG_GPIO_AF_U_MSB 0x92 + +#define STMPE1601_SYS_CTRL_ENABLE_GPIO (1 << 3) +#define STMPE1601_SYS_CTRL_ENABLE_KPC (1 << 1) +#define STMPE1601_SYS_CTRL_ENABLE_SPWM (1 << 0) + +/* The 1601/2403 share the same masks */ +#define STMPE1601_AUTOSLEEP_TIMEOUT_MASK (0x7) +#define STPME1601_AUTOSLEEP_ENABLE (1 << 3) + +/* + * STMPE1801 + */ +#define STMPE1801_ID 0xc110 +#define STMPE1801_NR_INTERNAL_IRQS 5 +#define STMPE1801_IRQ_KEYPAD_COMBI 4 +#define STMPE1801_IRQ_GPIOC 3 +#define STMPE1801_IRQ_KEYPAD_OVER 2 +#define STMPE1801_IRQ_KEYPAD 1 +#define STMPE1801_IRQ_WAKEUP 0 + +#define STMPE1801_REG_CHIP_ID 0x00 +#define STMPE1801_REG_SYS_CTRL 0x02 +#define STMPE1801_REG_INT_CTRL_LOW 0x04 +#define STMPE1801_REG_INT_EN_MASK_LOW 0x06 +#define STMPE1801_REG_INT_STA_LOW 0x08 +#define STMPE1801_REG_INT_EN_GPIO_MASK_LOW 0x0A +#define STMPE1801_REG_INT_EN_GPIO_MASK_MID 0x0B +#define STMPE1801_REG_INT_EN_GPIO_MASK_HIGH 0x0C +#define STMPE1801_REG_INT_STA_GPIO_LOW 0x0D +#define STMPE1801_REG_INT_STA_GPIO_MID 0x0E +#define STMPE1801_REG_INT_STA_GPIO_HIGH 0x0F +#define STMPE1801_REG_GPIO_SET_LOW 0x10 +#define STMPE1801_REG_GPIO_SET_MID 0x11 +#define STMPE1801_REG_GPIO_SET_HIGH 0x12 +#define STMPE1801_REG_GPIO_CLR_LOW 0x13 +#define STMPE1801_REG_GPIO_CLR_MID 0x14 +#define STMPE1801_REG_GPIO_CLR_HIGH 0x15 +#define STMPE1801_REG_GPIO_MP_LOW 0x16 +#define STMPE1801_REG_GPIO_MP_MID 0x17 +#define STMPE1801_REG_GPIO_MP_HIGH 0x18 +#define STMPE1801_REG_GPIO_SET_DIR_LOW 0x19 +#define STMPE1801_REG_GPIO_SET_DIR_MID 0x1A +#define STMPE1801_REG_GPIO_SET_DIR_HIGH 0x1B +#define STMPE1801_REG_GPIO_RE_LOW 0x1C +#define STMPE1801_REG_GPIO_RE_MID 0x1D +#define STMPE1801_REG_GPIO_RE_HIGH 0x1E +#define STMPE1801_REG_GPIO_FE_LOW 0x1F +#define STMPE1801_REG_GPIO_FE_MID 0x20 +#define STMPE1801_REG_GPIO_FE_HIGH 0x21 +#define STMPE1801_REG_GPIO_PULL_UP_LOW 0x22 +#define STMPE1801_REG_GPIO_PULL_UP_MID 0x23 +#define STMPE1801_REG_GPIO_PULL_UP_HIGH 0x24 + +#define STMPE1801_MSK_SYS_CTRL_RESET (1 << 7) + +#define STMPE1801_MSK_INT_EN_KPC (1 << 1) +#define STMPE1801_MSK_INT_EN_GPIO (1 << 3) + +/* + * STMPE24xx + */ + +#define STMPE24XX_IRQ_GPIOC 8 +#define STMPE24XX_IRQ_PWM2 7 +#define STMPE24XX_IRQ_PWM1 6 +#define STMPE24XX_IRQ_PWM0 5 +#define STMPE24XX_IRQ_ROT_OVER 4 +#define STMPE24XX_IRQ_ROT 3 +#define STMPE24XX_IRQ_KEYPAD_OVER 2 +#define STMPE24XX_IRQ_KEYPAD 1 +#define STMPE24XX_IRQ_WAKEUP 0 +#define STMPE24XX_NR_INTERNAL_IRQS 9 + +#define STMPE24XX_REG_SYS_CTRL 0x02 +#define STMPE24XX_REG_ICR_LSB 0x11 +#define STMPE24XX_REG_IER_LSB 0x13 +#define STMPE24XX_REG_ISR_MSB 0x14 +#define STMPE24XX_REG_CHIP_ID 0x80 +#define STMPE24XX_REG_IEGPIOR_LSB 0x18 +#define STMPE24XX_REG_ISGPIOR_MSB 0x19 +#define STMPE24XX_REG_GPMR_LSB 0xA4 +#define STMPE24XX_REG_GPSR_LSB 0x85 +#define STMPE24XX_REG_GPCR_LSB 0x88 +#define STMPE24XX_REG_GPDR_LSB 0x8B +#define STMPE24XX_REG_GPEDR_MSB 0x8C +#define STMPE24XX_REG_GPRER_LSB 0x91 +#define STMPE24XX_REG_GPFER_LSB 0x94 +#define STMPE24XX_REG_GPPUR_LSB 0x97 +#define STMPE24XX_REG_GPPDR_LSB 0x9a +#define STMPE24XX_REG_GPAFR_U_MSB 0x9B + +#define STMPE24XX_SYS_CTRL_ENABLE_GPIO (1 << 3) +#define STMPE24XX_SYSCON_ENABLE_PWM (1 << 2) +#define STMPE24XX_SYS_CTRL_ENABLE_KPC (1 << 1) +#define STMPE24XX_SYSCON_ENABLE_ROT (1 << 0) + +#endif diff --git a/kernel/drivers/mfd/stw481x.c b/kernel/drivers/mfd/stw481x.c new file mode 100644 index 000000000..7ceb3df09 --- /dev/null +++ b/kernel/drivers/mfd/stw481x.c @@ -0,0 +1,256 @@ +/* + * Core driver for STw4810/STw4811 + * + * Copyright (C) 2013 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/mfd/core.h> +#include <linux/mfd/stw481x.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> +#include <linux/slab.h> + +/* + * This driver can only access the non-USB portions of STw4811, the register + * range 0x00-0x10 dealing with USB is bound to the two special I2C pins used + * for USB control. + */ + +/* Registers inside the power control address space */ +#define STW_PC_VCORE_SEL 0x05U +#define STW_PC_VAUX_SEL 0x06U +#define STW_PC_VPLL_SEL 0x07U + +/** + * stw481x_get_pctl_reg() - get a power control register + * @stw481x: handle to the stw481x chip + * @reg: power control register to fetch + * + * The power control registers is a set of one-time-programmable registers + * in its own register space, accessed by writing addess bits to these + * two registers: bits 7,6,5 of PCTL_REG_LO corresponds to the 3 LSBs of + * the address and bits 8,9 of PCTL_REG_HI corresponds to the 2 MSBs of + * the address, forming an address space of 5 bits, i.e. 32 registers + * 0x00 ... 0x1f can be obtained. + */ +static int stw481x_get_pctl_reg(struct stw481x *stw481x, u8 reg) +{ + u8 msb = (reg >> 3) & 0x03; + u8 lsb = (reg << 5) & 0xe0; + unsigned int val; + u8 vrfy; + int ret; + + ret = regmap_write(stw481x->map, STW_PCTL_REG_HI, msb); + if (ret) + return ret; + ret = regmap_write(stw481x->map, STW_PCTL_REG_LO, lsb); + if (ret) + return ret; + ret = regmap_read(stw481x->map, STW_PCTL_REG_HI, &val); + if (ret) + return ret; + vrfy = (val & 0x03) << 3; + ret = regmap_read(stw481x->map, STW_PCTL_REG_LO, &val); + if (ret) + return ret; + vrfy |= ((val >> 5) & 0x07); + if (vrfy != reg) + return -EIO; + return (val >> 1) & 0x0f; +} + +static int stw481x_startup(struct stw481x *stw481x) +{ + /* Voltages multiplied by 100 */ + u8 vcore_val[] = { 100, 105, 110, 115, 120, 122, 124, 126, 128, + 130, 132, 134, 136, 138, 140, 145 }; + u8 vpll_val[] = { 105, 120, 130, 180 }; + u8 vaux_val[] = { 15, 18, 25, 28 }; + u8 vcore; + u8 vcore_slp; + u8 vpll; + u8 vaux; + bool vaux_en; + bool it_warn; + int ret; + unsigned int val; + + ret = regmap_read(stw481x->map, STW_CONF1, &val); + if (ret) + return ret; + vaux_en = !!(val & STW_CONF1_PDN_VAUX); + it_warn = !!(val & STW_CONF1_IT_WARN); + + dev_info(&stw481x->client->dev, "voltages %s\n", + (val & STW_CONF1_V_MONITORING) ? "OK" : "LOW"); + dev_info(&stw481x->client->dev, "MMC level shifter %s\n", + (val & STW_CONF1_MMC_LS_STATUS) ? "high impedance" : "ON"); + dev_info(&stw481x->client->dev, "VMMC: %s\n", + (val & STW_CONF1_PDN_VMMC) ? "ON" : "disabled"); + + dev_info(&stw481x->client->dev, "STw481x power control registers:\n"); + + ret = stw481x_get_pctl_reg(stw481x, STW_PC_VCORE_SEL); + if (ret < 0) + return ret; + vcore = ret & 0x0f; + + ret = stw481x_get_pctl_reg(stw481x, STW_PC_VAUX_SEL); + if (ret < 0) + return ret; + vaux = (ret >> 2) & 3; + vpll = (ret >> 4) & 1; /* Save bit 4 */ + + ret = stw481x_get_pctl_reg(stw481x, STW_PC_VPLL_SEL); + if (ret < 0) + return ret; + vpll |= (ret >> 1) & 2; + + dev_info(&stw481x->client->dev, "VCORE: %u.%uV %s\n", + vcore_val[vcore] / 100, vcore_val[vcore] % 100, + (ret & 4) ? "ON" : "OFF"); + + dev_info(&stw481x->client->dev, "VPLL: %u.%uV %s\n", + vpll_val[vpll] / 100, vpll_val[vpll] % 100, + (ret & 0x10) ? "ON" : "OFF"); + + dev_info(&stw481x->client->dev, "VAUX: %u.%uV %s\n", + vaux_val[vaux] / 10, vaux_val[vaux] % 10, + vaux_en ? "ON" : "OFF"); + + ret = regmap_read(stw481x->map, STW_CONF2, &val); + if (ret) + return ret; + + dev_info(&stw481x->client->dev, "TWARN: %s threshold, %s\n", + it_warn ? "below" : "above", + (val & STW_CONF2_MASK_TWARN) ? + "enabled" : "mask through VDDOK"); + dev_info(&stw481x->client->dev, "VMMC: %s\n", + (val & STW_CONF2_VMMC_EXT) ? "internal" : "external"); + dev_info(&stw481x->client->dev, "IT WAKE UP: %s\n", + (val & STW_CONF2_MASK_IT_WAKE_UP) ? "enabled" : "masked"); + dev_info(&stw481x->client->dev, "GPO1: %s\n", + (val & STW_CONF2_GPO1) ? "low" : "high impedance"); + dev_info(&stw481x->client->dev, "GPO2: %s\n", + (val & STW_CONF2_GPO2) ? "low" : "high impedance"); + + ret = regmap_read(stw481x->map, STW_VCORE_SLEEP, &val); + if (ret) + return ret; + vcore_slp = val & 0x0f; + dev_info(&stw481x->client->dev, "VCORE SLEEP: %u.%uV\n", + vcore_val[vcore_slp] / 100, vcore_val[vcore_slp] % 100); + + return 0; +} + +/* + * MFD cells - we have one cell which is selected operation + * mode, and we always have a GPIO cell. + */ +static struct mfd_cell stw481x_cells[] = { + { + .of_compatible = "st,stw481x-vmmc", + .name = "stw481x-vmmc-regulator", + .id = -1, + }, +}; + +static const struct regmap_config stw481x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int stw481x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct stw481x *stw481x; + int ret; + int i; + + stw481x = devm_kzalloc(&client->dev, sizeof(*stw481x), GFP_KERNEL); + if (!stw481x) + return -ENOMEM; + + i2c_set_clientdata(client, stw481x); + stw481x->client = client; + stw481x->map = devm_regmap_init_i2c(client, &stw481x_regmap_config); + if (IS_ERR(stw481x->map)) { + ret = PTR_ERR(stw481x->map); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = stw481x_startup(stw481x); + if (ret) { + dev_err(&client->dev, "chip initialization failed\n"); + return ret; + } + + /* Set up and register the platform devices. */ + for (i = 0; i < ARRAY_SIZE(stw481x_cells); i++) { + /* One state holder for all drivers, this is simple */ + stw481x_cells[i].platform_data = stw481x; + stw481x_cells[i].pdata_size = sizeof(*stw481x); + } + + ret = mfd_add_devices(&client->dev, 0, stw481x_cells, + ARRAY_SIZE(stw481x_cells), NULL, 0, NULL); + if (ret) + return ret; + + dev_info(&client->dev, "initialized STw481x device\n"); + + return ret; +} + +static int stw481x_remove(struct i2c_client *client) +{ + mfd_remove_devices(&client->dev); + return 0; +} + +/* + * This ID table is completely unused, as this is a pure + * device-tree probed driver, but it has to be here due to + * the structure of the I2C core. + */ +static const struct i2c_device_id stw481x_id[] = { + { "stw481x", 0 }, + { }, +}; + +static const struct of_device_id stw481x_match[] = { + { .compatible = "st,stw4810", }, + { .compatible = "st,stw4811", }, + { }, +}; +MODULE_DEVICE_TABLE(of, stw481x_match); + +static struct i2c_driver stw481x_driver = { + .driver = { + .name = "stw481x", + .of_match_table = stw481x_match, + }, + .probe = stw481x_probe, + .remove = stw481x_remove, + .id_table = stw481x_id, +}; + +module_i2c_driver(stw481x_driver); + +MODULE_AUTHOR("Linus Walleij"); +MODULE_DESCRIPTION("STw481x PMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/sun6i-prcm.c b/kernel/drivers/mfd/sun6i-prcm.c new file mode 100644 index 000000000..191173166 --- /dev/null +++ b/kernel/drivers/mfd/sun6i-prcm.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2014 Free Electrons + * + * License Terms: GNU General Public License v2 + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + * + * Allwinner PRCM (Power/Reset/Clock Management) driver + * + */ + +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> + +struct prcm_data { + int nsubdevs; + const struct mfd_cell *subdevs; +}; + +static const struct resource sun6i_a31_ar100_clk_res[] = { + { + .start = 0x0, + .end = 0x3, + .flags = IORESOURCE_MEM, + }, +}; + +static const struct resource sun6i_a31_apb0_clk_res[] = { + { + .start = 0xc, + .end = 0xf, + .flags = IORESOURCE_MEM, + }, +}; + +static const struct resource sun6i_a31_apb0_gates_clk_res[] = { + { + .start = 0x28, + .end = 0x2b, + .flags = IORESOURCE_MEM, + }, +}; + +static const struct resource sun6i_a31_ir_clk_res[] = { + { + .start = 0x54, + .end = 0x57, + .flags = IORESOURCE_MEM, + }, +}; + +static const struct resource sun6i_a31_apb0_rstc_res[] = { + { + .start = 0xb0, + .end = 0xb3, + .flags = IORESOURCE_MEM, + }, +}; + +static const struct mfd_cell sun6i_a31_prcm_subdevs[] = { + { + .name = "sun6i-a31-ar100-clk", + .of_compatible = "allwinner,sun6i-a31-ar100-clk", + .num_resources = ARRAY_SIZE(sun6i_a31_ar100_clk_res), + .resources = sun6i_a31_ar100_clk_res, + }, + { + .name = "sun6i-a31-apb0-clk", + .of_compatible = "allwinner,sun6i-a31-apb0-clk", + .num_resources = ARRAY_SIZE(sun6i_a31_apb0_clk_res), + .resources = sun6i_a31_apb0_clk_res, + }, + { + .name = "sun6i-a31-apb0-gates-clk", + .of_compatible = "allwinner,sun6i-a31-apb0-gates-clk", + .num_resources = ARRAY_SIZE(sun6i_a31_apb0_gates_clk_res), + .resources = sun6i_a31_apb0_gates_clk_res, + }, + { + .name = "sun6i-a31-ir-clk", + .of_compatible = "allwinner,sun4i-a10-mod0-clk", + .num_resources = ARRAY_SIZE(sun6i_a31_ir_clk_res), + .resources = sun6i_a31_ir_clk_res, + }, + { + .name = "sun6i-a31-apb0-clock-reset", + .of_compatible = "allwinner,sun6i-a31-clock-reset", + .num_resources = ARRAY_SIZE(sun6i_a31_apb0_rstc_res), + .resources = sun6i_a31_apb0_rstc_res, + }, +}; + +static const struct mfd_cell sun8i_a23_prcm_subdevs[] = { + { + .name = "sun8i-a23-apb0-clk", + .of_compatible = "allwinner,sun8i-a23-apb0-clk", + .num_resources = ARRAY_SIZE(sun6i_a31_apb0_clk_res), + .resources = sun6i_a31_apb0_clk_res, + }, + { + .name = "sun6i-a31-apb0-gates-clk", + .of_compatible = "allwinner,sun8i-a23-apb0-gates-clk", + .num_resources = ARRAY_SIZE(sun6i_a31_apb0_gates_clk_res), + .resources = sun6i_a31_apb0_gates_clk_res, + }, + { + .name = "sun6i-a31-apb0-clock-reset", + .of_compatible = "allwinner,sun6i-a31-clock-reset", + .num_resources = ARRAY_SIZE(sun6i_a31_apb0_rstc_res), + .resources = sun6i_a31_apb0_rstc_res, + }, +}; + +static const struct prcm_data sun6i_a31_prcm_data = { + .nsubdevs = ARRAY_SIZE(sun6i_a31_prcm_subdevs), + .subdevs = sun6i_a31_prcm_subdevs, +}; + +static const struct prcm_data sun8i_a23_prcm_data = { + .nsubdevs = ARRAY_SIZE(sun8i_a23_prcm_subdevs), + .subdevs = sun8i_a23_prcm_subdevs, +}; + +static const struct of_device_id sun6i_prcm_dt_ids[] = { + { + .compatible = "allwinner,sun6i-a31-prcm", + .data = &sun6i_a31_prcm_data, + }, + { + .compatible = "allwinner,sun8i-a23-prcm", + .data = &sun8i_a23_prcm_data, + }, + { /* sentinel */ }, +}; + +static int sun6i_prcm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + const struct prcm_data *data; + struct resource *res; + int ret; + + match = of_match_node(sun6i_prcm_dt_ids, np); + if (!match) + return -EINVAL; + + data = match->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no prcm memory region provided\n"); + return -ENOENT; + } + + ret = mfd_add_devices(&pdev->dev, 0, data->subdevs, data->nsubdevs, + res, -1, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to add subdevices\n"); + return ret; + } + + return 0; +} + +static struct platform_driver sun6i_prcm_driver = { + .driver = { + .name = "sun6i-prcm", + .of_match_table = sun6i_prcm_dt_ids, + }, + .probe = sun6i_prcm_probe, +}; +module_platform_driver(sun6i_prcm_driver); + +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner sun6i PRCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/syscon.c b/kernel/drivers/mfd/syscon.c new file mode 100644 index 000000000..176bf0fa2 --- /dev/null +++ b/kernel/drivers/mfd/syscon.c @@ -0,0 +1,241 @@ +/* + * System Control Driver + * + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Dong Aisheng <dong.aisheng@linaro.org> + * + * 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/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_data/syscon.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/slab.h> + +static struct platform_driver syscon_driver; + +static DEFINE_SPINLOCK(syscon_list_slock); +static LIST_HEAD(syscon_list); + +struct syscon { + struct device_node *np; + struct regmap *regmap; + struct list_head list; +}; + +static struct regmap_config syscon_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static struct syscon *of_syscon_register(struct device_node *np) +{ + struct syscon *syscon; + struct regmap *regmap; + void __iomem *base; + int ret; + struct regmap_config syscon_config = syscon_regmap_config; + + if (!of_device_is_compatible(np, "syscon")) + return ERR_PTR(-EINVAL); + + syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); + if (!syscon) + return ERR_PTR(-ENOMEM); + + base = of_iomap(np, 0); + if (!base) { + ret = -ENOMEM; + goto err_map; + } + + /* Parse the device's DT node for an endianness specification */ + if (of_property_read_bool(np, "big-endian")) + syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; + else if (of_property_read_bool(np, "little-endian")) + syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; + + regmap = regmap_init_mmio(NULL, base, &syscon_config); + if (IS_ERR(regmap)) { + pr_err("regmap init failed\n"); + ret = PTR_ERR(regmap); + goto err_regmap; + } + + syscon->regmap = regmap; + syscon->np = np; + + spin_lock(&syscon_list_slock); + list_add_tail(&syscon->list, &syscon_list); + spin_unlock(&syscon_list_slock); + + return syscon; + +err_regmap: + iounmap(base); +err_map: + kfree(syscon); + return ERR_PTR(ret); +} + +struct regmap *syscon_node_to_regmap(struct device_node *np) +{ + struct syscon *entry, *syscon = NULL; + + spin_lock(&syscon_list_slock); + + list_for_each_entry(entry, &syscon_list, list) + if (entry->np == np) { + syscon = entry; + break; + } + + spin_unlock(&syscon_list_slock); + + if (!syscon) + syscon = of_syscon_register(np); + + if (IS_ERR(syscon)) + return ERR_CAST(syscon); + + return syscon->regmap; +} +EXPORT_SYMBOL_GPL(syscon_node_to_regmap); + +struct regmap *syscon_regmap_lookup_by_compatible(const char *s) +{ + struct device_node *syscon_np; + struct regmap *regmap; + + syscon_np = of_find_compatible_node(NULL, NULL, s); + if (!syscon_np) + return ERR_PTR(-ENODEV); + + regmap = syscon_node_to_regmap(syscon_np); + of_node_put(syscon_np); + + return regmap; +} +EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); + +static int syscon_match_pdevname(struct device *dev, void *data) +{ + return !strcmp(dev_name(dev), (const char *)data); +} + +struct regmap *syscon_regmap_lookup_by_pdevname(const char *s) +{ + struct device *dev; + struct syscon *syscon; + + dev = driver_find_device(&syscon_driver.driver, NULL, (void *)s, + syscon_match_pdevname); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); + + syscon = dev_get_drvdata(dev); + + return syscon->regmap; +} +EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_pdevname); + +struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, + const char *property) +{ + struct device_node *syscon_np; + struct regmap *regmap; + + if (property) + syscon_np = of_parse_phandle(np, property, 0); + else + syscon_np = np; + + if (!syscon_np) + return ERR_PTR(-ENODEV); + + regmap = syscon_node_to_regmap(syscon_np); + of_node_put(syscon_np); + + return regmap; +} +EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); + +static int syscon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct syscon_platform_data *pdata = dev_get_platdata(dev); + struct syscon *syscon; + struct resource *res; + void __iomem *base; + + syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); + if (!syscon) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + base = devm_ioremap(dev, res->start, resource_size(res)); + if (!base) + return -ENOMEM; + + syscon_regmap_config.max_register = res->end - res->start - 3; + if (pdata) + syscon_regmap_config.name = pdata->label; + syscon->regmap = devm_regmap_init_mmio(dev, base, + &syscon_regmap_config); + if (IS_ERR(syscon->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(syscon->regmap); + } + + platform_set_drvdata(pdev, syscon); + + dev_dbg(dev, "regmap %pR registered\n", res); + + return 0; +} + +static const struct platform_device_id syscon_ids[] = { + { "syscon", }, + { } +}; + +static struct platform_driver syscon_driver = { + .driver = { + .name = "syscon", + }, + .probe = syscon_probe, + .id_table = syscon_ids, +}; + +static int __init syscon_init(void) +{ + return platform_driver_register(&syscon_driver); +} +postcore_initcall(syscon_init); + +static void __exit syscon_exit(void) +{ + platform_driver_unregister(&syscon_driver); +} +module_exit(syscon_exit); + +MODULE_AUTHOR("Dong Aisheng <dong.aisheng@linaro.org>"); +MODULE_DESCRIPTION("System Control driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/t7l66xb.c b/kernel/drivers/mfd/t7l66xb.c new file mode 100644 index 000000000..c09fb5dcc --- /dev/null +++ b/kernel/drivers/mfd/t7l66xb.c @@ -0,0 +1,448 @@ +/* + * + * Toshiba T7L66XB core mfd support + * + * Copyright (c) 2005, 2007, 2008 Ian Molton + * Copyright (c) 2008 Dmitry Baryshkov + * + * 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. + * + * T7L66 features: + * + * Supported in this driver: + * SD/MMC + * SM/NAND flash controller + * + * As yet not supported + * GPIO interface (on NAND pins) + * Serial interface + * TFT 'interface converter' + * PCMCIA interface logic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/t7l66xb.h> + +enum { + T7L66XB_CELL_NAND, + T7L66XB_CELL_MMC, +}; + +static const struct resource t7l66xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_T7L66XB_MMC, + .end = IRQ_T7L66XB_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +#define SCR_REVID 0x08 /* b Revision ID */ +#define SCR_IMR 0x42 /* b Interrupt Mask */ +#define SCR_DEV_CTL 0xe0 /* b Device control */ +#define SCR_ISR 0xe1 /* b Interrupt Status */ +#define SCR_GPO_OC 0xf0 /* b GPO output control */ +#define SCR_GPO_OS 0xf1 /* b GPO output enable */ +#define SCR_GPI_S 0xf2 /* w GPI status */ +#define SCR_APDC 0xf8 /* b Active pullup down ctrl */ + +#define SCR_DEV_CTL_USB BIT(0) /* USB enable */ +#define SCR_DEV_CTL_MMC BIT(1) /* MMC enable */ + +/*--------------------------------------------------------------------------*/ + +struct t7l66xb { + void __iomem *scr; + /* Lock to protect registers requiring read/modify/write ops. */ + spinlock_t lock; + + struct resource rscr; + struct clk *clk48m; + struct clk *clk32k; + int irq; + int irq_base; +}; + +/*--------------------------------------------------------------------------*/ + +static int t7l66xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned long flags; + u8 dev_ctl; + + clk_prepare_enable(t7l66xb->clk32k); + + spin_lock_irqsave(&t7l66xb->lock, flags); + + dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL); + dev_ctl |= SCR_DEV_CTL_MMC; + tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL); + + spin_unlock_irqrestore(&t7l66xb->lock, flags); + + tmio_core_mmc_enable(t7l66xb->scr + 0x200, 0, + t7l66xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static int t7l66xb_mmc_disable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned long flags; + u8 dev_ctl; + + spin_lock_irqsave(&t7l66xb->lock, flags); + + dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL); + dev_ctl &= ~SCR_DEV_CTL_MMC; + tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL); + + spin_unlock_irqrestore(&t7l66xb->lock, flags); + + clk_disable_unprepare(t7l66xb->clk32k); + + return 0; +} + +static void t7l66xb_mmc_pwr(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + + tmio_core_mmc_pwr(t7l66xb->scr + 0x200, 0, state); +} + +static void t7l66xb_mmc_clk_div(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + + tmio_core_mmc_clk_div(t7l66xb->scr + 0x200, 0, state); +} + +/*--------------------------------------------------------------------------*/ + +static struct tmio_mmc_data t7166xb_mmc_data = { + .hclk = 24000000, + .set_pwr = t7l66xb_mmc_pwr, + .set_clk_div = t7l66xb_mmc_clk_div, +}; + +static const struct resource t7l66xb_nand_resources[] = { + { + .start = 0xc00, + .end = 0xc07, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0100, + .end = 0x01ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_T7L66XB_NAND, + .end = IRQ_T7L66XB_NAND, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell t7l66xb_cells[] = { + [T7L66XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = t7l66xb_mmc_enable, + .disable = t7l66xb_mmc_disable, + .platform_data = &t7166xb_mmc_data, + .pdata_size = sizeof(t7166xb_mmc_data), + .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources), + .resources = t7l66xb_mmc_resources, + }, + [T7L66XB_CELL_NAND] = { + .name = "tmio-nand", + .num_resources = ARRAY_SIZE(t7l66xb_nand_resources), + .resources = t7l66xb_nand_resources, + }, +}; + +/*--------------------------------------------------------------------------*/ + +/* Handle the T7L66XB interrupt mux */ +static void t7l66xb_irq(unsigned int irq, struct irq_desc *desc) +{ + struct t7l66xb *t7l66xb = irq_get_handler_data(irq); + unsigned int isr; + unsigned int i, irq_base; + + irq_base = t7l66xb->irq_base; + + while ((isr = tmio_ioread8(t7l66xb->scr + SCR_ISR) & + ~tmio_ioread8(t7l66xb->scr + SCR_IMR))) + for (i = 0; i < T7L66XB_NR_IRQS; i++) + if (isr & (1 << i)) + generic_handle_irq(irq_base + i); +} + +static void t7l66xb_irq_mask(struct irq_data *data) +{ + struct t7l66xb *t7l66xb = irq_data_get_irq_chip_data(data); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&t7l66xb->lock, flags); + imr = tmio_ioread8(t7l66xb->scr + SCR_IMR); + imr |= 1 << (data->irq - t7l66xb->irq_base); + tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR); + spin_unlock_irqrestore(&t7l66xb->lock, flags); +} + +static void t7l66xb_irq_unmask(struct irq_data *data) +{ + struct t7l66xb *t7l66xb = irq_data_get_irq_chip_data(data); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&t7l66xb->lock, flags); + imr = tmio_ioread8(t7l66xb->scr + SCR_IMR); + imr &= ~(1 << (data->irq - t7l66xb->irq_base)); + tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR); + spin_unlock_irqrestore(&t7l66xb->lock, flags); +} + +static struct irq_chip t7l66xb_chip = { + .name = "t7l66xb", + .irq_ack = t7l66xb_irq_mask, + .irq_mask = t7l66xb_irq_mask, + .irq_unmask = t7l66xb_irq_unmask, +}; + +/*--------------------------------------------------------------------------*/ + +/* Install the IRQ handler */ +static void t7l66xb_attach_irq(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = t7l66xb->irq_base; + + for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) { + irq_set_chip_and_handler(irq, &t7l66xb_chip, handle_level_irq); + irq_set_chip_data(irq, t7l66xb); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); +#endif + } + + irq_set_irq_type(t7l66xb->irq, IRQ_TYPE_EDGE_FALLING); + irq_set_handler_data(t7l66xb->irq, t7l66xb); + irq_set_chained_handler(t7l66xb->irq, t7l66xb_irq); +} + +static void t7l66xb_detach_irq(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = t7l66xb->irq_base; + + irq_set_chained_handler(t7l66xb->irq, NULL); + irq_set_handler_data(t7l66xb->irq, NULL); + + for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + irq_set_chip(irq, NULL); + irq_set_chip_data(irq, NULL); + } +} + +/*--------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int t7l66xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + struct t7l66xb_platform_data *pdata = dev_get_platdata(&dev->dev); + + if (pdata && pdata->suspend) + pdata->suspend(dev); + clk_disable_unprepare(t7l66xb->clk48m); + + return 0; +} + +static int t7l66xb_resume(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + struct t7l66xb_platform_data *pdata = dev_get_platdata(&dev->dev); + + clk_prepare_enable(t7l66xb->clk48m); + if (pdata && pdata->resume) + pdata->resume(dev); + + tmio_core_mmc_enable(t7l66xb->scr + 0x200, 0, + t7l66xb_mmc_resources[0].start & 0xfffe); + + return 0; +} +#else +#define t7l66xb_suspend NULL +#define t7l66xb_resume NULL +#endif + +/*--------------------------------------------------------------------------*/ + +static int t7l66xb_probe(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev_get_platdata(&dev->dev); + struct t7l66xb *t7l66xb; + struct resource *iomem, *rscr; + int ret; + + if (pdata == NULL) + return -EINVAL; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) + return -EINVAL; + + t7l66xb = kzalloc(sizeof *t7l66xb, GFP_KERNEL); + if (!t7l66xb) + return -ENOMEM; + + spin_lock_init(&t7l66xb->lock); + + platform_set_drvdata(dev, t7l66xb); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + t7l66xb->irq = ret; + else + goto err_noirq; + + t7l66xb->irq_base = pdata->irq_base; + + t7l66xb->clk32k = clk_get(&dev->dev, "CLK_CK32K"); + if (IS_ERR(t7l66xb->clk32k)) { + ret = PTR_ERR(t7l66xb->clk32k); + goto err_clk32k_get; + } + + t7l66xb->clk48m = clk_get(&dev->dev, "CLK_CK48M"); + if (IS_ERR(t7l66xb->clk48m)) { + ret = PTR_ERR(t7l66xb->clk48m); + goto err_clk48m_get; + } + + rscr = &t7l66xb->rscr; + rscr->name = "t7l66xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_request_scr; + + t7l66xb->scr = ioremap(rscr->start, resource_size(rscr)); + if (!t7l66xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + clk_prepare_enable(t7l66xb->clk48m); + + if (pdata && pdata->enable) + pdata->enable(dev); + + /* Mask all interrupts */ + tmio_iowrite8(0xbf, t7l66xb->scr + SCR_IMR); + + printk(KERN_INFO "%s rev %d @ 0x%08lx, irq %d\n", + dev->name, tmio_ioread8(t7l66xb->scr + SCR_REVID), + (unsigned long)iomem->start, t7l66xb->irq); + + t7l66xb_attach_irq(dev); + + t7l66xb_cells[T7L66XB_CELL_NAND].platform_data = pdata->nand_data; + t7l66xb_cells[T7L66XB_CELL_NAND].pdata_size = sizeof(*pdata->nand_data); + + ret = mfd_add_devices(&dev->dev, dev->id, + t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), + iomem, t7l66xb->irq_base, NULL); + + if (!ret) + return 0; + + t7l66xb_detach_irq(dev); + iounmap(t7l66xb->scr); +err_ioremap: + release_resource(&t7l66xb->rscr); +err_request_scr: + clk_put(t7l66xb->clk48m); +err_clk48m_get: + clk_put(t7l66xb->clk32k); +err_clk32k_get: +err_noirq: + kfree(t7l66xb); + return ret; +} + +static int t7l66xb_remove(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev_get_platdata(&dev->dev); + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + int ret; + + ret = pdata->disable(dev); + clk_disable_unprepare(t7l66xb->clk48m); + clk_put(t7l66xb->clk48m); + clk_disable_unprepare(t7l66xb->clk32k); + clk_put(t7l66xb->clk32k); + t7l66xb_detach_irq(dev); + iounmap(t7l66xb->scr); + release_resource(&t7l66xb->rscr); + mfd_remove_devices(&dev->dev); + kfree(t7l66xb); + + return ret; + +} + +static struct platform_driver t7l66xb_platform_driver = { + .driver = { + .name = "t7l66xb", + }, + .suspend = t7l66xb_suspend, + .resume = t7l66xb_resume, + .probe = t7l66xb_probe, + .remove = t7l66xb_remove, +}; + +/*--------------------------------------------------------------------------*/ + +module_platform_driver(t7l66xb_platform_driver); + +MODULE_DESCRIPTION("Toshiba T7L66XB core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton"); +MODULE_ALIAS("platform:t7l66xb"); diff --git a/kernel/drivers/mfd/tc3589x.c b/kernel/drivers/mfd/tc3589x.c new file mode 100644 index 000000000..cf356395c --- /dev/null +++ b/kernel/drivers/mfd/tc3589x.c @@ -0,0 +1,518 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tc3589x.h> +#include <linux/err.h> + +/** + * enum tc3589x_version - indicates the TC3589x version + */ +enum tc3589x_version { + TC3589X_TC35890, + TC3589X_TC35892, + TC3589X_TC35893, + TC3589X_TC35894, + TC3589X_TC35895, + TC3589X_TC35896, + TC3589X_UNKNOWN, +}; + +#define TC3589x_CLKMODE_MODCTL_SLEEP 0x0 +#define TC3589x_CLKMODE_MODCTL_OPERATION (1 << 0) + +/** + * tc3589x_reg_read() - read a single TC3589x register + * @tc3589x: Device to read from + * @reg: Register to read + */ +int tc3589x_reg_read(struct tc3589x *tc3589x, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(tc3589x->i2c, reg); + if (ret < 0) + dev_err(tc3589x->dev, "failed to read reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc3589x_reg_read); + +/** + * tc3589x_reg_read() - write a single TC3589x register + * @tc3589x: Device to write to + * @reg: Register to read + * @data: Value to write + */ +int tc3589x_reg_write(struct tc3589x *tc3589x, u8 reg, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(tc3589x->i2c, reg, data); + if (ret < 0) + dev_err(tc3589x->dev, "failed to write reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc3589x_reg_write); + +/** + * tc3589x_block_read() - read multiple TC3589x registers + * @tc3589x: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int tc3589x_block_read(struct tc3589x *tc3589x, u8 reg, u8 length, u8 *values) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(tc3589x->i2c, reg, length, values); + if (ret < 0) + dev_err(tc3589x->dev, "failed to read regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc3589x_block_read); + +/** + * tc3589x_block_write() - write multiple TC3589x registers + * @tc3589x: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int tc3589x_block_write(struct tc3589x *tc3589x, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(tc3589x->i2c, reg, length, + values); + if (ret < 0) + dev_err(tc3589x->dev, "failed to write regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc3589x_block_write); + +/** + * tc3589x_set_bits() - set the value of a bitfield in a TC3589x register + * @tc3589x: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @values: Value to set + */ +int tc3589x_set_bits(struct tc3589x *tc3589x, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&tc3589x->lock); + + ret = tc3589x_reg_read(tc3589x, reg); + if (ret < 0) + goto out; + + ret &= ~mask; + ret |= val; + + ret = tc3589x_reg_write(tc3589x, reg, ret); + +out: + mutex_unlock(&tc3589x->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tc3589x_set_bits); + +static struct resource gpio_resources[] = { + { + .start = TC3589x_INT_GPIIRQ, + .end = TC3589x_INT_GPIIRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource keypad_resources[] = { + { + .start = TC3589x_INT_KBDIRQ, + .end = TC3589x_INT_KBDIRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell tc3589x_dev_gpio[] = { + { + .name = "tc3589x-gpio", + .num_resources = ARRAY_SIZE(gpio_resources), + .resources = &gpio_resources[0], + .of_compatible = "toshiba,tc3589x-gpio", + }, +}; + +static const struct mfd_cell tc3589x_dev_keypad[] = { + { + .name = "tc3589x-keypad", + .num_resources = ARRAY_SIZE(keypad_resources), + .resources = &keypad_resources[0], + .of_compatible = "toshiba,tc3589x-keypad", + }, +}; + +static irqreturn_t tc3589x_irq(int irq, void *data) +{ + struct tc3589x *tc3589x = data; + int status; + +again: + status = tc3589x_reg_read(tc3589x, TC3589x_IRQST); + if (status < 0) + return IRQ_NONE; + + while (status) { + int bit = __ffs(status); + int virq = irq_create_mapping(tc3589x->domain, bit); + + handle_nested_irq(virq); + status &= ~(1 << bit); + } + + /* + * A dummy read or write (to any register) appears to be necessary to + * have the last interrupt clear (for example, GPIO IC write) take + * effect. In such a case, recheck for any interrupt which is still + * pending. + */ + status = tc3589x_reg_read(tc3589x, TC3589x_IRQST); + if (status) + goto again; + + return IRQ_HANDLED; +} + +static int tc3589x_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct tc3589x *tc3589x = d->host_data; + + irq_set_chip_data(virq, tc3589x); + irq_set_chip_and_handler(virq, &dummy_irq_chip, + handle_edge_irq); + irq_set_nested_thread(virq, 1); +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static void tc3589x_irq_unmap(struct irq_domain *d, unsigned int virq) +{ +#ifdef CONFIG_ARM + set_irq_flags(virq, 0); +#endif + irq_set_chip_and_handler(virq, NULL, NULL); + irq_set_chip_data(virq, NULL); +} + +static struct irq_domain_ops tc3589x_irq_ops = { + .map = tc3589x_irq_map, + .unmap = tc3589x_irq_unmap, + .xlate = irq_domain_xlate_onecell, +}; + +static int tc3589x_irq_init(struct tc3589x *tc3589x, struct device_node *np) +{ + tc3589x->domain = irq_domain_add_simple( + np, TC3589x_NR_INTERNAL_IRQS, 0, + &tc3589x_irq_ops, tc3589x); + + if (!tc3589x->domain) { + dev_err(tc3589x->dev, "Failed to create irqdomain\n"); + return -ENOSYS; + } + + return 0; +} + +static int tc3589x_chip_init(struct tc3589x *tc3589x) +{ + int manf, ver, ret; + + manf = tc3589x_reg_read(tc3589x, TC3589x_MANFCODE); + if (manf < 0) + return manf; + + ver = tc3589x_reg_read(tc3589x, TC3589x_VERSION); + if (ver < 0) + return ver; + + if (manf != TC3589x_MANFCODE_MAGIC) { + dev_err(tc3589x->dev, "unknown manufacturer: %#x\n", manf); + return -EINVAL; + } + + dev_info(tc3589x->dev, "manufacturer: %#x, version: %#x\n", manf, ver); + + /* + * Put everything except the IRQ module into reset; + * also spare the GPIO module for any pin initialization + * done during pre-kernel boot + */ + ret = tc3589x_reg_write(tc3589x, TC3589x_RSTCTRL, + TC3589x_RSTCTRL_TIMRST + | TC3589x_RSTCTRL_ROTRST + | TC3589x_RSTCTRL_KBDRST); + if (ret < 0) + return ret; + + /* Clear the reset interrupt. */ + return tc3589x_reg_write(tc3589x, TC3589x_RSTINTCLR, 0x1); +} + +static int tc3589x_device_init(struct tc3589x *tc3589x) +{ + int ret = 0; + unsigned int blocks = tc3589x->pdata->block; + + if (blocks & TC3589x_BLOCK_GPIO) { + ret = mfd_add_devices(tc3589x->dev, -1, tc3589x_dev_gpio, + ARRAY_SIZE(tc3589x_dev_gpio), NULL, + 0, tc3589x->domain); + if (ret) { + dev_err(tc3589x->dev, "failed to add gpio child\n"); + return ret; + } + dev_info(tc3589x->dev, "added gpio block\n"); + } + + if (blocks & TC3589x_BLOCK_KEYPAD) { + ret = mfd_add_devices(tc3589x->dev, -1, tc3589x_dev_keypad, + ARRAY_SIZE(tc3589x_dev_keypad), NULL, + 0, tc3589x->domain); + if (ret) { + dev_err(tc3589x->dev, "failed to keypad child\n"); + return ret; + } + dev_info(tc3589x->dev, "added keypad block\n"); + } + + return ret; +} + +static const struct of_device_id tc3589x_match[] = { + /* Legacy compatible string */ + { .compatible = "tc3589x", .data = (void *) TC3589X_UNKNOWN }, + { .compatible = "toshiba,tc35890", .data = (void *) TC3589X_TC35890 }, + { .compatible = "toshiba,tc35892", .data = (void *) TC3589X_TC35892 }, + { .compatible = "toshiba,tc35893", .data = (void *) TC3589X_TC35893 }, + { .compatible = "toshiba,tc35894", .data = (void *) TC3589X_TC35894 }, + { .compatible = "toshiba,tc35895", .data = (void *) TC3589X_TC35895 }, + { .compatible = "toshiba,tc35896", .data = (void *) TC3589X_TC35896 }, + { } +}; + +MODULE_DEVICE_TABLE(of, tc3589x_match); + +static struct tc3589x_platform_data * +tc3589x_of_probe(struct device *dev, enum tc3589x_version *version) +{ + struct device_node *np = dev->of_node; + struct tc3589x_platform_data *pdata; + struct device_node *child; + const struct of_device_id *of_id; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + of_id = of_match_device(tc3589x_match, dev); + if (!of_id) + return ERR_PTR(-ENODEV); + *version = (enum tc3589x_version) of_id->data; + + for_each_child_of_node(np, child) { + if (of_device_is_compatible(child, "toshiba,tc3589x-gpio")) + pdata->block |= TC3589x_BLOCK_GPIO; + if (of_device_is_compatible(child, "toshiba,tc3589x-keypad")) + pdata->block |= TC3589x_BLOCK_KEYPAD; + } + + return pdata; +} + +static int tc3589x_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device_node *np = i2c->dev.of_node; + struct tc3589x_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct tc3589x *tc3589x; + enum tc3589x_version version; + int ret; + + if (!pdata) { + pdata = tc3589x_of_probe(&i2c->dev, &version); + if (IS_ERR(pdata)) { + dev_err(&i2c->dev, "No platform data or DT found\n"); + return PTR_ERR(pdata); + } + } else { + /* When not probing from device tree we have this ID */ + version = id->driver_data; + } + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + tc3589x = devm_kzalloc(&i2c->dev, sizeof(struct tc3589x), + GFP_KERNEL); + if (!tc3589x) + return -ENOMEM; + + mutex_init(&tc3589x->lock); + + tc3589x->dev = &i2c->dev; + tc3589x->i2c = i2c; + tc3589x->pdata = pdata; + + switch (version) { + case TC3589X_TC35893: + case TC3589X_TC35895: + case TC3589X_TC35896: + tc3589x->num_gpio = 20; + break; + case TC3589X_TC35890: + case TC3589X_TC35892: + case TC3589X_TC35894: + case TC3589X_UNKNOWN: + default: + tc3589x->num_gpio = 24; + break; + } + + i2c_set_clientdata(i2c, tc3589x); + + ret = tc3589x_chip_init(tc3589x); + if (ret) + return ret; + + ret = tc3589x_irq_init(tc3589x, np); + if (ret) + return ret; + + ret = request_threaded_irq(tc3589x->i2c->irq, NULL, tc3589x_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tc3589x", tc3589x); + if (ret) { + dev_err(tc3589x->dev, "failed to request IRQ: %d\n", ret); + return ret; + } + + ret = tc3589x_device_init(tc3589x); + if (ret) { + dev_err(tc3589x->dev, "failed to add child devices\n"); + return ret; + } + + return 0; +} + +static int tc3589x_remove(struct i2c_client *client) +{ + struct tc3589x *tc3589x = i2c_get_clientdata(client); + + mfd_remove_devices(tc3589x->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tc3589x_suspend(struct device *dev) +{ + struct tc3589x *tc3589x = dev_get_drvdata(dev); + struct i2c_client *client = tc3589x->i2c; + int ret = 0; + + /* put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) + ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_SLEEP); + + return ret; +} + +static int tc3589x_resume(struct device *dev) +{ + struct tc3589x *tc3589x = dev_get_drvdata(dev); + struct i2c_client *client = tc3589x->i2c; + int ret = 0; + + /* enable the system into operation */ + if (!device_may_wakeup(&client->dev)) + ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_OPERATION); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(tc3589x_dev_pm_ops, tc3589x_suspend, tc3589x_resume); + +static const struct i2c_device_id tc3589x_id[] = { + { "tc35890", TC3589X_TC35890 }, + { "tc35892", TC3589X_TC35892 }, + { "tc35893", TC3589X_TC35893 }, + { "tc35894", TC3589X_TC35894 }, + { "tc35895", TC3589X_TC35895 }, + { "tc35896", TC3589X_TC35896 }, + { "tc3589x", TC3589X_UNKNOWN }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc3589x_id); + +static struct i2c_driver tc3589x_driver = { + .driver = { + .name = "tc3589x", + .owner = THIS_MODULE, + .pm = &tc3589x_dev_pm_ops, + .of_match_table = of_match_ptr(tc3589x_match), + }, + .probe = tc3589x_probe, + .remove = tc3589x_remove, + .id_table = tc3589x_id, +}; + +static int __init tc3589x_init(void) +{ + return i2c_add_driver(&tc3589x_driver); +} +subsys_initcall(tc3589x_init); + +static void __exit tc3589x_exit(void) +{ + i2c_del_driver(&tc3589x_driver); +} +module_exit(tc3589x_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TC3589x MFD core driver"); +MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); diff --git a/kernel/drivers/mfd/tc6387xb.c b/kernel/drivers/mfd/tc6387xb.c new file mode 100644 index 000000000..85fab3729 --- /dev/null +++ b/kernel/drivers/mfd/tc6387xb.c @@ -0,0 +1,241 @@ +/* + * Toshiba TC6387XB support + * Copyright (c) 2005 Ian Molton + * + * 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 file contains TC6387XB base support. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/tc6387xb.h> +#include <linux/slab.h> + +enum { + TC6387XB_CELL_MMC, +}; + +struct tc6387xb { + void __iomem *scr; + struct clk *clk32k; + struct resource rscr; +}; + +static struct resource tc6387xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*--------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int tc6387xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + struct tc6387xb_platform_data *pdata = dev_get_platdata(&dev->dev); + + if (pdata && pdata->suspend) + pdata->suspend(dev); + clk_disable_unprepare(tc6387xb->clk32k); + + return 0; +} + +static int tc6387xb_resume(struct platform_device *dev) +{ + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + struct tc6387xb_platform_data *pdata = dev_get_platdata(&dev->dev); + + clk_prepare_enable(tc6387xb->clk32k); + if (pdata && pdata->resume) + pdata->resume(dev); + + tmio_core_mmc_resume(tc6387xb->scr + 0x200, 0, + tc6387xb_mmc_resources[0].start & 0xfffe); + + return 0; +} +#else +#define tc6387xb_suspend NULL +#define tc6387xb_resume NULL +#endif + +/*--------------------------------------------------------------------------*/ + +static void tc6387xb_mmc_pwr(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + tmio_core_mmc_pwr(tc6387xb->scr + 0x200, 0, state); +} + +static void tc6387xb_mmc_clk_div(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + tmio_core_mmc_clk_div(tc6387xb->scr + 0x200, 0, state); +} + + +static int tc6387xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + clk_prepare_enable(tc6387xb->clk32k); + + tmio_core_mmc_enable(tc6387xb->scr + 0x200, 0, + tc6387xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static int tc6387xb_mmc_disable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + clk_disable_unprepare(tc6387xb->clk32k); + + return 0; +} + +static struct tmio_mmc_data tc6387xb_mmc_data = { + .hclk = 24000000, + .set_pwr = tc6387xb_mmc_pwr, + .set_clk_div = tc6387xb_mmc_clk_div, +}; + +/*--------------------------------------------------------------------------*/ + +static const struct mfd_cell tc6387xb_cells[] = { + [TC6387XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = tc6387xb_mmc_enable, + .disable = tc6387xb_mmc_disable, + .platform_data = &tc6387xb_mmc_data, + .pdata_size = sizeof(tc6387xb_mmc_data), + .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources), + .resources = tc6387xb_mmc_resources, + }, +}; + +static int tc6387xb_probe(struct platform_device *dev) +{ + struct tc6387xb_platform_data *pdata = dev_get_platdata(&dev->dev); + struct resource *iomem, *rscr; + struct clk *clk32k; + struct tc6387xb *tc6387xb; + int irq, ret; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) + return -EINVAL; + + tc6387xb = kzalloc(sizeof(*tc6387xb), GFP_KERNEL); + if (!tc6387xb) + return -ENOMEM; + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + irq = ret; + else + goto err_no_irq; + + clk32k = clk_get(&dev->dev, "CLK_CK32K"); + if (IS_ERR(clk32k)) { + ret = PTR_ERR(clk32k); + goto err_no_clk; + } + + rscr = &tc6387xb->rscr; + rscr->name = "tc6387xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_resource; + + tc6387xb->scr = ioremap(rscr->start, resource_size(rscr)); + if (!tc6387xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + tc6387xb->clk32k = clk32k; + platform_set_drvdata(dev, tc6387xb); + + if (pdata && pdata->enable) + pdata->enable(dev); + + dev_info(&dev->dev, "Toshiba tc6387xb initialised\n"); + + ret = mfd_add_devices(&dev->dev, dev->id, tc6387xb_cells, + ARRAY_SIZE(tc6387xb_cells), iomem, irq, NULL); + + if (!ret) + return 0; + + iounmap(tc6387xb->scr); +err_ioremap: + release_resource(&tc6387xb->rscr); +err_resource: + clk_put(clk32k); +err_no_clk: +err_no_irq: + kfree(tc6387xb); + return ret; +} + +static int tc6387xb_remove(struct platform_device *dev) +{ + struct tc6387xb *tc6387xb = platform_get_drvdata(dev); + + mfd_remove_devices(&dev->dev); + iounmap(tc6387xb->scr); + release_resource(&tc6387xb->rscr); + clk_disable_unprepare(tc6387xb->clk32k); + clk_put(tc6387xb->clk32k); + kfree(tc6387xb); + + return 0; +} + + +static struct platform_driver tc6387xb_platform_driver = { + .driver = { + .name = "tc6387xb", + }, + .probe = tc6387xb_probe, + .remove = tc6387xb_remove, + .suspend = tc6387xb_suspend, + .resume = tc6387xb_resume, +}; + +module_platform_driver(tc6387xb_platform_driver); + +MODULE_DESCRIPTION("Toshiba TC6387XB core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton"); +MODULE_ALIAS("platform:tc6387xb"); + diff --git a/kernel/drivers/mfd/tc6393xb.c b/kernel/drivers/mfd/tc6393xb.c new file mode 100644 index 000000000..63458b39a --- /dev/null +++ b/kernel/drivers/mfd/tc6393xb.c @@ -0,0 +1,865 @@ +/* + * Toshiba TC6393XB SoC support + * + * Copyright(c) 2005-2006 Chris Humbert + * Copyright(c) 2005 Dirk Opfer + * Copyright(c) 2005 Ian Molton <spyro@f2s.com> + * Copyright(c) 2007 Dmitry Baryshkov + * + * Based on code written by Sharp/Lineo for 2.4 kernels + * Based on locomo.c + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/mfd/tc6393xb.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +#define SCR_REVID 0x08 /* b Revision ID */ +#define SCR_ISR 0x50 /* b Interrupt Status */ +#define SCR_IMR 0x52 /* b Interrupt Mask */ +#define SCR_IRR 0x54 /* b Interrupt Routing */ +#define SCR_GPER 0x60 /* w GP Enable */ +#define SCR_GPI_SR(i) (0x64 + (i)) /* b3 GPI Status */ +#define SCR_GPI_IMR(i) (0x68 + (i)) /* b3 GPI INT Mask */ +#define SCR_GPI_EDER(i) (0x6c + (i)) /* b3 GPI Edge Detect Enable */ +#define SCR_GPI_LIR(i) (0x70 + (i)) /* b3 GPI Level Invert */ +#define SCR_GPO_DSR(i) (0x78 + (i)) /* b3 GPO Data Set */ +#define SCR_GPO_DOECR(i) (0x7c + (i)) /* b3 GPO Data OE Control */ +#define SCR_GP_IARCR(i) (0x80 + (i)) /* b3 GP Internal Active Register Control */ +#define SCR_GP_IARLCR(i) (0x84 + (i)) /* b3 GP INTERNAL Active Register Level Control */ +#define SCR_GPI_BCR(i) (0x88 + (i)) /* b3 GPI Buffer Control */ +#define SCR_GPA_IARCR 0x8c /* w GPa Internal Active Register Control */ +#define SCR_GPA_IARLCR 0x90 /* w GPa Internal Active Register Level Control */ +#define SCR_GPA_BCR 0x94 /* w GPa Buffer Control */ +#define SCR_CCR 0x98 /* w Clock Control */ +#define SCR_PLL2CR 0x9a /* w PLL2 Control */ +#define SCR_PLL1CR 0x9c /* l PLL1 Control */ +#define SCR_DIARCR 0xa0 /* b Device Internal Active Register Control */ +#define SCR_DBOCR 0xa1 /* b Device Buffer Off Control */ +#define SCR_FER 0xe0 /* b Function Enable */ +#define SCR_MCR 0xe4 /* w Mode Control */ +#define SCR_CONFIG 0xfc /* b Configuration Control */ +#define SCR_DEBUG 0xff /* b Debug */ + +#define SCR_CCR_CK32K BIT(0) +#define SCR_CCR_USBCK BIT(1) +#define SCR_CCR_UNK1 BIT(4) +#define SCR_CCR_MCLK_MASK (7 << 8) +#define SCR_CCR_MCLK_OFF (0 << 8) +#define SCR_CCR_MCLK_12 (1 << 8) +#define SCR_CCR_MCLK_24 (2 << 8) +#define SCR_CCR_MCLK_48 (3 << 8) +#define SCR_CCR_HCLK_MASK (3 << 12) +#define SCR_CCR_HCLK_24 (0 << 12) +#define SCR_CCR_HCLK_48 (1 << 12) + +#define SCR_FER_USBEN BIT(0) /* USB host enable */ +#define SCR_FER_LCDCVEN BIT(1) /* polysilicon TFT enable */ +#define SCR_FER_SLCDEN BIT(2) /* SLCD enable */ + +#define SCR_MCR_RDY_MASK (3 << 0) +#define SCR_MCR_RDY_OPENDRAIN (0 << 0) +#define SCR_MCR_RDY_TRISTATE (1 << 0) +#define SCR_MCR_RDY_PUSHPULL (2 << 0) +#define SCR_MCR_RDY_UNK BIT(2) +#define SCR_MCR_RDY_EN BIT(3) +#define SCR_MCR_INT_MASK (3 << 4) +#define SCR_MCR_INT_OPENDRAIN (0 << 4) +#define SCR_MCR_INT_TRISTATE (1 << 4) +#define SCR_MCR_INT_PUSHPULL (2 << 4) +#define SCR_MCR_INT_UNK BIT(6) +#define SCR_MCR_INT_EN BIT(7) +/* bits 8 - 16 are unknown */ + +#define TC_GPIO_BIT(i) (1 << (i & 0x7)) + +/*--------------------------------------------------------------------------*/ + +struct tc6393xb { + void __iomem *scr; + + struct gpio_chip gpio; + + struct clk *clk; /* 3,6 Mhz */ + + spinlock_t lock; /* protects RMW cycles */ + + struct { + u8 fer; + u16 ccr; + u8 gpi_bcr[3]; + u8 gpo_dsr[3]; + u8 gpo_doecr[3]; + } suspend_state; + + struct resource rscr; + struct resource *iomem; + int irq; + int irq_base; +}; + +enum { + TC6393XB_CELL_NAND, + TC6393XB_CELL_MMC, + TC6393XB_CELL_OHCI, + TC6393XB_CELL_FB, +}; + +/*--------------------------------------------------------------------------*/ + +static int tc6393xb_nand_enable(struct platform_device *nand) +{ + struct platform_device *dev = to_platform_device(nand->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + /* SMD buffer on */ + dev_dbg(&dev->dev, "SMD buffer on\n"); + tmio_iowrite8(0xff, tc6393xb->scr + SCR_GPI_BCR(1)); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static struct resource tc6393xb_nand_resources[] = { + { + .start = 0x1000, + .end = 0x1007, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0100, + .end = 0x01ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_NAND, + .end = IRQ_TC6393_NAND, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource tc6393xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_MMC, + .end = IRQ_TC6393_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource tc6393xb_ohci_resources[] = { + { + .start = 0x3000, + .end = 0x31ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0300, + .end = 0x03ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x010000, + .end = 0x017fff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x018000, + .end = 0x01ffff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_OHCI, + .end = IRQ_TC6393_OHCI, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource tc6393xb_fb_resources[] = { + { + .start = 0x5000, + .end = 0x51ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0500, + .end = 0x05ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x100000, + .end = 0x1fffff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_FB, + .end = IRQ_TC6393_FB, + .flags = IORESOURCE_IRQ, + }, +}; + +static int tc6393xb_ohci_enable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + u8 fer; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr |= SCR_CCR_USBCK; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + fer = tmio_ioread8(tc6393xb->scr + SCR_FER); + fer |= SCR_FER_USBEN; + tmio_iowrite8(fer, tc6393xb->scr + SCR_FER); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_ohci_disable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + u8 fer; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + fer = tmio_ioread8(tc6393xb->scr + SCR_FER); + fer &= ~SCR_FER_USBEN; + tmio_iowrite8(fer, tc6393xb->scr + SCR_FER); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr &= ~SCR_CCR_USBCK; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_ohci_suspend(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev_get_platdata(dev->dev.parent); + + /* We can't properly store/restore OHCI state, so fail here */ + if (tcpd->resume_restore) + return -EBUSY; + + return tc6393xb_ohci_disable(dev); +} + +static int tc6393xb_fb_enable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr &= ~SCR_CCR_MCLK_MASK; + ccr |= SCR_CCR_MCLK_48; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_fb_disable(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = dev_get_drvdata(dev->dev.parent); + unsigned long flags; + u16 ccr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + ccr = tmio_ioread16(tc6393xb->scr + SCR_CCR); + ccr &= ~SCR_CCR_MCLK_MASK; + ccr |= SCR_CCR_MCLK_OFF; + tmio_iowrite16(ccr, tc6393xb->scr + SCR_CCR); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +int tc6393xb_lcd_set_power(struct platform_device *fb, bool on) +{ + struct platform_device *dev = to_platform_device(fb->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + u8 fer; + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + fer = ioread8(tc6393xb->scr + SCR_FER); + if (on) + fer |= SCR_FER_SLCDEN; + else + fer &= ~SCR_FER_SLCDEN; + iowrite8(fer, tc6393xb->scr + SCR_FER); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} +EXPORT_SYMBOL(tc6393xb_lcd_set_power); + +int tc6393xb_lcd_mode(struct platform_device *fb, + const struct fb_videomode *mode) { + struct platform_device *dev = to_platform_device(fb->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + iowrite16(mode->pixclock, tc6393xb->scr + SCR_PLL1CR + 0); + iowrite16(mode->pixclock >> 16, tc6393xb->scr + SCR_PLL1CR + 2); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} +EXPORT_SYMBOL(tc6393xb_lcd_mode); + +static int tc6393xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_enable(tc6393xb->scr + 0x200, 0, + tc6393xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static int tc6393xb_mmc_resume(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_resume(tc6393xb->scr + 0x200, 0, + tc6393xb_mmc_resources[0].start & 0xfffe); + + return 0; +} + +static void tc6393xb_mmc_pwr(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_pwr(tc6393xb->scr + 0x200, 0, state); +} + +static void tc6393xb_mmc_clk_div(struct platform_device *mmc, int state) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + + tmio_core_mmc_clk_div(tc6393xb->scr + 0x200, 0, state); +} + +static struct tmio_mmc_data tc6393xb_mmc_data = { + .hclk = 24000000, + .set_pwr = tc6393xb_mmc_pwr, + .set_clk_div = tc6393xb_mmc_clk_div, +}; + +static struct mfd_cell tc6393xb_cells[] = { + [TC6393XB_CELL_NAND] = { + .name = "tmio-nand", + .enable = tc6393xb_nand_enable, + .num_resources = ARRAY_SIZE(tc6393xb_nand_resources), + .resources = tc6393xb_nand_resources, + }, + [TC6393XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = tc6393xb_mmc_enable, + .resume = tc6393xb_mmc_resume, + .platform_data = &tc6393xb_mmc_data, + .pdata_size = sizeof(tc6393xb_mmc_data), + .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources), + .resources = tc6393xb_mmc_resources, + }, + [TC6393XB_CELL_OHCI] = { + .name = "tmio-ohci", + .num_resources = ARRAY_SIZE(tc6393xb_ohci_resources), + .resources = tc6393xb_ohci_resources, + .enable = tc6393xb_ohci_enable, + .suspend = tc6393xb_ohci_suspend, + .resume = tc6393xb_ohci_enable, + .disable = tc6393xb_ohci_disable, + }, + [TC6393XB_CELL_FB] = { + .name = "tmio-fb", + .num_resources = ARRAY_SIZE(tc6393xb_fb_resources), + .resources = tc6393xb_fb_resources, + .enable = tc6393xb_fb_enable, + .suspend = tc6393xb_fb_disable, + .resume = tc6393xb_fb_enable, + .disable = tc6393xb_fb_disable, + }, +}; + +/*--------------------------------------------------------------------------*/ + +static int tc6393xb_gpio_get(struct gpio_chip *chip, + unsigned offset) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + + /* XXX: does dsr also represent inputs? */ + return tmio_ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)) + & TC_GPIO_BIT(offset); +} + +static void __tc6393xb_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + u8 dsr; + + dsr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)); + if (value) + dsr |= TC_GPIO_BIT(offset); + else + dsr &= ~TC_GPIO_BIT(offset); + + tmio_iowrite8(dsr, tc6393xb->scr + SCR_GPO_DSR(offset / 8)); +} + +static void tc6393xb_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + unsigned long flags; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + __tc6393xb_gpio_set(chip, offset, value); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); +} + +static int tc6393xb_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + unsigned long flags; + u8 doecr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + doecr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + doecr &= ~TC_GPIO_BIT(offset); + tmio_iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); + unsigned long flags; + u8 doecr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + + __tc6393xb_gpio_set(chip, offset, value); + + doecr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + doecr |= TC_GPIO_BIT(offset); + tmio_iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + + spin_unlock_irqrestore(&tc6393xb->lock, flags); + + return 0; +} + +static int tc6393xb_register_gpio(struct tc6393xb *tc6393xb, int gpio_base) +{ + tc6393xb->gpio.label = "tc6393xb"; + tc6393xb->gpio.base = gpio_base; + tc6393xb->gpio.ngpio = 16; + tc6393xb->gpio.set = tc6393xb_gpio_set; + tc6393xb->gpio.get = tc6393xb_gpio_get; + tc6393xb->gpio.direction_input = tc6393xb_gpio_direction_input; + tc6393xb->gpio.direction_output = tc6393xb_gpio_direction_output; + + return gpiochip_add(&tc6393xb->gpio); +} + +/*--------------------------------------------------------------------------*/ + +static void +tc6393xb_irq(unsigned int irq, struct irq_desc *desc) +{ + struct tc6393xb *tc6393xb = irq_get_handler_data(irq); + unsigned int isr; + unsigned int i, irq_base; + + irq_base = tc6393xb->irq_base; + + while ((isr = tmio_ioread8(tc6393xb->scr + SCR_ISR) & + ~tmio_ioread8(tc6393xb->scr + SCR_IMR))) + for (i = 0; i < TC6393XB_NR_IRQS; i++) { + if (isr & (1 << i)) + generic_handle_irq(irq_base + i); + } +} + +static void tc6393xb_irq_ack(struct irq_data *data) +{ +} + +static void tc6393xb_irq_mask(struct irq_data *data) +{ + struct tc6393xb *tc6393xb = irq_data_get_irq_chip_data(data); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + imr = tmio_ioread8(tc6393xb->scr + SCR_IMR); + imr |= 1 << (data->irq - tc6393xb->irq_base); + tmio_iowrite8(imr, tc6393xb->scr + SCR_IMR); + spin_unlock_irqrestore(&tc6393xb->lock, flags); +} + +static void tc6393xb_irq_unmask(struct irq_data *data) +{ + struct tc6393xb *tc6393xb = irq_data_get_irq_chip_data(data); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&tc6393xb->lock, flags); + imr = tmio_ioread8(tc6393xb->scr + SCR_IMR); + imr &= ~(1 << (data->irq - tc6393xb->irq_base)); + tmio_iowrite8(imr, tc6393xb->scr + SCR_IMR); + spin_unlock_irqrestore(&tc6393xb->lock, flags); +} + +static struct irq_chip tc6393xb_chip = { + .name = "tc6393xb", + .irq_ack = tc6393xb_irq_ack, + .irq_mask = tc6393xb_irq_mask, + .irq_unmask = tc6393xb_irq_unmask, +}; + +static void tc6393xb_attach_irq(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = tc6393xb->irq_base; + + for (irq = irq_base; irq < irq_base + TC6393XB_NR_IRQS; irq++) { + irq_set_chip_and_handler(irq, &tc6393xb_chip, handle_edge_irq); + irq_set_chip_data(irq, tc6393xb); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + + irq_set_irq_type(tc6393xb->irq, IRQ_TYPE_EDGE_FALLING); + irq_set_handler_data(tc6393xb->irq, tc6393xb); + irq_set_chained_handler(tc6393xb->irq, tc6393xb_irq); +} + +static void tc6393xb_detach_irq(struct platform_device *dev) +{ + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_set_chained_handler(tc6393xb->irq, NULL); + irq_set_handler_data(tc6393xb->irq, NULL); + + irq_base = tc6393xb->irq_base; + + for (irq = irq_base; irq < irq_base + TC6393XB_NR_IRQS; irq++) { + set_irq_flags(irq, 0); + irq_set_chip(irq, NULL); + irq_set_chip_data(irq, NULL); + } +} + +/*--------------------------------------------------------------------------*/ + +static int tc6393xb_probe(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev_get_platdata(&dev->dev); + struct tc6393xb *tc6393xb; + struct resource *iomem, *rscr; + int ret; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) + return -EINVAL; + + tc6393xb = kzalloc(sizeof *tc6393xb, GFP_KERNEL); + if (!tc6393xb) { + ret = -ENOMEM; + goto err_kzalloc; + } + + spin_lock_init(&tc6393xb->lock); + + platform_set_drvdata(dev, tc6393xb); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + tc6393xb->irq = ret; + else + goto err_noirq; + + tc6393xb->iomem = iomem; + tc6393xb->irq_base = tcpd->irq_base; + + tc6393xb->clk = clk_get(&dev->dev, "CLK_CK3P6MI"); + if (IS_ERR(tc6393xb->clk)) { + ret = PTR_ERR(tc6393xb->clk); + goto err_clk_get; + } + + rscr = &tc6393xb->rscr; + rscr->name = "tc6393xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_request_scr; + + tc6393xb->scr = ioremap(rscr->start, resource_size(rscr)); + if (!tc6393xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + ret = clk_prepare_enable(tc6393xb->clk); + if (ret) + goto err_clk_enable; + + ret = tcpd->enable(dev); + if (ret) + goto err_enable; + + iowrite8(0, tc6393xb->scr + SCR_FER); + iowrite16(tcpd->scr_pll2cr, tc6393xb->scr + SCR_PLL2CR); + iowrite16(SCR_CCR_UNK1 | SCR_CCR_HCLK_48, + tc6393xb->scr + SCR_CCR); + iowrite16(SCR_MCR_RDY_OPENDRAIN | SCR_MCR_RDY_UNK | SCR_MCR_RDY_EN | + SCR_MCR_INT_OPENDRAIN | SCR_MCR_INT_UNK | SCR_MCR_INT_EN | + BIT(15), tc6393xb->scr + SCR_MCR); + iowrite16(tcpd->scr_gper, tc6393xb->scr + SCR_GPER); + iowrite8(0, tc6393xb->scr + SCR_IRR); + iowrite8(0xbf, tc6393xb->scr + SCR_IMR); + + printk(KERN_INFO "Toshiba tc6393xb revision %d at 0x%08lx, irq %d\n", + tmio_ioread8(tc6393xb->scr + SCR_REVID), + (unsigned long) iomem->start, tc6393xb->irq); + + tc6393xb->gpio.base = -1; + + if (tcpd->gpio_base >= 0) { + ret = tc6393xb_register_gpio(tc6393xb, tcpd->gpio_base); + if (ret) + goto err_gpio_add; + } + + tc6393xb_attach_irq(dev); + + if (tcpd->setup) { + ret = tcpd->setup(dev); + if (ret) + goto err_setup; + } + + tc6393xb_cells[TC6393XB_CELL_NAND].platform_data = tcpd->nand_data; + tc6393xb_cells[TC6393XB_CELL_NAND].pdata_size = + sizeof(*tcpd->nand_data); + tc6393xb_cells[TC6393XB_CELL_FB].platform_data = tcpd->fb_data; + tc6393xb_cells[TC6393XB_CELL_FB].pdata_size = sizeof(*tcpd->fb_data); + + ret = mfd_add_devices(&dev->dev, dev->id, + tc6393xb_cells, ARRAY_SIZE(tc6393xb_cells), + iomem, tcpd->irq_base, NULL); + + if (!ret) + return 0; + + if (tcpd->teardown) + tcpd->teardown(dev); + +err_setup: + tc6393xb_detach_irq(dev); + +err_gpio_add: + if (tc6393xb->gpio.base != -1) + gpiochip_remove(&tc6393xb->gpio); + tcpd->disable(dev); +err_enable: + clk_disable_unprepare(tc6393xb->clk); +err_clk_enable: + iounmap(tc6393xb->scr); +err_ioremap: + release_resource(&tc6393xb->rscr); +err_request_scr: + clk_put(tc6393xb->clk); +err_noirq: +err_clk_get: + kfree(tc6393xb); +err_kzalloc: + return ret; +} + +static int tc6393xb_remove(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev_get_platdata(&dev->dev); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int ret; + + mfd_remove_devices(&dev->dev); + + if (tcpd->teardown) + tcpd->teardown(dev); + + tc6393xb_detach_irq(dev); + + if (tc6393xb->gpio.base != -1) + gpiochip_remove(&tc6393xb->gpio); + + ret = tcpd->disable(dev); + clk_disable_unprepare(tc6393xb->clk); + iounmap(tc6393xb->scr); + release_resource(&tc6393xb->rscr); + clk_put(tc6393xb->clk); + kfree(tc6393xb); + + return ret; +} + +#ifdef CONFIG_PM +static int tc6393xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tc6393xb_platform_data *tcpd = dev_get_platdata(&dev->dev); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int i, ret; + + tc6393xb->suspend_state.ccr = ioread16(tc6393xb->scr + SCR_CCR); + tc6393xb->suspend_state.fer = ioread8(tc6393xb->scr + SCR_FER); + + for (i = 0; i < 3; i++) { + tc6393xb->suspend_state.gpo_dsr[i] = + ioread8(tc6393xb->scr + SCR_GPO_DSR(i)); + tc6393xb->suspend_state.gpo_doecr[i] = + ioread8(tc6393xb->scr + SCR_GPO_DOECR(i)); + tc6393xb->suspend_state.gpi_bcr[i] = + ioread8(tc6393xb->scr + SCR_GPI_BCR(i)); + } + ret = tcpd->suspend(dev); + clk_disable_unprepare(tc6393xb->clk); + + return ret; +} + +static int tc6393xb_resume(struct platform_device *dev) +{ + struct tc6393xb_platform_data *tcpd = dev_get_platdata(&dev->dev); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int ret; + int i; + + clk_prepare_enable(tc6393xb->clk); + + ret = tcpd->resume(dev); + if (ret) + return ret; + + if (!tcpd->resume_restore) + return 0; + + iowrite8(tc6393xb->suspend_state.fer, tc6393xb->scr + SCR_FER); + iowrite16(tcpd->scr_pll2cr, tc6393xb->scr + SCR_PLL2CR); + iowrite16(tc6393xb->suspend_state.ccr, tc6393xb->scr + SCR_CCR); + iowrite16(SCR_MCR_RDY_OPENDRAIN | SCR_MCR_RDY_UNK | SCR_MCR_RDY_EN | + SCR_MCR_INT_OPENDRAIN | SCR_MCR_INT_UNK | SCR_MCR_INT_EN | + BIT(15), tc6393xb->scr + SCR_MCR); + iowrite16(tcpd->scr_gper, tc6393xb->scr + SCR_GPER); + iowrite8(0, tc6393xb->scr + SCR_IRR); + iowrite8(0xbf, tc6393xb->scr + SCR_IMR); + + for (i = 0; i < 3; i++) { + iowrite8(tc6393xb->suspend_state.gpo_dsr[i], + tc6393xb->scr + SCR_GPO_DSR(i)); + iowrite8(tc6393xb->suspend_state.gpo_doecr[i], + tc6393xb->scr + SCR_GPO_DOECR(i)); + iowrite8(tc6393xb->suspend_state.gpi_bcr[i], + tc6393xb->scr + SCR_GPI_BCR(i)); + } + + return 0; +} +#else +#define tc6393xb_suspend NULL +#define tc6393xb_resume NULL +#endif + +static struct platform_driver tc6393xb_driver = { + .probe = tc6393xb_probe, + .remove = tc6393xb_remove, + .suspend = tc6393xb_suspend, + .resume = tc6393xb_resume, + + .driver = { + .name = "tc6393xb", + }, +}; + +static int __init tc6393xb_init(void) +{ + return platform_driver_register(&tc6393xb_driver); +} + +static void __exit tc6393xb_exit(void) +{ + platform_driver_unregister(&tc6393xb_driver); +} + +subsys_initcall(tc6393xb_init); +module_exit(tc6393xb_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov and Dirk Opfer"); +MODULE_DESCRIPTION("tc6393xb Toshiba Mobile IO Controller"); +MODULE_ALIAS("platform:tc6393xb"); + diff --git a/kernel/drivers/mfd/ti_am335x_tscadc.c b/kernel/drivers/mfd/ti_am335x_tscadc.c new file mode 100644 index 000000000..e4e4b22ee --- /dev/null +++ b/kernel/drivers/mfd/ti_am335x_tscadc.c @@ -0,0 +1,380 @@ +/* + * TI Touch Screen / ADC MFD driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/mfd/core.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/sched.h> + +#include <linux/mfd/ti_am335x_tscadc.h> + +static unsigned int tscadc_readl(struct ti_tscadc_dev *tsadc, unsigned int reg) +{ + unsigned int val; + + regmap_read(tsadc->regmap_tscadc, reg, &val); + return val; +} + +static void tscadc_writel(struct ti_tscadc_dev *tsadc, unsigned int reg, + unsigned int val) +{ + regmap_write(tsadc->regmap_tscadc, reg, val); +} + +static const struct regmap_config tscadc_regmap_config = { + .name = "ti_tscadc", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +void am335x_tsc_se_set_cache(struct ti_tscadc_dev *tsadc, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&tsadc->reg_lock, flags); + tsadc->reg_se_cache |= val; + if (tsadc->adc_waiting) + wake_up(&tsadc->reg_se_wait); + else if (!tsadc->adc_in_use) + tscadc_writel(tsadc, REG_SE, tsadc->reg_se_cache); + + spin_unlock_irqrestore(&tsadc->reg_lock, flags); +} +EXPORT_SYMBOL_GPL(am335x_tsc_se_set_cache); + +static void am335x_tscadc_need_adc(struct ti_tscadc_dev *tsadc) +{ + DEFINE_WAIT(wait); + u32 reg; + + reg = tscadc_readl(tsadc, REG_ADCFSM); + if (reg & SEQ_STATUS) { + tsadc->adc_waiting = true; + prepare_to_wait(&tsadc->reg_se_wait, &wait, + TASK_UNINTERRUPTIBLE); + spin_unlock_irq(&tsadc->reg_lock); + + schedule(); + + spin_lock_irq(&tsadc->reg_lock); + finish_wait(&tsadc->reg_se_wait, &wait); + + /* + * Sequencer should either be idle or + * busy applying the charge step. + */ + reg = tscadc_readl(tsadc, REG_ADCFSM); + WARN_ON((reg & SEQ_STATUS) && !(reg & CHARGE_STEP)); + tsadc->adc_waiting = false; + } + tsadc->adc_in_use = true; +} + +void am335x_tsc_se_set_once(struct ti_tscadc_dev *tsadc, u32 val) +{ + spin_lock_irq(&tsadc->reg_lock); + am335x_tscadc_need_adc(tsadc); + + tscadc_writel(tsadc, REG_SE, val); + spin_unlock_irq(&tsadc->reg_lock); +} +EXPORT_SYMBOL_GPL(am335x_tsc_se_set_once); + +void am335x_tsc_se_adc_done(struct ti_tscadc_dev *tsadc) +{ + unsigned long flags; + + spin_lock_irqsave(&tsadc->reg_lock, flags); + tsadc->adc_in_use = false; + tscadc_writel(tsadc, REG_SE, tsadc->reg_se_cache); + spin_unlock_irqrestore(&tsadc->reg_lock, flags); +} +EXPORT_SYMBOL_GPL(am335x_tsc_se_adc_done); + +void am335x_tsc_se_clr(struct ti_tscadc_dev *tsadc, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&tsadc->reg_lock, flags); + tsadc->reg_se_cache &= ~val; + tscadc_writel(tsadc, REG_SE, tsadc->reg_se_cache); + spin_unlock_irqrestore(&tsadc->reg_lock, flags); +} +EXPORT_SYMBOL_GPL(am335x_tsc_se_clr); + +static void tscadc_idle_config(struct ti_tscadc_dev *config) +{ + unsigned int idleconfig; + + idleconfig = STEPCONFIG_YNN | STEPCONFIG_INM_ADCREFM | + STEPCONFIG_INP_ADCREFM | STEPCONFIG_YPN; + + tscadc_writel(config, REG_IDLECONFIG, idleconfig); +} + +static int ti_tscadc_probe(struct platform_device *pdev) +{ + struct ti_tscadc_dev *tscadc; + struct resource *res; + struct clk *clk; + struct device_node *node = pdev->dev.of_node; + struct mfd_cell *cell; + struct property *prop; + const __be32 *cur; + u32 val; + int err, ctrl; + int clock_rate; + int tsc_wires = 0, adc_channels = 0, total_channels; + int readouts = 0; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "Could not find valid DT data.\n"); + return -EINVAL; + } + + node = of_get_child_by_name(pdev->dev.of_node, "tsc"); + of_property_read_u32(node, "ti,wires", &tsc_wires); + of_property_read_u32(node, "ti,coordiante-readouts", &readouts); + + node = of_get_child_by_name(pdev->dev.of_node, "adc"); + of_property_for_each_u32(node, "ti,adc-channels", prop, cur, val) { + adc_channels++; + if (val > 7) { + dev_err(&pdev->dev, " PIN numbers are 0..7 (not %d)\n", + val); + return -EINVAL; + } + } + total_channels = tsc_wires + adc_channels; + if (total_channels > 8) { + dev_err(&pdev->dev, "Number of i/p channels more than 8\n"); + return -EINVAL; + } + if (total_channels == 0) { + dev_err(&pdev->dev, "Need atleast one channel.\n"); + return -EINVAL; + } + + if (readouts * 2 + 2 + adc_channels > 16) { + dev_err(&pdev->dev, "Too many step configurations requested\n"); + return -EINVAL; + } + + /* Allocate memory for device */ + tscadc = devm_kzalloc(&pdev->dev, + sizeof(struct ti_tscadc_dev), GFP_KERNEL); + if (!tscadc) { + dev_err(&pdev->dev, "failed to allocate memory.\n"); + return -ENOMEM; + } + tscadc->dev = &pdev->dev; + + err = platform_get_irq(pdev, 0); + if (err < 0) { + dev_err(&pdev->dev, "no irq ID is specified.\n"); + goto ret; + } else + tscadc->irq = err; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tscadc->tscadc_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tscadc->tscadc_base)) + return PTR_ERR(tscadc->tscadc_base); + + tscadc->regmap_tscadc = devm_regmap_init_mmio(&pdev->dev, + tscadc->tscadc_base, &tscadc_regmap_config); + if (IS_ERR(tscadc->regmap_tscadc)) { + dev_err(&pdev->dev, "regmap init failed\n"); + err = PTR_ERR(tscadc->regmap_tscadc); + goto ret; + } + + spin_lock_init(&tscadc->reg_lock); + init_waitqueue_head(&tscadc->reg_se_wait); + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + /* + * The TSC_ADC_Subsystem has 2 clock domains + * OCP_CLK and ADC_CLK. + * The ADC clock is expected to run at target of 3MHz, + * and expected to capture 12-bit data at a rate of 200 KSPS. + * The TSC_ADC_SS controller design assumes the OCP clock is + * at least 6x faster than the ADC clock. + */ + clk = clk_get(&pdev->dev, "adc_tsc_fck"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get TSC fck\n"); + err = PTR_ERR(clk); + goto err_disable_clk; + } + clock_rate = clk_get_rate(clk); + clk_put(clk); + tscadc->clk_div = clock_rate / ADC_CLK; + + /* TSCADC_CLKDIV needs to be configured to the value minus 1 */ + tscadc->clk_div--; + tscadc_writel(tscadc, REG_CLKDIV, tscadc->clk_div); + + /* Set the control register bits */ + ctrl = CNTRLREG_STEPCONFIGWRT | CNTRLREG_STEPID; + tscadc_writel(tscadc, REG_CTRL, ctrl); + + /* Set register bits for Idle Config Mode */ + if (tsc_wires > 0) { + tscadc->tsc_wires = tsc_wires; + if (tsc_wires == 5) + ctrl |= CNTRLREG_5WIRE | CNTRLREG_TSCENB; + else + ctrl |= CNTRLREG_4WIRE | CNTRLREG_TSCENB; + tscadc_idle_config(tscadc); + } + + /* Enable the TSC module enable bit */ + ctrl |= CNTRLREG_TSCSSENB; + tscadc_writel(tscadc, REG_CTRL, ctrl); + + tscadc->used_cells = 0; + tscadc->tsc_cell = -1; + tscadc->adc_cell = -1; + + /* TSC Cell */ + if (tsc_wires > 0) { + tscadc->tsc_cell = tscadc->used_cells; + cell = &tscadc->cells[tscadc->used_cells++]; + cell->name = "TI-am335x-tsc"; + cell->of_compatible = "ti,am3359-tsc"; + cell->platform_data = &tscadc; + cell->pdata_size = sizeof(tscadc); + } + + /* ADC Cell */ + if (adc_channels > 0) { + tscadc->adc_cell = tscadc->used_cells; + cell = &tscadc->cells[tscadc->used_cells++]; + cell->name = "TI-am335x-adc"; + cell->of_compatible = "ti,am3359-adc"; + cell->platform_data = &tscadc; + cell->pdata_size = sizeof(tscadc); + } + + err = mfd_add_devices(&pdev->dev, pdev->id, tscadc->cells, + tscadc->used_cells, NULL, 0, NULL); + if (err < 0) + goto err_disable_clk; + + device_init_wakeup(&pdev->dev, true); + platform_set_drvdata(pdev, tscadc); + return 0; + +err_disable_clk: + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); +ret: + return err; +} + +static int ti_tscadc_remove(struct platform_device *pdev) +{ + struct ti_tscadc_dev *tscadc = platform_get_drvdata(pdev); + + tscadc_writel(tscadc, REG_SE, 0x00); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + mfd_remove_devices(tscadc->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int tscadc_suspend(struct device *dev) +{ + struct ti_tscadc_dev *tscadc_dev = dev_get_drvdata(dev); + + tscadc_writel(tscadc_dev, REG_SE, 0x00); + pm_runtime_put_sync(dev); + + return 0; +} + +static int tscadc_resume(struct device *dev) +{ + struct ti_tscadc_dev *tscadc_dev = dev_get_drvdata(dev); + u32 ctrl; + + pm_runtime_get_sync(dev); + + /* context restore */ + ctrl = CNTRLREG_STEPCONFIGWRT | CNTRLREG_STEPID; + tscadc_writel(tscadc_dev, REG_CTRL, ctrl); + + if (tscadc_dev->tsc_cell != -1) { + if (tscadc_dev->tsc_wires == 5) + ctrl |= CNTRLREG_5WIRE | CNTRLREG_TSCENB; + else + ctrl |= CNTRLREG_4WIRE | CNTRLREG_TSCENB; + tscadc_idle_config(tscadc_dev); + } + ctrl |= CNTRLREG_TSCSSENB; + tscadc_writel(tscadc_dev, REG_CTRL, ctrl); + + tscadc_writel(tscadc_dev, REG_CLKDIV, tscadc_dev->clk_div); + + return 0; +} + +static const struct dev_pm_ops tscadc_pm_ops = { + .suspend = tscadc_suspend, + .resume = tscadc_resume, +}; +#define TSCADC_PM_OPS (&tscadc_pm_ops) +#else +#define TSCADC_PM_OPS NULL +#endif + +static const struct of_device_id ti_tscadc_dt_ids[] = { + { .compatible = "ti,am3359-tscadc", }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_tscadc_dt_ids); + +static struct platform_driver ti_tscadc_driver = { + .driver = { + .name = "ti_am3359-tscadc", + .pm = TSCADC_PM_OPS, + .of_match_table = ti_tscadc_dt_ids, + }, + .probe = ti_tscadc_probe, + .remove = ti_tscadc_remove, + +}; + +module_platform_driver(ti_tscadc_driver); + +MODULE_DESCRIPTION("TI touchscreen / ADC MFD controller driver"); +MODULE_AUTHOR("Rachna Patil <rachna@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/timberdale.c b/kernel/drivers/mfd/timberdale.c new file mode 100644 index 000000000..6ce36d697 --- /dev/null +++ b/kernel/drivers/mfd/timberdale.c @@ -0,0 +1,871 @@ +/* + * timberdale.c timberdale FPGA MFD driver + * Copyright (c) 2009 Intel 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Timberdale FPGA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/msi.h> +#include <linux/mfd/core.h> +#include <linux/slab.h> + +#include <linux/timb_gpio.h> + +#include <linux/i2c.h> +#include <linux/i2c-ocores.h> +#include <linux/i2c-xiic.h> +#include <linux/i2c/tsc2007.h> + +#include <linux/spi/spi.h> +#include <linux/spi/xilinx_spi.h> +#include <linux/spi/max7301.h> +#include <linux/spi/mc33880.h> + +#include <media/timb_radio.h> +#include <media/timb_video.h> + +#include <linux/timb_dma.h> + +#include <linux/ks8842.h> + +#include "timberdale.h" + +#define DRIVER_NAME "timberdale" + +struct timberdale_device { + resource_size_t ctl_mapbase; + unsigned char __iomem *ctl_membase; + struct { + u32 major; + u32 minor; + u32 config; + } fw; +}; + +/*--------------------------------------------------------------------------*/ + +static struct tsc2007_platform_data timberdale_tsc2007_platform_data = { + .model = 2003, + .x_plate_ohms = 100 +}; + +static struct i2c_board_info timberdale_i2c_board_info[] = { + { + I2C_BOARD_INFO("tsc2007", 0x48), + .platform_data = &timberdale_tsc2007_platform_data, + .irq = IRQ_TIMBERDALE_TSC_INT + }, +}; + +static struct xiic_i2c_platform_data +timberdale_xiic_platform_data = { + .devices = timberdale_i2c_board_info, + .num_devices = ARRAY_SIZE(timberdale_i2c_board_info) +}; + +static struct ocores_i2c_platform_data +timberdale_ocores_platform_data = { + .reg_shift = 2, + .clock_khz = 62500, + .devices = timberdale_i2c_board_info, + .num_devices = ARRAY_SIZE(timberdale_i2c_board_info) +}; + +static const struct resource timberdale_xiic_resources[] = { + { + .start = XIICOFFSET, + .end = XIICEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_I2C, + .end = IRQ_TIMBERDALE_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource timberdale_ocores_resources[] = { + { + .start = OCORESOFFSET, + .end = OCORESEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_I2C, + .end = IRQ_TIMBERDALE_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct max7301_platform_data timberdale_max7301_platform_data = { + .base = 200 +}; + +static const struct mc33880_platform_data timberdale_mc33880_platform_data = { + .base = 100 +}; + +static struct spi_board_info timberdale_spi_16bit_board_info[] = { + { + .modalias = "max7301", + .max_speed_hz = 26000, + .chip_select = 2, + .mode = SPI_MODE_0, + .platform_data = &timberdale_max7301_platform_data + }, +}; + +static struct spi_board_info timberdale_spi_8bit_board_info[] = { + { + .modalias = "mc33880", + .max_speed_hz = 4000, + .chip_select = 1, + .mode = SPI_MODE_1, + .platform_data = &timberdale_mc33880_platform_data + }, +}; + +static struct xspi_platform_data timberdale_xspi_platform_data = { + .num_chipselect = 3, + /* bits per word and devices will be filled in runtime depending + * on the HW config + */ +}; + +static const struct resource timberdale_spi_resources[] = { + { + .start = SPIOFFSET, + .end = SPIEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_SPI, + .end = IRQ_TIMBERDALE_SPI, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct ks8842_platform_data + timberdale_ks8842_platform_data = { + .rx_dma_channel = DMA_ETH_RX, + .tx_dma_channel = DMA_ETH_TX +}; + +static const struct resource timberdale_eth_resources[] = { + { + .start = ETHOFFSET, + .end = ETHEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_ETHSW_IF, + .end = IRQ_TIMBERDALE_ETHSW_IF, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct timbgpio_platform_data + timberdale_gpio_platform_data = { + .gpio_base = 0, + .nr_pins = GPIO_NR_PINS, + .irq_base = 200, +}; + +static const struct resource timberdale_gpio_resources[] = { + { + .start = GPIOOFFSET, + .end = GPIOEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_GPIO, + .end = IRQ_TIMBERDALE_GPIO, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource timberdale_mlogicore_resources[] = { + { + .start = MLCOREOFFSET, + .end = MLCOREEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_MLCORE, + .end = IRQ_TIMBERDALE_MLCORE, + .flags = IORESOURCE_IRQ, + }, + { + .start = IRQ_TIMBERDALE_MLCORE_BUF, + .end = IRQ_TIMBERDALE_MLCORE_BUF, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource timberdale_uart_resources[] = { + { + .start = UARTOFFSET, + .end = UARTEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_UART, + .end = IRQ_TIMBERDALE_UART, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct resource timberdale_uartlite_resources[] = { + { + .start = UARTLITEOFFSET, + .end = UARTLITEEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_UARTLITE, + .end = IRQ_TIMBERDALE_UARTLITE, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct i2c_board_info timberdale_adv7180_i2c_board_info = { + /* Requires jumper JP9 to be off */ + I2C_BOARD_INFO("adv7180", 0x42 >> 1), + .irq = IRQ_TIMBERDALE_ADV7180 +}; + +static struct timb_video_platform_data + timberdale_video_platform_data = { + .dma_channel = DMA_VIDEO_RX, + .i2c_adapter = 0, + .encoder = { + .info = &timberdale_adv7180_i2c_board_info + } +}; + +static const struct resource +timberdale_radio_resources[] = { + { + .start = RDSOFFSET, + .end = RDSEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_RDS, + .end = IRQ_TIMBERDALE_RDS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct i2c_board_info timberdale_tef6868_i2c_board_info = { + I2C_BOARD_INFO("tef6862", 0x60) +}; + +static struct i2c_board_info timberdale_saa7706_i2c_board_info = { + I2C_BOARD_INFO("saa7706h", 0x1C) +}; + +static struct timb_radio_platform_data + timberdale_radio_platform_data = { + .i2c_adapter = 0, + .tuner = &timberdale_tef6868_i2c_board_info, + .dsp = &timberdale_saa7706_i2c_board_info +}; + +static const struct resource timberdale_video_resources[] = { + { + .start = LOGIWOFFSET, + .end = LOGIWEND, + .flags = IORESOURCE_MEM, + }, + /* + note that the "frame buffer" is located in DMA area + starting at 0x1200000 + */ +}; + +static struct timb_dma_platform_data timb_dma_platform_data = { + .nr_channels = 10, + .channels = { + { + /* UART RX */ + .rx = true, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* UART TX */ + .rx = false, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* MLB RX */ + .rx = true, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* MLB TX */ + .rx = false, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* Video RX */ + .rx = true, + .bytes_per_line = 1440, + .descriptors = 2, + .descriptor_elements = 16 + }, + { + /* Video framedrop */ + }, + { + /* SDHCI RX */ + .rx = true, + }, + { + /* SDHCI TX */ + }, + { + /* ETH RX */ + .rx = true, + .descriptors = 2, + .descriptor_elements = 1 + }, + { + /* ETH TX */ + .rx = false, + .descriptors = 2, + .descriptor_elements = 1 + }, + } +}; + +static const struct resource timberdale_dma_resources[] = { + { + .start = DMAOFFSET, + .end = DMAEND, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_DMA, + .end = IRQ_TIMBERDALE_DMA, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell timberdale_cells_bar0_cfg0[] = { + { + .name = "timb-dma", + .num_resources = ARRAY_SIZE(timberdale_dma_resources), + .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), + }, + { + .name = "timb-uart", + .num_resources = ARRAY_SIZE(timberdale_uart_resources), + .resources = timberdale_uart_resources, + }, + { + .name = "xiic-i2c", + .num_resources = ARRAY_SIZE(timberdale_xiic_resources), + .resources = timberdale_xiic_resources, + .platform_data = &timberdale_xiic_platform_data, + .pdata_size = sizeof(timberdale_xiic_platform_data), + }, + { + .name = "timb-gpio", + .num_resources = ARRAY_SIZE(timberdale_gpio_resources), + .resources = timberdale_gpio_resources, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), + }, + { + .name = "timb-video", + .num_resources = ARRAY_SIZE(timberdale_video_resources), + .resources = timberdale_video_resources, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), + }, + { + .name = "timb-radio", + .num_resources = ARRAY_SIZE(timberdale_radio_resources), + .resources = timberdale_radio_resources, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), + }, + { + .name = "xilinx_spi", + .num_resources = ARRAY_SIZE(timberdale_spi_resources), + .resources = timberdale_spi_resources, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), + }, + { + .name = "ks8842", + .num_resources = ARRAY_SIZE(timberdale_eth_resources), + .resources = timberdale_eth_resources, + .platform_data = &timberdale_ks8842_platform_data, + .pdata_size = sizeof(timberdale_ks8842_platform_data), + }, +}; + +static const struct mfd_cell timberdale_cells_bar0_cfg1[] = { + { + .name = "timb-dma", + .num_resources = ARRAY_SIZE(timberdale_dma_resources), + .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), + }, + { + .name = "timb-uart", + .num_resources = ARRAY_SIZE(timberdale_uart_resources), + .resources = timberdale_uart_resources, + }, + { + .name = "uartlite", + .num_resources = ARRAY_SIZE(timberdale_uartlite_resources), + .resources = timberdale_uartlite_resources, + }, + { + .name = "xiic-i2c", + .num_resources = ARRAY_SIZE(timberdale_xiic_resources), + .resources = timberdale_xiic_resources, + .platform_data = &timberdale_xiic_platform_data, + .pdata_size = sizeof(timberdale_xiic_platform_data), + }, + { + .name = "timb-gpio", + .num_resources = ARRAY_SIZE(timberdale_gpio_resources), + .resources = timberdale_gpio_resources, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), + }, + { + .name = "timb-mlogicore", + .num_resources = ARRAY_SIZE(timberdale_mlogicore_resources), + .resources = timberdale_mlogicore_resources, + }, + { + .name = "timb-video", + .num_resources = ARRAY_SIZE(timberdale_video_resources), + .resources = timberdale_video_resources, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), + }, + { + .name = "timb-radio", + .num_resources = ARRAY_SIZE(timberdale_radio_resources), + .resources = timberdale_radio_resources, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), + }, + { + .name = "xilinx_spi", + .num_resources = ARRAY_SIZE(timberdale_spi_resources), + .resources = timberdale_spi_resources, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), + }, + { + .name = "ks8842", + .num_resources = ARRAY_SIZE(timberdale_eth_resources), + .resources = timberdale_eth_resources, + .platform_data = &timberdale_ks8842_platform_data, + .pdata_size = sizeof(timberdale_ks8842_platform_data), + }, +}; + +static const struct mfd_cell timberdale_cells_bar0_cfg2[] = { + { + .name = "timb-dma", + .num_resources = ARRAY_SIZE(timberdale_dma_resources), + .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), + }, + { + .name = "timb-uart", + .num_resources = ARRAY_SIZE(timberdale_uart_resources), + .resources = timberdale_uart_resources, + }, + { + .name = "xiic-i2c", + .num_resources = ARRAY_SIZE(timberdale_xiic_resources), + .resources = timberdale_xiic_resources, + .platform_data = &timberdale_xiic_platform_data, + .pdata_size = sizeof(timberdale_xiic_platform_data), + }, + { + .name = "timb-gpio", + .num_resources = ARRAY_SIZE(timberdale_gpio_resources), + .resources = timberdale_gpio_resources, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), + }, + { + .name = "timb-video", + .num_resources = ARRAY_SIZE(timberdale_video_resources), + .resources = timberdale_video_resources, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), + }, + { + .name = "timb-radio", + .num_resources = ARRAY_SIZE(timberdale_radio_resources), + .resources = timberdale_radio_resources, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), + }, + { + .name = "xilinx_spi", + .num_resources = ARRAY_SIZE(timberdale_spi_resources), + .resources = timberdale_spi_resources, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), + }, +}; + +static const struct mfd_cell timberdale_cells_bar0_cfg3[] = { + { + .name = "timb-dma", + .num_resources = ARRAY_SIZE(timberdale_dma_resources), + .resources = timberdale_dma_resources, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), + }, + { + .name = "timb-uart", + .num_resources = ARRAY_SIZE(timberdale_uart_resources), + .resources = timberdale_uart_resources, + }, + { + .name = "ocores-i2c", + .num_resources = ARRAY_SIZE(timberdale_ocores_resources), + .resources = timberdale_ocores_resources, + .platform_data = &timberdale_ocores_platform_data, + .pdata_size = sizeof(timberdale_ocores_platform_data), + }, + { + .name = "timb-gpio", + .num_resources = ARRAY_SIZE(timberdale_gpio_resources), + .resources = timberdale_gpio_resources, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), + }, + { + .name = "timb-video", + .num_resources = ARRAY_SIZE(timberdale_video_resources), + .resources = timberdale_video_resources, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), + }, + { + .name = "timb-radio", + .num_resources = ARRAY_SIZE(timberdale_radio_resources), + .resources = timberdale_radio_resources, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), + }, + { + .name = "xilinx_spi", + .num_resources = ARRAY_SIZE(timberdale_spi_resources), + .resources = timberdale_spi_resources, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), + }, + { + .name = "ks8842", + .num_resources = ARRAY_SIZE(timberdale_eth_resources), + .resources = timberdale_eth_resources, + .platform_data = &timberdale_ks8842_platform_data, + .pdata_size = sizeof(timberdale_ks8842_platform_data), + }, +}; + +static const struct resource timberdale_sdhc_resources[] = { + /* located in bar 1 and bar 2 */ + { + .start = SDHC0OFFSET, + .end = SDHC0END, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TIMBERDALE_SDHC, + .end = IRQ_TIMBERDALE_SDHC, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell timberdale_cells_bar1[] = { + { + .name = "sdhci", + .num_resources = ARRAY_SIZE(timberdale_sdhc_resources), + .resources = timberdale_sdhc_resources, + }, +}; + +static const struct mfd_cell timberdale_cells_bar2[] = { + { + .name = "sdhci", + .num_resources = ARRAY_SIZE(timberdale_sdhc_resources), + .resources = timberdale_sdhc_resources, + }, +}; + +static ssize_t show_fw_ver(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct timberdale_device *priv = pci_get_drvdata(pdev); + + return sprintf(buf, "%d.%d.%d\n", priv->fw.major, priv->fw.minor, + priv->fw.config); +} + +static DEVICE_ATTR(fw_ver, S_IRUGO, show_fw_ver, NULL); + +/*--------------------------------------------------------------------------*/ + +static int timb_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct timberdale_device *priv; + int err, i; + resource_size_t mapbase; + struct msix_entry *msix_entries = NULL; + u8 ip_setup; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + pci_set_drvdata(dev, priv); + + err = pci_enable_device(dev); + if (err) + goto err_enable; + + mapbase = pci_resource_start(dev, 0); + if (!mapbase) { + dev_err(&dev->dev, "No resource\n"); + goto err_start; + } + + /* create a resource for the PCI master register */ + priv->ctl_mapbase = mapbase + CHIPCTLOFFSET; + if (!request_mem_region(priv->ctl_mapbase, CHIPCTLSIZE, "timb-ctl")) { + dev_err(&dev->dev, "Failed to request ctl mem\n"); + goto err_start; + } + + priv->ctl_membase = ioremap(priv->ctl_mapbase, CHIPCTLSIZE); + if (!priv->ctl_membase) { + dev_err(&dev->dev, "ioremap failed for ctl mem\n"); + goto err_ioremap; + } + + /* read the HW config */ + priv->fw.major = ioread32(priv->ctl_membase + TIMB_REV_MAJOR); + priv->fw.minor = ioread32(priv->ctl_membase + TIMB_REV_MINOR); + priv->fw.config = ioread32(priv->ctl_membase + TIMB_HW_CONFIG); + + if (priv->fw.major > TIMB_SUPPORTED_MAJOR) { + dev_err(&dev->dev, "The driver supports an older " + "version of the FPGA, please update the driver to " + "support %d.%d\n", priv->fw.major, priv->fw.minor); + goto err_config; + } + if (priv->fw.major < TIMB_SUPPORTED_MAJOR || + priv->fw.minor < TIMB_REQUIRED_MINOR) { + dev_err(&dev->dev, "The FPGA image is too old (%d.%d), " + "please upgrade the FPGA to at least: %d.%d\n", + priv->fw.major, priv->fw.minor, + TIMB_SUPPORTED_MAJOR, TIMB_REQUIRED_MINOR); + goto err_config; + } + + msix_entries = kzalloc(TIMBERDALE_NR_IRQS * sizeof(*msix_entries), + GFP_KERNEL); + if (!msix_entries) + goto err_config; + + for (i = 0; i < TIMBERDALE_NR_IRQS; i++) + msix_entries[i].entry = i; + + err = pci_enable_msix_exact(dev, msix_entries, TIMBERDALE_NR_IRQS); + if (err) { + dev_err(&dev->dev, + "MSI-X init failed: %d, expected entries: %d\n", + err, TIMBERDALE_NR_IRQS); + goto err_msix; + } + + err = device_create_file(&dev->dev, &dev_attr_fw_ver); + if (err) + goto err_create_file; + + /* Reset all FPGA PLB peripherals */ + iowrite32(0x1, priv->ctl_membase + TIMB_SW_RST); + + /* update IRQ offsets in I2C board info */ + for (i = 0; i < ARRAY_SIZE(timberdale_i2c_board_info); i++) + timberdale_i2c_board_info[i].irq = + msix_entries[timberdale_i2c_board_info[i].irq].vector; + + /* Update the SPI configuration depending on the HW (8 or 16 bit) */ + if (priv->fw.config & TIMB_HW_CONFIG_SPI_8BIT) { + timberdale_xspi_platform_data.bits_per_word = 8; + timberdale_xspi_platform_data.devices = + timberdale_spi_8bit_board_info; + timberdale_xspi_platform_data.num_devices = + ARRAY_SIZE(timberdale_spi_8bit_board_info); + } else { + timberdale_xspi_platform_data.bits_per_word = 16; + timberdale_xspi_platform_data.devices = + timberdale_spi_16bit_board_info; + timberdale_xspi_platform_data.num_devices = + ARRAY_SIZE(timberdale_spi_16bit_board_info); + } + + ip_setup = priv->fw.config & TIMB_HW_VER_MASK; + switch (ip_setup) { + case TIMB_HW_VER0: + err = mfd_add_devices(&dev->dev, -1, + timberdale_cells_bar0_cfg0, + ARRAY_SIZE(timberdale_cells_bar0_cfg0), + &dev->resource[0], msix_entries[0].vector, NULL); + break; + case TIMB_HW_VER1: + err = mfd_add_devices(&dev->dev, -1, + timberdale_cells_bar0_cfg1, + ARRAY_SIZE(timberdale_cells_bar0_cfg1), + &dev->resource[0], msix_entries[0].vector, NULL); + break; + case TIMB_HW_VER2: + err = mfd_add_devices(&dev->dev, -1, + timberdale_cells_bar0_cfg2, + ARRAY_SIZE(timberdale_cells_bar0_cfg2), + &dev->resource[0], msix_entries[0].vector, NULL); + break; + case TIMB_HW_VER3: + err = mfd_add_devices(&dev->dev, -1, + timberdale_cells_bar0_cfg3, + ARRAY_SIZE(timberdale_cells_bar0_cfg3), + &dev->resource[0], msix_entries[0].vector, NULL); + break; + default: + dev_err(&dev->dev, "Uknown IP setup: %d.%d.%d\n", + priv->fw.major, priv->fw.minor, ip_setup); + err = -ENODEV; + goto err_mfd; + } + + if (err) { + dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err); + goto err_mfd; + } + + err = mfd_add_devices(&dev->dev, 0, + timberdale_cells_bar1, ARRAY_SIZE(timberdale_cells_bar1), + &dev->resource[1], msix_entries[0].vector, NULL); + if (err) { + dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err); + goto err_mfd2; + } + + /* only version 0 and 3 have the iNand routed to SDHCI */ + if (((priv->fw.config & TIMB_HW_VER_MASK) == TIMB_HW_VER0) || + ((priv->fw.config & TIMB_HW_VER_MASK) == TIMB_HW_VER3)) { + err = mfd_add_devices(&dev->dev, 1, timberdale_cells_bar2, + ARRAY_SIZE(timberdale_cells_bar2), + &dev->resource[2], msix_entries[0].vector, NULL); + if (err) { + dev_err(&dev->dev, "mfd_add_devices failed: %d\n", err); + goto err_mfd2; + } + } + + kfree(msix_entries); + + dev_info(&dev->dev, + "Found Timberdale Card. Rev: %d.%d, HW config: 0x%02x\n", + priv->fw.major, priv->fw.minor, priv->fw.config); + + return 0; + +err_mfd2: + mfd_remove_devices(&dev->dev); +err_mfd: + device_remove_file(&dev->dev, &dev_attr_fw_ver); +err_create_file: + pci_disable_msix(dev); +err_msix: + kfree(msix_entries); +err_config: + iounmap(priv->ctl_membase); +err_ioremap: + release_mem_region(priv->ctl_mapbase, CHIPCTLSIZE); +err_start: + pci_disable_device(dev); +err_enable: + kfree(priv); + return -ENODEV; +} + +static void timb_remove(struct pci_dev *dev) +{ + struct timberdale_device *priv = pci_get_drvdata(dev); + + mfd_remove_devices(&dev->dev); + + device_remove_file(&dev->dev, &dev_attr_fw_ver); + + iounmap(priv->ctl_membase); + release_mem_region(priv->ctl_mapbase, CHIPCTLSIZE); + + pci_disable_msix(dev); + pci_disable_device(dev); + kfree(priv); +} + +static const struct pci_device_id timberdale_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_TIMB, PCI_DEVICE_ID_TIMB) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, timberdale_pci_tbl); + +static struct pci_driver timberdale_pci_driver = { + .name = DRIVER_NAME, + .id_table = timberdale_pci_tbl, + .probe = timb_probe, + .remove = timb_remove, +}; + +module_pci_driver(timberdale_pci_driver); + +MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/timberdale.h b/kernel/drivers/mfd/timberdale.h new file mode 100644 index 000000000..4412acd82 --- /dev/null +++ b/kernel/drivers/mfd/timberdale.h @@ -0,0 +1,142 @@ +/* + * timberdale.h timberdale FPGA MFD driver defines + * Copyright (c) 2009 Intel 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Timberdale FPGA + */ + +#ifndef MFD_TIMBERDALE_H +#define MFD_TIMBERDALE_H + +#define DRV_VERSION "0.3" + +/* This driver only support versions >= 3.8 and < 4.0 */ +#define TIMB_SUPPORTED_MAJOR 3 + +/* This driver only support minor >= 8 */ +#define TIMB_REQUIRED_MINOR 8 + +/* Registers of the control area */ +#define TIMB_REV_MAJOR 0x00 +#define TIMB_REV_MINOR 0x04 +#define TIMB_HW_CONFIG 0x08 +#define TIMB_SW_RST 0x40 + +/* bits in the TIMB_HW_CONFIG register */ +#define TIMB_HW_CONFIG_SPI_8BIT 0x80 + +#define TIMB_HW_VER_MASK 0x0f +#define TIMB_HW_VER0 0x00 +#define TIMB_HW_VER1 0x01 +#define TIMB_HW_VER2 0x02 +#define TIMB_HW_VER3 0x03 + +#define OCORESOFFSET 0x0 +#define OCORESEND 0x1f + +#define SPIOFFSET 0x80 +#define SPIEND 0xff + +#define UARTLITEOFFSET 0x100 +#define UARTLITEEND 0x10f + +#define RDSOFFSET 0x180 +#define RDSEND 0x183 + +#define ETHOFFSET 0x300 +#define ETHEND 0x3ff + +#define GPIOOFFSET 0x400 +#define GPIOEND 0x7ff + +#define CHIPCTLOFFSET 0x800 +#define CHIPCTLEND 0x8ff +#define CHIPCTLSIZE (CHIPCTLEND - CHIPCTLOFFSET + 1) + +#define INTCOFFSET 0xc00 +#define INTCEND 0xfff +#define INTCSIZE (INTCEND - INTCOFFSET) + +#define MOSTOFFSET 0x1000 +#define MOSTEND 0x13ff + +#define UARTOFFSET 0x1400 +#define UARTEND 0x17ff + +#define XIICOFFSET 0x1800 +#define XIICEND 0x19ff + +#define I2SOFFSET 0x1C00 +#define I2SEND 0x1fff + +#define LOGIWOFFSET 0x30000 +#define LOGIWEND 0x37fff + +#define MLCOREOFFSET 0x40000 +#define MLCOREEND 0x43fff + +#define DMAOFFSET 0x01000000 +#define DMAEND 0x013fffff + +/* SDHC0 is placed in PCI bar 1 */ +#define SDHC0OFFSET 0x00 +#define SDHC0END 0xff + +/* SDHC1 is placed in PCI bar 2 */ +#define SDHC1OFFSET 0x00 +#define SDHC1END 0xff + +#define PCI_VENDOR_ID_TIMB 0x10ee +#define PCI_DEVICE_ID_TIMB 0xa123 + +#define IRQ_TIMBERDALE_INIC 0 +#define IRQ_TIMBERDALE_MLB 1 +#define IRQ_TIMBERDALE_GPIO 2 +#define IRQ_TIMBERDALE_I2C 3 +#define IRQ_TIMBERDALE_UART 4 +#define IRQ_TIMBERDALE_DMA 5 +#define IRQ_TIMBERDALE_I2S 6 +#define IRQ_TIMBERDALE_TSC_INT 7 +#define IRQ_TIMBERDALE_SDHC 8 +#define IRQ_TIMBERDALE_ADV7180 9 +#define IRQ_TIMBERDALE_ETHSW_IF 10 +#define IRQ_TIMBERDALE_SPI 11 +#define IRQ_TIMBERDALE_UARTLITE 12 +#define IRQ_TIMBERDALE_MLCORE 13 +#define IRQ_TIMBERDALE_MLCORE_BUF 14 +#define IRQ_TIMBERDALE_RDS 15 +#define TIMBERDALE_NR_IRQS 16 + +#define GPIO_PIN_ASCB 8 +#define GPIO_PIN_INIC_RST 14 +#define GPIO_PIN_BT_RST 15 +#define GPIO_NR_PINS 16 + +/* DMA Channels */ +#define DMA_UART_RX 0 +#define DMA_UART_TX 1 +#define DMA_MLB_RX 2 +#define DMA_MLB_TX 3 +#define DMA_VIDEO_RX 4 +#define DMA_VIDEO_DROP 5 +#define DMA_SDHCI_RX 6 +#define DMA_SDHCI_TX 7 +#define DMA_ETH_RX 8 +#define DMA_ETH_TX 9 + +#endif diff --git a/kernel/drivers/mfd/tmio_core.c b/kernel/drivers/mfd/tmio_core.c new file mode 100644 index 000000000..83af78c1b --- /dev/null +++ b/kernel/drivers/mfd/tmio_core.c @@ -0,0 +1,53 @@ +/* + * Copyright(c) 2009 Ian Molton <spyro@f2s.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. + */ + +#include <linux/export.h> +#include <linux/mfd/tmio.h> + +int tmio_core_mmc_enable(void __iomem *cnf, int shift, unsigned long base) +{ + /* Enable the MMC/SD Control registers */ + sd_config_write16(cnf, shift, CNF_CMD, SDCREN); + sd_config_write32(cnf, shift, CNF_CTL_BASE, base & 0xfffe); + + /* Disable SD power during suspend */ + sd_config_write8(cnf, shift, CNF_PWR_CTL_3, 0x01); + + /* The below is required but why? FIXME */ + sd_config_write8(cnf, shift, CNF_STOP_CLK_CTL, 0x1f); + + /* Power down SD bus */ + sd_config_write8(cnf, shift, CNF_PWR_CTL_2, 0x00); + + return 0; +} +EXPORT_SYMBOL(tmio_core_mmc_enable); + +int tmio_core_mmc_resume(void __iomem *cnf, int shift, unsigned long base) +{ + + /* Enable the MMC/SD Control registers */ + sd_config_write16(cnf, shift, CNF_CMD, SDCREN); + sd_config_write32(cnf, shift, CNF_CTL_BASE, base & 0xfffe); + + return 0; +} +EXPORT_SYMBOL(tmio_core_mmc_resume); + +void tmio_core_mmc_pwr(void __iomem *cnf, int shift, int state) +{ + sd_config_write8(cnf, shift, CNF_PWR_CTL_2, state ? 0x02 : 0x00); +} +EXPORT_SYMBOL(tmio_core_mmc_pwr); + +void tmio_core_mmc_clk_div(void __iomem *cnf, int shift, int state) +{ + sd_config_write8(cnf, shift, CNF_SD_CLK_MODE, state ? 1 : 0); +} +EXPORT_SYMBOL(tmio_core_mmc_clk_div); + diff --git a/kernel/drivers/mfd/tps6105x.c b/kernel/drivers/mfd/tps6105x.c new file mode 100644 index 000000000..5de95c265 --- /dev/null +++ b/kernel/drivers/mfd/tps6105x.c @@ -0,0 +1,238 @@ +/* + * Core driver for TPS61050/61052 boost converters, used for while LED + * driving, audio power amplification, white LED flash, and generic + * boost conversion. Additionally it provides a 1-bit GPIO pin (out or in) + * and a flash synchronization pin to synchronize flash events when used as + * flashgun. + * + * Copyright (C) 2011 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps6105x.h> + +int tps6105x_set(struct tps6105x *tps6105x, u8 reg, u8 value) +{ + int ret; + + ret = mutex_lock_interruptible(&tps6105x->lock); + if (ret) + return ret; + ret = i2c_smbus_write_byte_data(tps6105x->client, reg, value); + mutex_unlock(&tps6105x->lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(tps6105x_set); + +int tps6105x_get(struct tps6105x *tps6105x, u8 reg, u8 *buf) +{ + int ret; + + ret = mutex_lock_interruptible(&tps6105x->lock); + if (ret) + return ret; + ret = i2c_smbus_read_byte_data(tps6105x->client, reg); + mutex_unlock(&tps6105x->lock); + if (ret < 0) + return ret; + + *buf = ret; + return 0; +} +EXPORT_SYMBOL(tps6105x_get); + +/* + * Masks off the bits in the mask and sets the bits in the bitvalues + * parameter in one atomic operation + */ +int tps6105x_mask_and_set(struct tps6105x *tps6105x, u8 reg, + u8 bitmask, u8 bitvalues) +{ + int ret; + u8 regval; + + ret = mutex_lock_interruptible(&tps6105x->lock); + if (ret) + return ret; + ret = i2c_smbus_read_byte_data(tps6105x->client, reg); + if (ret < 0) + goto fail; + regval = ret; + regval = (~bitmask & regval) | (bitmask & bitvalues); + ret = i2c_smbus_write_byte_data(tps6105x->client, reg, regval); +fail: + mutex_unlock(&tps6105x->lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(tps6105x_mask_and_set); + +static int tps6105x_startup(struct tps6105x *tps6105x) +{ + int ret; + u8 regval; + + ret = tps6105x_get(tps6105x, TPS6105X_REG_0, ®val); + if (ret) + return ret; + switch (regval >> TPS6105X_REG0_MODE_SHIFT) { + case TPS6105X_REG0_MODE_SHUTDOWN: + dev_info(&tps6105x->client->dev, + "TPS6105x found in SHUTDOWN mode\n"); + break; + case TPS6105X_REG0_MODE_TORCH: + dev_info(&tps6105x->client->dev, + "TPS6105x found in TORCH mode\n"); + break; + case TPS6105X_REG0_MODE_TORCH_FLASH: + dev_info(&tps6105x->client->dev, + "TPS6105x found in FLASH mode\n"); + break; + case TPS6105X_REG0_MODE_VOLTAGE: + dev_info(&tps6105x->client->dev, + "TPS6105x found in VOLTAGE mode\n"); + break; + default: + break; + } + + return ret; +} + +/* + * MFD cells - we have one cell which is selected operation + * mode, and we always have a GPIO cell. + */ +static struct mfd_cell tps6105x_cells[] = { + { + /* name will be runtime assigned */ + .id = -1, + }, + { + .name = "tps6105x-gpio", + .id = -1, + }, +}; + +static int tps6105x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps6105x *tps6105x; + struct tps6105x_platform_data *pdata; + int ret; + int i; + + tps6105x = devm_kmalloc(&client->dev, sizeof(*tps6105x), GFP_KERNEL); + if (!tps6105x) + return -ENOMEM; + + i2c_set_clientdata(client, tps6105x); + tps6105x->client = client; + pdata = dev_get_platdata(&client->dev); + tps6105x->pdata = pdata; + mutex_init(&tps6105x->lock); + + ret = tps6105x_startup(tps6105x); + if (ret) { + dev_err(&client->dev, "chip initialization failed\n"); + return ret; + } + + /* Remove warning texts when you implement new cell drivers */ + switch (pdata->mode) { + case TPS6105X_MODE_SHUTDOWN: + dev_info(&client->dev, + "present, not used for anything, only GPIO\n"); + break; + case TPS6105X_MODE_TORCH: + tps6105x_cells[0].name = "tps6105x-leds"; + dev_warn(&client->dev, + "torch mode is unsupported\n"); + break; + case TPS6105X_MODE_TORCH_FLASH: + tps6105x_cells[0].name = "tps6105x-flash"; + dev_warn(&client->dev, + "flash mode is unsupported\n"); + break; + case TPS6105X_MODE_VOLTAGE: + tps6105x_cells[0].name ="tps6105x-regulator"; + break; + default: + break; + } + + /* Set up and register the platform devices. */ + for (i = 0; i < ARRAY_SIZE(tps6105x_cells); i++) { + /* One state holder for all drivers, this is simple */ + tps6105x_cells[i].platform_data = tps6105x; + tps6105x_cells[i].pdata_size = sizeof(*tps6105x); + } + + return mfd_add_devices(&client->dev, 0, tps6105x_cells, + ARRAY_SIZE(tps6105x_cells), NULL, 0, NULL); +} + +static int tps6105x_remove(struct i2c_client *client) +{ + struct tps6105x *tps6105x = i2c_get_clientdata(client); + + mfd_remove_devices(&client->dev); + + /* Put chip in shutdown mode */ + tps6105x_mask_and_set(tps6105x, TPS6105X_REG_0, + TPS6105X_REG0_MODE_MASK, + TPS6105X_MODE_SHUTDOWN << TPS6105X_REG0_MODE_SHIFT); + + return 0; +} + +static const struct i2c_device_id tps6105x_id[] = { + { "tps61050", 0 }, + { "tps61052", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps6105x_id); + +static struct i2c_driver tps6105x_driver = { + .driver = { + .name = "tps6105x", + }, + .probe = tps6105x_probe, + .remove = tps6105x_remove, + .id_table = tps6105x_id, +}; + +static int __init tps6105x_init(void) +{ + return i2c_add_driver(&tps6105x_driver); +} +subsys_initcall(tps6105x_init); + +static void __exit tps6105x_exit(void) +{ + i2c_del_driver(&tps6105x_driver); +} +module_exit(tps6105x_exit); + +MODULE_AUTHOR("Linus Walleij"); +MODULE_DESCRIPTION("TPS6105x White LED Boost Converter Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/tps65010.c b/kernel/drivers/mfd/tps65010.c new file mode 100644 index 000000000..448f0a182 --- /dev/null +++ b/kernel/drivers/mfd/tps65010.c @@ -0,0 +1,1095 @@ +/* + * tps65010 - driver for tps6501x power management chips + * + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004-2005 David Brownell + * + * 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <linux/i2c/tps65010.h> + +#include <linux/gpio.h> + + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_VERSION "2 May 2005" +#define DRIVER_NAME (tps65010_driver.driver.name) + +MODULE_DESCRIPTION("TPS6501x Power Management Driver"); +MODULE_LICENSE("GPL"); + +static struct i2c_driver tps65010_driver; + +/*-------------------------------------------------------------------------*/ + +/* This driver handles a family of multipurpose chips, which incorporate + * voltage regulators, lithium ion/polymer battery charging, GPIOs, LEDs, + * and other features often needed in portable devices like cell phones + * or digital cameras. + * + * The tps65011 and tps65013 have different voltage settings compared + * to tps65010 and tps65012. The tps65013 has a NO_CHG status/irq. + * All except tps65010 have "wait" mode, possibly defaulted so that + * battery-insert != device-on. + * + * We could distinguish between some models by checking VDCDC1.UVLO or + * other registers, unless they've been changed already after powerup + * as part of board setup by a bootloader. + */ +enum tps_model { + TPS65010, + TPS65011, + TPS65012, + TPS65013, +}; + +struct tps65010 { + struct i2c_client *client; + struct mutex lock; + struct delayed_work work; + struct dentry *file; + unsigned charging:1; + unsigned por:1; + unsigned model:8; + u16 vbus; + unsigned long flags; +#define FLAG_VBUS_CHANGED 0 +#define FLAG_IRQ_ENABLE 1 + + /* copies of last register state */ + u8 chgstatus, regstatus, chgconf; + u8 nmask1, nmask2; + + u8 outmask; + struct gpio_chip chip; + struct platform_device *leds; +}; + +#define POWER_POLL_DELAY msecs_to_jiffies(5000) + +/*-------------------------------------------------------------------------*/ + +#if defined(DEBUG) || defined(CONFIG_DEBUG_FS) + +static void dbg_chgstat(char *buf, size_t len, u8 chgstatus) +{ + snprintf(buf, len, "%02x%s%s%s%s%s%s%s%s\n", + chgstatus, + (chgstatus & TPS_CHG_USB) ? " USB" : "", + (chgstatus & TPS_CHG_AC) ? " AC" : "", + (chgstatus & TPS_CHG_THERM) ? " therm" : "", + (chgstatus & TPS_CHG_TERM) ? " done" : + ((chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + ? " (charging)" : ""), + (chgstatus & TPS_CHG_TAPER_TMO) ? " taper_tmo" : "", + (chgstatus & TPS_CHG_CHG_TMO) ? " charge_tmo" : "", + (chgstatus & TPS_CHG_PRECHG_TMO) ? " prechg_tmo" : "", + (chgstatus & TPS_CHG_TEMP_ERR) ? " temp_err" : ""); +} + +static void dbg_regstat(char *buf, size_t len, u8 regstatus) +{ + snprintf(buf, len, "%02x %s%s%s%s%s%s%s%s\n", + regstatus, + (regstatus & TPS_REG_ONOFF) ? "off" : "(on)", + (regstatus & TPS_REG_COVER) ? " uncover" : "", + (regstatus & TPS_REG_UVLO) ? " UVLO" : "", + (regstatus & TPS_REG_NO_CHG) ? " NO_CHG" : "", + (regstatus & TPS_REG_PG_LD02) ? " ld02_bad" : "", + (regstatus & TPS_REG_PG_LD01) ? " ld01_bad" : "", + (regstatus & TPS_REG_PG_MAIN) ? " main_bad" : "", + (regstatus & TPS_REG_PG_CORE) ? " core_bad" : ""); +} + +static void dbg_chgconf(int por, char *buf, size_t len, u8 chgconfig) +{ + const char *hibit; + + if (por) + hibit = (chgconfig & TPS_CHARGE_POR) + ? "POR=69ms" : "POR=1sec"; + else + hibit = (chgconfig & TPS65013_AUA) ? "AUA" : ""; + + snprintf(buf, len, "%02x %s%s%s AC=%d%% USB=%dmA %sCharge\n", + chgconfig, hibit, + (chgconfig & TPS_CHARGE_RESET) ? " reset" : "", + (chgconfig & TPS_CHARGE_FAST) ? " fast" : "", + ({int p; switch ((chgconfig >> 3) & 3) { + case 3: p = 100; break; + case 2: p = 75; break; + case 1: p = 50; break; + default: p = 25; break; + }; p; }), + (chgconfig & TPS_VBUS_CHARGING) + ? ((chgconfig & TPS_VBUS_500MA) ? 500 : 100) + : 0, + (chgconfig & TPS_CHARGE_ENABLE) ? "" : "No"); +} + +#endif + +#ifdef DEBUG + +static void show_chgstatus(const char *label, u8 chgstatus) +{ + char buf [100]; + + dbg_chgstat(buf, sizeof buf, chgstatus); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +static void show_regstatus(const char *label, u8 regstatus) +{ + char buf [100]; + + dbg_regstat(buf, sizeof buf, regstatus); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +static void show_chgconfig(int por, const char *label, u8 chgconfig) +{ + char buf [100]; + + dbg_chgconf(por, buf, sizeof buf, chgconfig); + pr_debug("%s: %s %s", DRIVER_NAME, label, buf); +} + +#else + +static inline void show_chgstatus(const char *label, u8 chgstatus) { } +static inline void show_regstatus(const char *label, u8 chgstatus) { } +static inline void show_chgconfig(int por, const char *label, u8 chgconfig) { } + +#endif + +#ifdef CONFIG_DEBUG_FS + +static int dbg_show(struct seq_file *s, void *_) +{ + struct tps65010 *tps = s->private; + u8 value, v2; + unsigned i; + char buf[100]; + const char *chip; + + switch (tps->model) { + case TPS65010: chip = "tps65010"; break; + case TPS65011: chip = "tps65011"; break; + case TPS65012: chip = "tps65012"; break; + case TPS65013: chip = "tps65013"; break; + default: chip = NULL; break; + } + seq_printf(s, "driver %s\nversion %s\nchip %s\n\n", + DRIVER_NAME, DRIVER_VERSION, chip); + + mutex_lock(&tps->lock); + + /* FIXME how can we tell whether a battery is present? + * likely involves a charge gauging chip (like BQ26501). + */ + + seq_printf(s, "%scharging\n\n", tps->charging ? "" : "(not) "); + + + /* registers for monitoring battery charging and status; note + * that reading chgstat and regstat may ack IRQs... + */ + value = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG); + dbg_chgconf(tps->por, buf, sizeof buf, value); + seq_printf(s, "chgconfig %s", buf); + + value = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS); + dbg_chgstat(buf, sizeof buf, value); + seq_printf(s, "chgstat %s", buf); + value = i2c_smbus_read_byte_data(tps->client, TPS_MASK1); + dbg_chgstat(buf, sizeof buf, value); + seq_printf(s, "mask1 %s", buf); + /* ignore ackint1 */ + + value = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS); + dbg_regstat(buf, sizeof buf, value); + seq_printf(s, "regstat %s", buf); + value = i2c_smbus_read_byte_data(tps->client, TPS_MASK2); + dbg_regstat(buf, sizeof buf, value); + seq_printf(s, "mask2 %s\n", buf); + /* ignore ackint2 */ + + queue_delayed_work(system_power_efficient_wq, &tps->work, + POWER_POLL_DELAY); + + /* VMAIN voltage, enable lowpower, etc */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC1); + seq_printf(s, "vdcdc1 %02x\n", value); + + /* VCORE voltage, vibrator on/off */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VDCDC2); + seq_printf(s, "vdcdc2 %02x\n", value); + + /* both LD0s, and their lowpower behavior */ + value = i2c_smbus_read_byte_data(tps->client, TPS_VREGS1); + seq_printf(s, "vregs1 %02x\n\n", value); + + + /* LEDs and GPIOs */ + value = i2c_smbus_read_byte_data(tps->client, TPS_LED1_ON); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED1_PER); + seq_printf(s, "led1 %s, on=%02x, per=%02x, %d/%d msec\n", + (value & 0x80) + ? ((v2 & 0x80) ? "on" : "off") + : ((v2 & 0x80) ? "blink" : "(nPG)"), + value, v2, + (value & 0x7f) * 10, (v2 & 0x7f) * 100); + + value = i2c_smbus_read_byte_data(tps->client, TPS_LED2_ON); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_LED2_PER); + seq_printf(s, "led2 %s, on=%02x, per=%02x, %d/%d msec\n", + (value & 0x80) + ? ((v2 & 0x80) ? "on" : "off") + : ((v2 & 0x80) ? "blink" : "off"), + value, v2, + (value & 0x7f) * 10, (v2 & 0x7f) * 100); + + value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO); + v2 = i2c_smbus_read_byte_data(tps->client, TPS_MASK3); + seq_printf(s, "defgpio %02x mask3 %02x\n", value, v2); + + for (i = 0; i < 4; i++) { + if (value & (1 << (4 + i))) + seq_printf(s, " gpio%d-out %s\n", i + 1, + (value & (1 << i)) ? "low" : "hi "); + else + seq_printf(s, " gpio%d-in %s %s %s\n", i + 1, + (value & (1 << i)) ? "hi " : "low", + (v2 & (1 << i)) ? "no-irq" : "irq", + (v2 & (1 << (4 + i))) ? "rising" : "falling"); + } + + mutex_unlock(&tps->lock); + return 0; +} + +static int dbg_tps_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = dbg_tps_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define DEBUG_FOPS &debug_fops + +#else +#define DEBUG_FOPS NULL +#endif + +/*-------------------------------------------------------------------------*/ + +/* handle IRQS in a task context, so we can use I2C calls */ +static void tps65010_interrupt(struct tps65010 *tps) +{ + u8 tmp = 0, mask, poll; + + /* IRQs won't trigger for certain events, but we can get + * others by polling (normally, with external power applied). + */ + poll = 0; + + /* regstatus irqs */ + if (tps->nmask2) { + tmp = i2c_smbus_read_byte_data(tps->client, TPS_REGSTATUS); + mask = tmp ^ tps->regstatus; + tps->regstatus = tmp; + mask &= tps->nmask2; + } else + mask = 0; + if (mask) { + tps->regstatus = tmp; + /* may need to shut something down ... */ + + /* "off" usually means deep sleep */ + if (tmp & TPS_REG_ONOFF) { + pr_info("%s: power off button\n", DRIVER_NAME); +#if 0 + /* REVISIT: this might need its own workqueue + * plus tweaks including deadlock avoidance ... + * also needs to get error handling and probably + * an #ifdef CONFIG_HIBERNATION + */ + hibernate(); +#endif + poll = 1; + } + } + + /* chgstatus irqs */ + if (tps->nmask1) { + tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGSTATUS); + mask = tmp ^ tps->chgstatus; + tps->chgstatus = tmp; + mask &= tps->nmask1; + } else + mask = 0; + if (mask) { + unsigned charging = 0; + + show_chgstatus("chg/irq", tmp); + if (tmp & (TPS_CHG_USB|TPS_CHG_AC)) + show_chgconfig(tps->por, "conf", tps->chgconf); + + /* Unless it was turned off or disabled, we charge any + * battery whenever there's power available for it + * and the charger hasn't been disabled. + */ + if (!(tps->chgstatus & ~(TPS_CHG_USB|TPS_CHG_AC)) + && (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + && (tps->chgconf & TPS_CHARGE_ENABLE) + ) { + if (tps->chgstatus & TPS_CHG_USB) { + /* VBUS options are readonly until reconnect */ + if (mask & TPS_CHG_USB) + set_bit(FLAG_VBUS_CHANGED, &tps->flags); + charging = 1; + } else if (tps->chgstatus & TPS_CHG_AC) + charging = 1; + } + if (charging != tps->charging) { + tps->charging = charging; + pr_info("%s: battery %scharging\n", + DRIVER_NAME, charging ? "" : + ((tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC)) + ? "NOT " : "dis")); + } + } + + /* always poll to detect (a) power removal, without tps65013 + * NO_CHG IRQ; or (b) restart of charging after stop. + */ + if ((tps->model != TPS65013 || !tps->charging) + && (tps->chgstatus & (TPS_CHG_USB|TPS_CHG_AC))) + poll = 1; + if (poll) + queue_delayed_work(system_power_efficient_wq, &tps->work, + POWER_POLL_DELAY); + + /* also potentially gpio-in rise or fall */ +} + +/* handle IRQs and polling using keventd for now */ +static void tps65010_work(struct work_struct *work) +{ + struct tps65010 *tps; + + tps = container_of(to_delayed_work(work), struct tps65010, work); + mutex_lock(&tps->lock); + + tps65010_interrupt(tps); + + if (test_and_clear_bit(FLAG_VBUS_CHANGED, &tps->flags)) { + int status; + u8 chgconfig, tmp; + + chgconfig = i2c_smbus_read_byte_data(tps->client, + TPS_CHGCONFIG); + chgconfig &= ~(TPS_VBUS_500MA | TPS_VBUS_CHARGING); + if (tps->vbus == 500) + chgconfig |= TPS_VBUS_500MA | TPS_VBUS_CHARGING; + else if (tps->vbus >= 100) + chgconfig |= TPS_VBUS_CHARGING; + + status = i2c_smbus_write_byte_data(tps->client, + TPS_CHGCONFIG, chgconfig); + + /* vbus update fails unless VBUS is connected! */ + tmp = i2c_smbus_read_byte_data(tps->client, TPS_CHGCONFIG); + tps->chgconf = tmp; + show_chgconfig(tps->por, "update vbus", tmp); + } + + if (test_and_clear_bit(FLAG_IRQ_ENABLE, &tps->flags)) + enable_irq(tps->client->irq); + + mutex_unlock(&tps->lock); +} + +static irqreturn_t tps65010_irq(int irq, void *_tps) +{ + struct tps65010 *tps = _tps; + + disable_irq_nosync(irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + queue_delayed_work(system_power_efficient_wq, &tps->work, 0); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* offsets 0..3 == GPIO1..GPIO4 + * offsets 4..5 == LED1/nPG, LED2 (we set one of the non-BLINK modes) + * offset 6 == vibrator motor driver + */ +static void +tps65010_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < 4) + tps65010_set_gpio_out_value(offset + 1, value); + else if (offset < 6) + tps65010_set_led(offset - 3, value ? ON : OFF); + else + tps65010_set_vib(value); +} + +static int +tps65010_output(struct gpio_chip *chip, unsigned offset, int value) +{ + /* GPIOs may be input-only */ + if (offset < 4) { + struct tps65010 *tps; + + tps = container_of(chip, struct tps65010, chip); + if (!(tps->outmask & (1 << offset))) + return -EINVAL; + tps65010_set_gpio_out_value(offset + 1, value); + } else if (offset < 6) + tps65010_set_led(offset - 3, value ? ON : OFF); + else + tps65010_set_vib(value); + + return 0; +} + +static int tps65010_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + int value; + struct tps65010 *tps; + + tps = container_of(chip, struct tps65010, chip); + + if (offset < 4) { + value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO); + if (value < 0) + return 0; + if (value & (1 << (offset + 4))) /* output */ + return !(value & (1 << offset)); + else /* input */ + return (value & (1 << offset)); + } + + /* REVISIT we *could* report LED1/nPG and LED2 state ... */ + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static struct tps65010 *the_tps; + +static int tps65010_remove(struct i2c_client *client) +{ + struct tps65010 *tps = i2c_get_clientdata(client); + struct tps65010_board *board = dev_get_platdata(&client->dev); + + if (board && board->teardown) { + int status = board->teardown(client, board->context); + if (status < 0) + dev_dbg(&client->dev, "board %s %s err %d\n", + "teardown", client->name, status); + } + if (client->irq > 0) + free_irq(client->irq, tps); + cancel_delayed_work_sync(&tps->work); + debugfs_remove(tps->file); + the_tps = NULL; + return 0; +} + +static int tps65010_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps65010 *tps; + int status; + struct tps65010_board *board = dev_get_platdata(&client->dev); + + if (the_tps) { + dev_dbg(&client->dev, "only one tps6501x chip allowed\n"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->lock); + INIT_DELAYED_WORK(&tps->work, tps65010_work); + tps->client = client; + tps->model = id->driver_data; + + /* the IRQ is active low, but many gpio lines can't support that + * so this driver uses falling-edge triggers instead. + */ + if (client->irq > 0) { + status = request_irq(client->irq, tps65010_irq, + IRQF_TRIGGER_FALLING, DRIVER_NAME, tps); + if (status < 0) { + dev_dbg(&client->dev, "can't get IRQ %d, err %d\n", + client->irq, status); + return status; + } + /* annoying race here, ideally we'd have an option + * to claim the irq now and enable it later. + * FIXME genirq IRQF_NOAUTOEN now solves that ... + */ + disable_irq(client->irq); + set_bit(FLAG_IRQ_ENABLE, &tps->flags); + } else + dev_warn(&client->dev, "IRQ not configured!\n"); + + + switch (tps->model) { + case TPS65010: + case TPS65012: + tps->por = 1; + break; + /* else CHGCONFIG.POR is replaced by AUA, enabling a WAIT mode */ + } + tps->chgconf = i2c_smbus_read_byte_data(client, TPS_CHGCONFIG); + show_chgconfig(tps->por, "conf/init", tps->chgconf); + + show_chgstatus("chg/init", + i2c_smbus_read_byte_data(client, TPS_CHGSTATUS)); + show_regstatus("reg/init", + i2c_smbus_read_byte_data(client, TPS_REGSTATUS)); + + pr_debug("%s: vdcdc1 0x%02x, vdcdc2 %02x, vregs1 %02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(client, TPS_VDCDC1), + i2c_smbus_read_byte_data(client, TPS_VDCDC2), + i2c_smbus_read_byte_data(client, TPS_VREGS1)); + pr_debug("%s: defgpio 0x%02x, mask3 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(client, TPS_DEFGPIO), + i2c_smbus_read_byte_data(client, TPS_MASK3)); + + i2c_set_clientdata(client, tps); + the_tps = tps; + +#if defined(CONFIG_USB_GADGET) && !defined(CONFIG_USB_OTG) + /* USB hosts can't draw VBUS. OTG devices could, later + * when OTG infrastructure enables it. USB peripherals + * could be relying on VBUS while booting, though. + */ + tps->vbus = 100; +#endif + + /* unmask the "interesting" irqs, then poll once to + * kickstart monitoring, initialize shadowed status + * registers, and maybe disable VBUS draw. + */ + tps->nmask1 = ~0; + (void) i2c_smbus_write_byte_data(client, TPS_MASK1, ~tps->nmask1); + + tps->nmask2 = TPS_REG_ONOFF; + if (tps->model == TPS65013) + tps->nmask2 |= TPS_REG_NO_CHG; + (void) i2c_smbus_write_byte_data(client, TPS_MASK2, ~tps->nmask2); + + (void) i2c_smbus_write_byte_data(client, TPS_MASK3, 0x0f + | i2c_smbus_read_byte_data(client, TPS_MASK3)); + + tps65010_work(&tps->work.work); + + tps->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL, + tps, DEBUG_FOPS); + + /* optionally register GPIOs */ + if (board && board->base != 0) { + tps->outmask = board->outmask; + + tps->chip.label = client->name; + tps->chip.dev = &client->dev; + tps->chip.owner = THIS_MODULE; + + tps->chip.set = tps65010_gpio_set; + tps->chip.direction_output = tps65010_output; + + /* NOTE: only partial support for inputs; nyet IRQs */ + tps->chip.get = tps65010_gpio_get; + + tps->chip.base = board->base; + tps->chip.ngpio = 7; + tps->chip.can_sleep = 1; + + status = gpiochip_add(&tps->chip); + if (status < 0) + dev_err(&client->dev, "can't add gpiochip, err %d\n", + status); + else if (board->setup) { + status = board->setup(client, board->context); + if (status < 0) { + dev_dbg(&client->dev, + "board %s %s err %d\n", + "setup", client->name, status); + status = 0; + } + } + } + + return 0; +} + +static const struct i2c_device_id tps65010_id[] = { + { "tps65010", TPS65010 }, + { "tps65011", TPS65011 }, + { "tps65012", TPS65012 }, + { "tps65013", TPS65013 }, + { "tps65014", TPS65011 }, /* tps65011 charging at 6.5V max */ + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65010_id); + +static struct i2c_driver tps65010_driver = { + .driver = { + .name = "tps65010", + }, + .probe = tps65010_probe, + .remove = tps65010_remove, + .id_table = tps65010_id, +}; + +/*-------------------------------------------------------------------------*/ + +/* Draw from VBUS: + * 0 mA -- DON'T DRAW (might supply power instead) + * 100 mA -- usb unit load (slowest charge rate) + * 500 mA -- usb high power (fast battery charge) + */ +int tps65010_set_vbus_draw(unsigned mA) +{ + unsigned long flags; + + if (!the_tps) + return -ENODEV; + + /* assumes non-SMP */ + local_irq_save(flags); + if (mA >= 500) + mA = 500; + else if (mA >= 100) + mA = 100; + else + mA = 0; + the_tps->vbus = mA; + if ((the_tps->chgstatus & TPS_CHG_USB) + && test_and_set_bit( + FLAG_VBUS_CHANGED, &the_tps->flags)) { + /* gadget drivers call this in_irq() */ + queue_delayed_work(system_power_efficient_wq, &the_tps->work, + 0); + } + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL(tps65010_set_vbus_draw); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_gpio_out_value parameter: + * gpio: GPIO1, GPIO2, GPIO3 or GPIO4 + * value: LOW or HIGH + */ +int tps65010_set_gpio_out_value(unsigned gpio, unsigned value) +{ + int status; + unsigned defgpio; + + if (!the_tps) + return -ENODEV; + if ((gpio < GPIO1) || (gpio > GPIO4)) + return -EINVAL; + + mutex_lock(&the_tps->lock); + + defgpio = i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO); + + /* Configure GPIO for output */ + defgpio |= 1 << (gpio + 3); + + /* Writing 1 forces a logic 0 on that GPIO and vice versa */ + switch (value) { + case LOW: + defgpio |= 1 << (gpio - 1); /* set GPIO low by writing 1 */ + break; + /* case HIGH: */ + default: + defgpio &= ~(1 << (gpio - 1)); /* set GPIO high by writing 0 */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_DEFGPIO, defgpio); + + pr_debug("%s: gpio%dout = %s, defgpio 0x%02x\n", DRIVER_NAME, + gpio, value ? "high" : "low", + i2c_smbus_read_byte_data(the_tps->client, TPS_DEFGPIO)); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_set_gpio_out_value); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_led parameter: + * led: LED1 or LED2 + * mode: ON, OFF or BLINK + */ +int tps65010_set_led(unsigned led, unsigned mode) +{ + int status; + unsigned led_on, led_per, offs; + + if (!the_tps) + return -ENODEV; + + if (led == LED1) + offs = 0; + else { + offs = 2; + led = LED2; + } + + mutex_lock(&the_tps->lock); + + pr_debug("%s: led%i_on 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_ON + offs)); + + pr_debug("%s: led%i_per 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_PER + offs)); + + switch (mode) { + case OFF: + led_on = 1 << 7; + led_per = 0 << 7; + break; + case ON: + led_on = 1 << 7; + led_per = 1 << 7; + break; + case BLINK: + led_on = 0x30 | (0 << 7); + led_per = 0x08 | (1 << 7); + break; + default: + printk(KERN_ERR "%s: Wrong mode parameter for set_led()\n", + DRIVER_NAME); + mutex_unlock(&the_tps->lock); + return -EINVAL; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_LED1_ON + offs, led_on); + + if (status != 0) { + printk(KERN_ERR "%s: Failed to write led%i_on register\n", + DRIVER_NAME, led); + mutex_unlock(&the_tps->lock); + return status; + } + + pr_debug("%s: led%i_on 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, TPS_LED1_ON + offs)); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_LED1_PER + offs, led_per); + + if (status != 0) { + printk(KERN_ERR "%s: Failed to write led%i_per register\n", + DRIVER_NAME, led); + mutex_unlock(&the_tps->lock); + return status; + } + + pr_debug("%s: led%i_per 0x%02x\n", DRIVER_NAME, led, + i2c_smbus_read_byte_data(the_tps->client, + TPS_LED1_PER + offs)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_set_led); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_vib parameter: + * value: ON or OFF + */ +int tps65010_set_vib(unsigned value) +{ + int status; + unsigned vdcdc2; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + vdcdc2 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC2); + vdcdc2 &= ~(1 << 1); + if (value) + vdcdc2 |= (1 << 1); + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC2, vdcdc2); + + pr_debug("%s: vibrator %s\n", DRIVER_NAME, value ? "on" : "off"); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_set_vib); + +/*-------------------------------------------------------------------------*/ +/* tps65010_set_low_pwr parameter: + * mode: ON or OFF + */ +int tps65010_set_low_pwr(unsigned mode) +{ + int status; + unsigned vdcdc1; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: %s low_pwr, vdcdc1 0x%02x\n", DRIVER_NAME, + mode ? "enable" : "disable", + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1); + + switch (mode) { + case OFF: + vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */ + break; + /* case ON: */ + default: + vdcdc1 |= TPS_ENABLE_LP; /* enable ENABLE_LP bit */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC1, vdcdc1); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_set_low_pwr); + +/*-------------------------------------------------------------------------*/ +/* tps65010_config_vregs1 parameter: + * value to be written to VREGS1 register + * Note: The complete register is written, set all bits you need + */ +int tps65010_config_vregs1(unsigned value) +{ + int status; + + if (!the_tps) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1)); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VREGS1, value); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vregs1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VREGS1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65010_config_vregs1); + +int tps65010_config_vdcdc2(unsigned value) +{ + struct i2c_client *c; + int status; + + if (!the_tps) + return -ENODEV; + + c = the_tps->client; + mutex_lock(&the_tps->lock); + + pr_debug("%s: vdcdc2 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(c, TPS_VDCDC2)); + + status = i2c_smbus_write_byte_data(c, TPS_VDCDC2, value); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc2 register\n", + DRIVER_NAME); + else + pr_debug("%s: vregs1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(c, TPS_VDCDC2)); + + mutex_unlock(&the_tps->lock); + return status; +} +EXPORT_SYMBOL(tps65010_config_vdcdc2); + +/*-------------------------------------------------------------------------*/ +/* tps65013_set_low_pwr parameter: + * mode: ON or OFF + */ + +/* FIXME: Assumes AC or USB power is present. Setting AUA bit is not + required if power supply is through a battery */ + +int tps65013_set_low_pwr(unsigned mode) +{ + int status; + unsigned vdcdc1, chgconfig; + + if (!the_tps || the_tps->por) + return -ENODEV; + + mutex_lock(&the_tps->lock); + + pr_debug("%s: %s low_pwr, chgconfig 0x%02x vdcdc1 0x%02x\n", + DRIVER_NAME, + mode ? "enable" : "disable", + i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG), + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG); + vdcdc1 = i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1); + + switch (mode) { + case OFF: + chgconfig &= ~TPS65013_AUA; /* disable AUA bit */ + vdcdc1 &= ~TPS_ENABLE_LP; /* disable ENABLE_LP bit */ + break; + /* case ON: */ + default: + chgconfig |= TPS65013_AUA; /* enable AUA bit */ + vdcdc1 |= TPS_ENABLE_LP; /* enable ENABLE_LP bit */ + break; + } + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_CHGCONFIG, chgconfig); + if (status != 0) { + printk(KERN_ERR "%s: Failed to write chconfig register\n", + DRIVER_NAME); + mutex_unlock(&the_tps->lock); + return status; + } + + chgconfig = i2c_smbus_read_byte_data(the_tps->client, TPS_CHGCONFIG); + the_tps->chgconf = chgconfig; + show_chgconfig(0, "chgconf", chgconfig); + + status = i2c_smbus_write_byte_data(the_tps->client, + TPS_VDCDC1, vdcdc1); + + if (status != 0) + printk(KERN_ERR "%s: Failed to write vdcdc1 register\n", + DRIVER_NAME); + else + pr_debug("%s: vdcdc1 0x%02x\n", DRIVER_NAME, + i2c_smbus_read_byte_data(the_tps->client, TPS_VDCDC1)); + + mutex_unlock(&the_tps->lock); + + return status; +} +EXPORT_SYMBOL(tps65013_set_low_pwr); + +/*-------------------------------------------------------------------------*/ + +static int __init tps_init(void) +{ + u32 tries = 3; + int status = -ENODEV; + + printk(KERN_INFO "%s: version %s\n", DRIVER_NAME, DRIVER_VERSION); + + /* some boards have startup glitches */ + while (tries--) { + status = i2c_add_driver(&tps65010_driver); + if (the_tps) + break; + i2c_del_driver(&tps65010_driver); + if (!tries) { + printk(KERN_ERR "%s: no chip?\n", DRIVER_NAME); + return -ENODEV; + } + pr_debug("%s: re-probe ...\n", DRIVER_NAME); + msleep(10); + } + + return status; +} +/* NOTE: this MUST be initialized before the other parts of the system + * that rely on it ... but after the i2c bus on which this relies. + * That is, much earlier than on PC-type systems, which don't often use + * I2C as a core system bus. + */ +subsys_initcall(tps_init); + +static void __exit tps_exit(void) +{ + i2c_del_driver(&tps65010_driver); +} +module_exit(tps_exit); + diff --git a/kernel/drivers/mfd/tps6507x.c b/kernel/drivers/mfd/tps6507x.c new file mode 100644 index 000000000..a2e1990c9 --- /dev/null +++ b/kernel/drivers/mfd/tps6507x.c @@ -0,0 +1,154 @@ +/* + * tps6507x.c -- TPS6507x chip family multi-function driver + * + * Copyright (c) 2010 RidgeRun (todd.fischer@ridgerun.com) + * + * Author: Todd Fischer + * todd.fischer@ridgerun.com + * + * Credits: + * + * Using code from wm831x-*.c, wm8400-core, Wolfson Microelectronics PLC. + * + * For licencing details see kernel-base/COPYING + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps6507x.h> + +static const struct mfd_cell tps6507x_devs[] = { + { + .name = "tps6507x-pmic", + }, + { + .name = "tps6507x-ts", + }, +}; + + +static int tps6507x_i2c_read_device(struct tps6507x_dev *tps6507x, char reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = tps6507x->i2c_client; + 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 tps6507x_i2c_write_device(struct tps6507x_dev *tps6507x, char reg, + int bytes, void *src) +{ + struct i2c_client *i2c = tps6507x->i2c_client; + /* we add 1 byte for device register */ + u8 msg[TPS6507X_MAX_REGISTER + 1]; + int ret; + + if (bytes > TPS6507X_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 tps6507x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tps6507x_dev *tps6507x; + + tps6507x = devm_kzalloc(&i2c->dev, sizeof(struct tps6507x_dev), + GFP_KERNEL); + if (tps6507x == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, tps6507x); + tps6507x->dev = &i2c->dev; + tps6507x->i2c_client = i2c; + tps6507x->read_dev = tps6507x_i2c_read_device; + tps6507x->write_dev = tps6507x_i2c_write_device; + + return mfd_add_devices(tps6507x->dev, -1, tps6507x_devs, + ARRAY_SIZE(tps6507x_devs), NULL, 0, NULL); +} + +static int tps6507x_i2c_remove(struct i2c_client *i2c) +{ + struct tps6507x_dev *tps6507x = i2c_get_clientdata(i2c); + + mfd_remove_devices(tps6507x->dev); + return 0; +} + +static const struct i2c_device_id tps6507x_i2c_id[] = { + { "tps6507x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps6507x_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id tps6507x_of_match[] = { + {.compatible = "ti,tps6507x", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tps6507x_of_match); +#endif + +static struct i2c_driver tps6507x_i2c_driver = { + .driver = { + .name = "tps6507x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tps6507x_of_match), + }, + .probe = tps6507x_i2c_probe, + .remove = tps6507x_i2c_remove, + .id_table = tps6507x_i2c_id, +}; + +static int __init tps6507x_i2c_init(void) +{ + return i2c_add_driver(&tps6507x_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps6507x_i2c_init); + +static void __exit tps6507x_i2c_exit(void) +{ + i2c_del_driver(&tps6507x_i2c_driver); +} +module_exit(tps6507x_i2c_exit); + +MODULE_DESCRIPTION("TPS6507x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/tps65090.c b/kernel/drivers/mfd/tps65090.c new file mode 100644 index 000000000..14b62e11a --- /dev/null +++ b/kernel/drivers/mfd/tps65090.c @@ -0,0 +1,284 @@ +/* + * Core driver for TI TPS65090 PMIC family + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65090.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/err.h> + +#define NUM_INT_REG 2 +#define TOTAL_NUM_REG 0x18 + +#define TPS65090_INT1_MASK_VAC_STATUS_CHANGE 1 +#define TPS65090_INT1_MASK_VSYS_STATUS_CHANGE 2 +#define TPS65090_INT1_MASK_BAT_STATUS_CHANGE 3 +#define TPS65090_INT1_MASK_CHARGING_STATUS_CHANGE 4 +#define TPS65090_INT1_MASK_CHARGING_COMPLETE 5 +#define TPS65090_INT1_MASK_OVERLOAD_DCDC1 6 +#define TPS65090_INT1_MASK_OVERLOAD_DCDC2 7 +#define TPS65090_INT2_MASK_OVERLOAD_DCDC3 0 +#define TPS65090_INT2_MASK_OVERLOAD_FET1 1 +#define TPS65090_INT2_MASK_OVERLOAD_FET2 2 +#define TPS65090_INT2_MASK_OVERLOAD_FET3 3 +#define TPS65090_INT2_MASK_OVERLOAD_FET4 4 +#define TPS65090_INT2_MASK_OVERLOAD_FET5 5 +#define TPS65090_INT2_MASK_OVERLOAD_FET6 6 +#define TPS65090_INT2_MASK_OVERLOAD_FET7 7 + +static struct resource charger_resources[] = { + { + .start = TPS65090_IRQ_VAC_STATUS_CHANGE, + .end = TPS65090_IRQ_VAC_STATUS_CHANGE, + .flags = IORESOURCE_IRQ, + } +}; + +enum tps65090_cells { + PMIC = 0, + CHARGER = 1, +}; + +static struct mfd_cell tps65090s[] = { + [PMIC] = { + .name = "tps65090-pmic", + }, + [CHARGER] = { + .name = "tps65090-charger", + .num_resources = ARRAY_SIZE(charger_resources), + .resources = &charger_resources[0], + .of_compatible = "ti,tps65090-charger", + }, +}; + +static const struct regmap_irq tps65090_irqs[] = { + /* INT1 IRQs*/ + [TPS65090_IRQ_VAC_STATUS_CHANGE] = { + .mask = TPS65090_INT1_MASK_VAC_STATUS_CHANGE, + }, + [TPS65090_IRQ_VSYS_STATUS_CHANGE] = { + .mask = TPS65090_INT1_MASK_VSYS_STATUS_CHANGE, + }, + [TPS65090_IRQ_BAT_STATUS_CHANGE] = { + .mask = TPS65090_INT1_MASK_BAT_STATUS_CHANGE, + }, + [TPS65090_IRQ_CHARGING_STATUS_CHANGE] = { + .mask = TPS65090_INT1_MASK_CHARGING_STATUS_CHANGE, + }, + [TPS65090_IRQ_CHARGING_COMPLETE] = { + .mask = TPS65090_INT1_MASK_CHARGING_COMPLETE, + }, + [TPS65090_IRQ_OVERLOAD_DCDC1] = { + .mask = TPS65090_INT1_MASK_OVERLOAD_DCDC1, + }, + [TPS65090_IRQ_OVERLOAD_DCDC2] = { + .mask = TPS65090_INT1_MASK_OVERLOAD_DCDC2, + }, + /* INT2 IRQs*/ + [TPS65090_IRQ_OVERLOAD_DCDC3] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_DCDC3, + }, + [TPS65090_IRQ_OVERLOAD_FET1] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET1, + }, + [TPS65090_IRQ_OVERLOAD_FET2] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET2, + }, + [TPS65090_IRQ_OVERLOAD_FET3] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET3, + }, + [TPS65090_IRQ_OVERLOAD_FET4] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET4, + }, + [TPS65090_IRQ_OVERLOAD_FET5] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET5, + }, + [TPS65090_IRQ_OVERLOAD_FET6] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET6, + }, + [TPS65090_IRQ_OVERLOAD_FET7] = { + .reg_offset = 1, + .mask = TPS65090_INT2_MASK_OVERLOAD_FET7, + }, +}; + +static struct regmap_irq_chip tps65090_irq_chip = { + .name = "tps65090", + .irqs = tps65090_irqs, + .num_irqs = ARRAY_SIZE(tps65090_irqs), + .num_regs = NUM_INT_REG, + .status_base = TPS65090_REG_INTR_STS, + .mask_base = TPS65090_REG_INTR_MASK, + .mask_invert = true, +}; + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Nearly all registers have status bits mixed in, except a few */ + switch (reg) { + case TPS65090_REG_INTR_MASK: + case TPS65090_REG_INTR_MASK2: + case TPS65090_REG_CG_CTRL0: + case TPS65090_REG_CG_CTRL1: + case TPS65090_REG_CG_CTRL2: + case TPS65090_REG_CG_CTRL3: + case TPS65090_REG_CG_CTRL4: + case TPS65090_REG_CG_CTRL5: + return false; + } + return true; +} + +static const struct regmap_config tps65090_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TOTAL_NUM_REG, + .num_reg_defaults_raw = TOTAL_NUM_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = is_volatile_reg, +}; + +#ifdef CONFIG_OF +static const struct of_device_id tps65090_of_match[] = { + { .compatible = "ti,tps65090",}, + {}, +}; +MODULE_DEVICE_TABLE(of, tps65090_of_match); +#endif + +static int tps65090_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps65090_platform_data *pdata = dev_get_platdata(&client->dev); + int irq_base = 0; + struct tps65090 *tps65090; + int ret; + + if (!pdata && !client->dev.of_node) { + dev_err(&client->dev, + "tps65090 requires platform data or of_node\n"); + return -EINVAL; + } + + if (pdata) + irq_base = pdata->irq_base; + + tps65090 = devm_kzalloc(&client->dev, sizeof(*tps65090), GFP_KERNEL); + if (!tps65090) { + dev_err(&client->dev, "mem alloc for tps65090 failed\n"); + return -ENOMEM; + } + + tps65090->dev = &client->dev; + i2c_set_clientdata(client, tps65090); + + tps65090->rmap = devm_regmap_init_i2c(client, &tps65090_regmap_config); + if (IS_ERR(tps65090->rmap)) { + ret = PTR_ERR(tps65090->rmap); + dev_err(&client->dev, "regmap_init failed with err: %d\n", ret); + return ret; + } + + if (client->irq) { + ret = regmap_add_irq_chip(tps65090->rmap, client->irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, irq_base, + &tps65090_irq_chip, &tps65090->irq_data); + if (ret) { + dev_err(&client->dev, + "IRQ init failed with err: %d\n", ret); + return ret; + } + } else { + /* Don't tell children they have an IRQ that'll never fire */ + tps65090s[CHARGER].num_resources = 0; + } + + ret = mfd_add_devices(tps65090->dev, -1, tps65090s, + ARRAY_SIZE(tps65090s), NULL, + 0, regmap_irq_get_domain(tps65090->irq_data)); + if (ret) { + dev_err(&client->dev, "add mfd devices failed with err: %d\n", + ret); + goto err_irq_exit; + } + + return 0; + +err_irq_exit: + if (client->irq) + regmap_del_irq_chip(client->irq, tps65090->irq_data); + return ret; +} + +static int tps65090_i2c_remove(struct i2c_client *client) +{ + struct tps65090 *tps65090 = i2c_get_clientdata(client); + + mfd_remove_devices(tps65090->dev); + if (client->irq) + regmap_del_irq_chip(client->irq, tps65090->irq_data); + + return 0; +} + +static const struct i2c_device_id tps65090_id_table[] = { + { "tps65090", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tps65090_id_table); + +static struct i2c_driver tps65090_driver = { + .driver = { + .name = "tps65090", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tps65090_of_match), + }, + .probe = tps65090_i2c_probe, + .remove = tps65090_i2c_remove, + .id_table = tps65090_id_table, +}; + +static int __init tps65090_init(void) +{ + return i2c_add_driver(&tps65090_driver); +} +subsys_initcall(tps65090_init); + +static void __exit tps65090_exit(void) +{ + i2c_del_driver(&tps65090_driver); +} +module_exit(tps65090_exit); + +MODULE_DESCRIPTION("TPS65090 core driver"); +MODULE_AUTHOR("Venu Byravarasu <vbyravarasu@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/tps65217.c b/kernel/drivers/mfd/tps65217.c new file mode 100644 index 000000000..7d1cfc1d3 --- /dev/null +++ b/kernel/drivers/mfd/tps65217.c @@ -0,0 +1,273 @@ +/* + * tps65217.c + * + * TPS65217 chip family multi-function driver + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/tps65217.h> + +static const struct mfd_cell tps65217s[] = { + { + .name = "tps65217-pmic", + .of_compatible = "ti,tps65217-pmic", + }, + { + .name = "tps65217-bl", + .of_compatible = "ti,tps65217-bl", + }, +}; + +/** + * tps65217_reg_read: Read a single tps65217 register. + * + * @tps: Device to read from. + * @reg: Register to read. + * @val: Contians the value + */ +int tps65217_reg_read(struct tps65217 *tps, unsigned int reg, + unsigned int *val) +{ + return regmap_read(tps->regmap, reg, val); +} +EXPORT_SYMBOL_GPL(tps65217_reg_read); + +/** + * tps65217_reg_write: Write a single tps65217 register. + * + * @tps65217: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + * @level: Password protected level + */ +int tps65217_reg_write(struct tps65217 *tps, unsigned int reg, + unsigned int val, unsigned int level) +{ + int ret; + unsigned int xor_reg_val; + + switch (level) { + case TPS65217_PROTECT_NONE: + return regmap_write(tps->regmap, reg, val); + case TPS65217_PROTECT_L1: + xor_reg_val = reg ^ TPS65217_PASSWORD_REGS_UNLOCK; + ret = regmap_write(tps->regmap, TPS65217_REG_PASSWORD, + xor_reg_val); + if (ret < 0) + return ret; + + return regmap_write(tps->regmap, reg, val); + case TPS65217_PROTECT_L2: + xor_reg_val = reg ^ TPS65217_PASSWORD_REGS_UNLOCK; + ret = regmap_write(tps->regmap, TPS65217_REG_PASSWORD, + xor_reg_val); + if (ret < 0) + return ret; + ret = regmap_write(tps->regmap, reg, val); + if (ret < 0) + return ret; + ret = regmap_write(tps->regmap, TPS65217_REG_PASSWORD, + xor_reg_val); + if (ret < 0) + return ret; + return regmap_write(tps->regmap, reg, val); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(tps65217_reg_write); + +/** + * tps65217_update_bits: Modify bits w.r.t mask, val and level. + * + * @tps65217: Device to write to. + * @reg: Register to read-write to. + * @mask: Mask. + * @val: Value to write. + * @level: Password protected level + */ +static int tps65217_update_bits(struct tps65217 *tps, unsigned int reg, + unsigned int mask, unsigned int val, unsigned int level) +{ + int ret; + unsigned int data; + + ret = tps65217_reg_read(tps, reg, &data); + if (ret) { + dev_err(tps->dev, "Read from reg 0x%x failed\n", reg); + return ret; + } + + data &= ~mask; + data |= val & mask; + + ret = tps65217_reg_write(tps, reg, data, level); + if (ret) + dev_err(tps->dev, "Write for reg 0x%x failed\n", reg); + + return ret; +} + +int tps65217_set_bits(struct tps65217 *tps, unsigned int reg, + unsigned int mask, unsigned int val, unsigned int level) +{ + return tps65217_update_bits(tps, reg, mask, val, level); +} +EXPORT_SYMBOL_GPL(tps65217_set_bits); + +int tps65217_clear_bits(struct tps65217 *tps, unsigned int reg, + unsigned int mask, unsigned int level) +{ + return tps65217_update_bits(tps, reg, mask, 0, level); +} +EXPORT_SYMBOL_GPL(tps65217_clear_bits); + +static const struct regmap_config tps65217_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TPS65217_REG_MAX, +}; + +static const struct of_device_id tps65217_of_match[] = { + { .compatible = "ti,tps65217", .data = (void *)TPS65217 }, + { /* sentinel */ }, +}; + +static int tps65217_probe(struct i2c_client *client, + const struct i2c_device_id *ids) +{ + struct tps65217 *tps; + unsigned int version; + unsigned long chip_id = ids->driver_data; + const struct of_device_id *match; + bool status_off = false; + int ret; + + if (client->dev.of_node) { + match = of_match_device(tps65217_of_match, &client->dev); + if (!match) { + dev_err(&client->dev, + "Failed to find matching dt id\n"); + return -EINVAL; + } + chip_id = (unsigned long)match->data; + status_off = of_property_read_bool(client->dev.of_node, + "ti,pmic-shutdown-controller"); + } + + if (!chip_id) { + dev_err(&client->dev, "id is null.\n"); + return -ENODEV; + } + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + i2c_set_clientdata(client, tps); + tps->dev = &client->dev; + tps->id = chip_id; + + tps->regmap = devm_regmap_init_i2c(client, &tps65217_regmap_config); + if (IS_ERR(tps->regmap)) { + ret = PTR_ERR(tps->regmap); + dev_err(tps->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = mfd_add_devices(tps->dev, -1, tps65217s, + ARRAY_SIZE(tps65217s), NULL, 0, NULL); + if (ret < 0) { + dev_err(tps->dev, "mfd_add_devices failed: %d\n", ret); + return ret; + } + + ret = tps65217_reg_read(tps, TPS65217_REG_CHIPID, &version); + if (ret < 0) { + dev_err(tps->dev, "Failed to read revision register: %d\n", + ret); + return ret; + } + + /* Set the PMIC to shutdown on PWR_EN toggle */ + if (status_off) { + ret = tps65217_set_bits(tps, TPS65217_REG_STATUS, + TPS65217_STATUS_OFF, TPS65217_STATUS_OFF, + TPS65217_PROTECT_NONE); + if (ret) + dev_warn(tps->dev, "unable to set the status OFF\n"); + } + + dev_info(tps->dev, "TPS65217 ID %#x version 1.%d\n", + (version & TPS65217_CHIPID_CHIP_MASK) >> 4, + version & TPS65217_CHIPID_REV_MASK); + + return 0; +} + +static int tps65217_remove(struct i2c_client *client) +{ + struct tps65217 *tps = i2c_get_clientdata(client); + + mfd_remove_devices(tps->dev); + + return 0; +} + +static const struct i2c_device_id tps65217_id_table[] = { + {"tps65217", TPS65217}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, tps65217_id_table); + +static struct i2c_driver tps65217_driver = { + .driver = { + .name = "tps65217", + .owner = THIS_MODULE, + .of_match_table = tps65217_of_match, + }, + .id_table = tps65217_id_table, + .probe = tps65217_probe, + .remove = tps65217_remove, +}; + +static int __init tps65217_init(void) +{ + return i2c_add_driver(&tps65217_driver); +} +subsys_initcall(tps65217_init); + +static void __exit tps65217_exit(void) +{ + i2c_del_driver(&tps65217_driver); +} +module_exit(tps65217_exit); + +MODULE_AUTHOR("AnilKumar Ch <anilkumar@ti.com>"); +MODULE_DESCRIPTION("TPS65217 chip family multi-function driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/tps65218.c b/kernel/drivers/mfd/tps65218.c new file mode 100644 index 000000000..7af11a8b9 --- /dev/null +++ b/kernel/drivers/mfd/tps65218.c @@ -0,0 +1,295 @@ +/* + * Driver for TPS65218 Integrated power management chipsets + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.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 "as is" WITHOUT ANY WARRANTY of any + * kind, whether expressed or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License version 2 for more details. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/tps65218.h> + +#define TPS65218_PASSWORD_REGS_UNLOCK 0x7D + +/** + * tps65218_reg_read: Read a single tps65218 register. + * + * @tps: Device to read from. + * @reg: Register to read. + * @val: Contians the value + */ +int tps65218_reg_read(struct tps65218 *tps, unsigned int reg, + unsigned int *val) +{ + return regmap_read(tps->regmap, reg, val); +} +EXPORT_SYMBOL_GPL(tps65218_reg_read); + +/** + * tps65218_reg_write: Write a single tps65218 register. + * + * @tps65218: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + * @level: Password protected level + */ +int tps65218_reg_write(struct tps65218 *tps, unsigned int reg, + unsigned int val, unsigned int level) +{ + int ret; + unsigned int xor_reg_val; + + switch (level) { + case TPS65218_PROTECT_NONE: + return regmap_write(tps->regmap, reg, val); + case TPS65218_PROTECT_L1: + xor_reg_val = reg ^ TPS65218_PASSWORD_REGS_UNLOCK; + ret = regmap_write(tps->regmap, TPS65218_REG_PASSWORD, + xor_reg_val); + if (ret < 0) + return ret; + + return regmap_write(tps->regmap, reg, val); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(tps65218_reg_write); + +/** + * tps65218_update_bits: Modify bits w.r.t mask, val and level. + * + * @tps65218: Device to write to. + * @reg: Register to read-write to. + * @mask: Mask. + * @val: Value to write. + * @level: Password protected level + */ +static int tps65218_update_bits(struct tps65218 *tps, unsigned int reg, + unsigned int mask, unsigned int val, unsigned int level) +{ + int ret; + unsigned int data; + + ret = tps65218_reg_read(tps, reg, &data); + if (ret) { + dev_err(tps->dev, "Read from reg 0x%x failed\n", reg); + return ret; + } + + data &= ~mask; + data |= val & mask; + + mutex_lock(&tps->tps_lock); + ret = tps65218_reg_write(tps, reg, data, level); + if (ret) + dev_err(tps->dev, "Write for reg 0x%x failed\n", reg); + mutex_unlock(&tps->tps_lock); + + return ret; +} + +int tps65218_set_bits(struct tps65218 *tps, unsigned int reg, + unsigned int mask, unsigned int val, unsigned int level) +{ + return tps65218_update_bits(tps, reg, mask, val, level); +} +EXPORT_SYMBOL_GPL(tps65218_set_bits); + +int tps65218_clear_bits(struct tps65218 *tps, unsigned int reg, + unsigned int mask, unsigned int level) +{ + return tps65218_update_bits(tps, reg, mask, 0, level); +} +EXPORT_SYMBOL_GPL(tps65218_clear_bits); + +static const struct regmap_range tps65218_yes_ranges[] = { + regmap_reg_range(TPS65218_REG_INT1, TPS65218_REG_INT2), + regmap_reg_range(TPS65218_REG_STATUS, TPS65218_REG_STATUS), +}; + +static const struct regmap_access_table tps65218_volatile_table = { + .yes_ranges = tps65218_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(tps65218_yes_ranges), +}; + +static const struct regmap_config tps65218_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &tps65218_volatile_table, +}; + +static const struct regmap_irq tps65218_irqs[] = { + /* INT1 IRQs */ + [TPS65218_PRGC_IRQ] = { + .mask = TPS65218_INT1_PRGC, + }, + [TPS65218_CC_AQC_IRQ] = { + .mask = TPS65218_INT1_CC_AQC, + }, + [TPS65218_HOT_IRQ] = { + .mask = TPS65218_INT1_HOT, + }, + [TPS65218_PB_IRQ] = { + .mask = TPS65218_INT1_PB, + }, + [TPS65218_AC_IRQ] = { + .mask = TPS65218_INT1_AC, + }, + [TPS65218_VPRG_IRQ] = { + .mask = TPS65218_INT1_VPRG, + }, + [TPS65218_INVALID1_IRQ] = { + }, + [TPS65218_INVALID2_IRQ] = { + }, + /* INT2 IRQs*/ + [TPS65218_LS1_I_IRQ] = { + .mask = TPS65218_INT2_LS1_I, + .reg_offset = 1, + }, + [TPS65218_LS2_I_IRQ] = { + .mask = TPS65218_INT2_LS2_I, + .reg_offset = 1, + }, + [TPS65218_LS3_I_IRQ] = { + .mask = TPS65218_INT2_LS3_I, + .reg_offset = 1, + }, + [TPS65218_LS1_F_IRQ] = { + .mask = TPS65218_INT2_LS1_F, + .reg_offset = 1, + }, + [TPS65218_LS2_F_IRQ] = { + .mask = TPS65218_INT2_LS2_F, + .reg_offset = 1, + }, + [TPS65218_LS3_F_IRQ] = { + .mask = TPS65218_INT2_LS3_F, + .reg_offset = 1, + }, + [TPS65218_INVALID3_IRQ] = { + }, + [TPS65218_INVALID4_IRQ] = { + }, +}; + +static struct regmap_irq_chip tps65218_irq_chip = { + .name = "tps65218", + .irqs = tps65218_irqs, + .num_irqs = ARRAY_SIZE(tps65218_irqs), + + .num_regs = 2, + .mask_base = TPS65218_REG_INT_MASK1, + .status_base = TPS65218_REG_INT1, +}; + +static const struct of_device_id of_tps65218_match_table[] = { + { .compatible = "ti,tps65218", }, + {} +}; + +static int tps65218_probe(struct i2c_client *client, + const struct i2c_device_id *ids) +{ + struct tps65218 *tps; + const struct of_device_id *match; + int ret; + + match = of_match_device(of_tps65218_match_table, &client->dev); + if (!match) { + dev_err(&client->dev, + "Failed to find matching dt id\n"); + return -EINVAL; + } + + tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + if (!tps) + return -ENOMEM; + + i2c_set_clientdata(client, tps); + tps->dev = &client->dev; + tps->irq = client->irq; + tps->regmap = devm_regmap_init_i2c(client, &tps65218_regmap_config); + if (IS_ERR(tps->regmap)) { + ret = PTR_ERR(tps->regmap); + dev_err(tps->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + mutex_init(&tps->tps_lock); + + ret = regmap_add_irq_chip(tps->regmap, tps->irq, + IRQF_ONESHOT, 0, &tps65218_irq_chip, + &tps->irq_data); + if (ret < 0) + return ret; + + ret = of_platform_populate(client->dev.of_node, NULL, NULL, + &client->dev); + if (ret < 0) + goto err_irq; + + return 0; + +err_irq: + regmap_del_irq_chip(tps->irq, tps->irq_data); + + return ret; +} + +static int tps65218_remove(struct i2c_client *client) +{ + struct tps65218 *tps = i2c_get_clientdata(client); + + regmap_del_irq_chip(tps->irq, tps->irq_data); + + return 0; +} + +static const struct i2c_device_id tps65218_id_table[] = { + { "tps65218", TPS65218 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tps65218_id_table); + +static struct i2c_driver tps65218_driver = { + .driver = { + .name = "tps65218", + .owner = THIS_MODULE, + .of_match_table = of_tps65218_match_table, + }, + .probe = tps65218_probe, + .remove = tps65218_remove, + .id_table = tps65218_id_table, +}; + +module_i2c_driver(tps65218_driver); + +MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>"); +MODULE_DESCRIPTION("TPS65218 chip family multi-function driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/tps6586x.c b/kernel/drivers/mfd/tps6586x.c new file mode 100644 index 000000000..8e1dbc469 --- /dev/null +++ b/kernel/drivers/mfd/tps6586x.c @@ -0,0 +1,635 @@ +/* + * Core driver for TI TPS6586x PMIC family + * + * Copyright (c) 2010 CompuLab Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Based on da903x.c. + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.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. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/of.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/tps6586x.h> + +#define TPS6586X_SUPPLYENE 0x14 +#define EXITSLREQ_BIT BIT(1) +#define SLEEP_MODE_BIT BIT(3) + +/* interrupt control registers */ +#define TPS6586X_INT_ACK1 0xb5 +#define TPS6586X_INT_ACK2 0xb6 +#define TPS6586X_INT_ACK3 0xb7 +#define TPS6586X_INT_ACK4 0xb8 + +/* interrupt mask registers */ +#define TPS6586X_INT_MASK1 0xb0 +#define TPS6586X_INT_MASK2 0xb1 +#define TPS6586X_INT_MASK3 0xb2 +#define TPS6586X_INT_MASK4 0xb3 +#define TPS6586X_INT_MASK5 0xb4 + +/* device id */ +#define TPS6586X_VERSIONCRC 0xcd + +/* Maximum register */ +#define TPS6586X_MAX_REGISTER (TPS6586X_VERSIONCRC + 1) + +struct tps6586x_irq_data { + u8 mask_reg; + u8 mask_mask; +}; + +#define TPS6586X_IRQ(_reg, _mask) \ + { \ + .mask_reg = (_reg) - TPS6586X_INT_MASK1, \ + .mask_mask = (_mask), \ + } + +static const struct tps6586x_irq_data tps6586x_irqs[] = { + [TPS6586X_INT_PLDO_0] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 0), + [TPS6586X_INT_PLDO_1] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 1), + [TPS6586X_INT_PLDO_2] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 2), + [TPS6586X_INT_PLDO_3] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 3), + [TPS6586X_INT_PLDO_4] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 4), + [TPS6586X_INT_PLDO_5] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 5), + [TPS6586X_INT_PLDO_6] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 6), + [TPS6586X_INT_PLDO_7] = TPS6586X_IRQ(TPS6586X_INT_MASK1, 1 << 7), + [TPS6586X_INT_COMP_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 1 << 0), + [TPS6586X_INT_ADC] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 1), + [TPS6586X_INT_PLDO_8] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 2), + [TPS6586X_INT_PLDO_9] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 3), + [TPS6586X_INT_PSM_0] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 4), + [TPS6586X_INT_PSM_1] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 5), + [TPS6586X_INT_PSM_2] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 6), + [TPS6586X_INT_PSM_3] = TPS6586X_IRQ(TPS6586X_INT_MASK2, 1 << 7), + [TPS6586X_INT_RTC_ALM1] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 4), + [TPS6586X_INT_ACUSB_OVP] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 0x03), + [TPS6586X_INT_USB_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 2), + [TPS6586X_INT_AC_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 3), + [TPS6586X_INT_BAT_DET] = TPS6586X_IRQ(TPS6586X_INT_MASK3, 1 << 0), + [TPS6586X_INT_CHG_STAT] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 0xfc), + [TPS6586X_INT_CHG_TEMP] = TPS6586X_IRQ(TPS6586X_INT_MASK3, 0x06), + [TPS6586X_INT_PP] = TPS6586X_IRQ(TPS6586X_INT_MASK3, 0xf0), + [TPS6586X_INT_RESUME] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 5), + [TPS6586X_INT_LOW_SYS] = TPS6586X_IRQ(TPS6586X_INT_MASK5, 1 << 6), + [TPS6586X_INT_RTC_ALM2] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 1 << 1), +}; + +static struct resource tps6586x_rtc_resources[] = { + { + .start = TPS6586X_INT_RTC_ALM1, + .end = TPS6586X_INT_RTC_ALM1, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell tps6586x_cell[] = { + { + .name = "tps6586x-gpio", + }, + { + .name = "tps6586x-regulator", + }, + { + .name = "tps6586x-rtc", + .num_resources = ARRAY_SIZE(tps6586x_rtc_resources), + .resources = &tps6586x_rtc_resources[0], + }, + { + .name = "tps6586x-onkey", + }, +}; + +struct tps6586x { + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + int version; + + int irq; + struct irq_chip irq_chip; + struct mutex irq_lock; + int irq_base; + u32 irq_en; + u8 mask_reg[5]; + struct irq_domain *irq_domain; +}; + +static inline struct tps6586x *dev_to_tps6586x(struct device *dev) +{ + return i2c_get_clientdata(to_i2c_client(dev)); +} + +int tps6586x_write(struct device *dev, int reg, uint8_t val) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return regmap_write(tps6586x->regmap, reg, val); +} +EXPORT_SYMBOL_GPL(tps6586x_write); + +int tps6586x_writes(struct device *dev, int reg, int len, uint8_t *val) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return regmap_bulk_write(tps6586x->regmap, reg, val, len); +} +EXPORT_SYMBOL_GPL(tps6586x_writes); + +int tps6586x_read(struct device *dev, int reg, uint8_t *val) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + unsigned int rval; + int ret; + + ret = regmap_read(tps6586x->regmap, reg, &rval); + if (!ret) + *val = rval; + return ret; +} +EXPORT_SYMBOL_GPL(tps6586x_read); + +int tps6586x_reads(struct device *dev, int reg, int len, uint8_t *val) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return regmap_bulk_read(tps6586x->regmap, reg, val, len); +} +EXPORT_SYMBOL_GPL(tps6586x_reads); + +int tps6586x_set_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return regmap_update_bits(tps6586x->regmap, reg, bit_mask, bit_mask); +} +EXPORT_SYMBOL_GPL(tps6586x_set_bits); + +int tps6586x_clr_bits(struct device *dev, int reg, uint8_t bit_mask) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return regmap_update_bits(tps6586x->regmap, reg, bit_mask, 0); +} +EXPORT_SYMBOL_GPL(tps6586x_clr_bits); + +int tps6586x_update(struct device *dev, int reg, uint8_t val, uint8_t mask) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return regmap_update_bits(tps6586x->regmap, reg, mask, val); +} +EXPORT_SYMBOL_GPL(tps6586x_update); + +int tps6586x_irq_get_virq(struct device *dev, int irq) +{ + struct tps6586x *tps6586x = dev_to_tps6586x(dev); + + return irq_create_mapping(tps6586x->irq_domain, irq); +} +EXPORT_SYMBOL_GPL(tps6586x_irq_get_virq); + +int tps6586x_get_version(struct device *dev) +{ + struct tps6586x *tps6586x = dev_get_drvdata(dev); + + return tps6586x->version; +} +EXPORT_SYMBOL_GPL(tps6586x_get_version); + +static int __remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int tps6586x_remove_subdevs(struct tps6586x *tps6586x) +{ + return device_for_each_child(tps6586x->dev, NULL, __remove_subdev); +} + +static void tps6586x_irq_lock(struct irq_data *data) +{ + struct tps6586x *tps6586x = irq_data_get_irq_chip_data(data); + + mutex_lock(&tps6586x->irq_lock); +} + +static void tps6586x_irq_enable(struct irq_data *irq_data) +{ + struct tps6586x *tps6586x = irq_data_get_irq_chip_data(irq_data); + unsigned int __irq = irq_data->hwirq; + const struct tps6586x_irq_data *data = &tps6586x_irqs[__irq]; + + tps6586x->mask_reg[data->mask_reg] &= ~data->mask_mask; + tps6586x->irq_en |= (1 << __irq); +} + +static void tps6586x_irq_disable(struct irq_data *irq_data) +{ + struct tps6586x *tps6586x = irq_data_get_irq_chip_data(irq_data); + + unsigned int __irq = irq_data->hwirq; + const struct tps6586x_irq_data *data = &tps6586x_irqs[__irq]; + + tps6586x->mask_reg[data->mask_reg] |= data->mask_mask; + tps6586x->irq_en &= ~(1 << __irq); +} + +static void tps6586x_irq_sync_unlock(struct irq_data *data) +{ + struct tps6586x *tps6586x = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(tps6586x->mask_reg); i++) { + int ret; + ret = tps6586x_write(tps6586x->dev, + TPS6586X_INT_MASK1 + i, + tps6586x->mask_reg[i]); + WARN_ON(ret); + } + + mutex_unlock(&tps6586x->irq_lock); +} + +#ifdef CONFIG_PM_SLEEP +static int tps6586x_irq_set_wake(struct irq_data *irq_data, unsigned int on) +{ + struct tps6586x *tps6586x = irq_data_get_irq_chip_data(irq_data); + return irq_set_irq_wake(tps6586x->irq, on); +} +#else +#define tps6586x_irq_set_wake NULL +#endif + +static struct irq_chip tps6586x_irq_chip = { + .name = "tps6586x", + .irq_bus_lock = tps6586x_irq_lock, + .irq_bus_sync_unlock = tps6586x_irq_sync_unlock, + .irq_disable = tps6586x_irq_disable, + .irq_enable = tps6586x_irq_enable, + .irq_set_wake = tps6586x_irq_set_wake, +}; + +static int tps6586x_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct tps6586x *tps6586x = h->host_data; + + irq_set_chip_data(virq, tps6586x); + irq_set_chip_and_handler(virq, &tps6586x_irq_chip, handle_simple_irq); + irq_set_nested_thread(virq, 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(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static struct irq_domain_ops tps6586x_domain_ops = { + .map = tps6586x_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static irqreturn_t tps6586x_irq(int irq, void *data) +{ + struct tps6586x *tps6586x = data; + u32 acks; + int ret = 0; + + ret = tps6586x_reads(tps6586x->dev, TPS6586X_INT_ACK1, + sizeof(acks), (uint8_t *)&acks); + + if (ret < 0) { + dev_err(tps6586x->dev, "failed to read interrupt status\n"); + return IRQ_NONE; + } + + acks = le32_to_cpu(acks); + + while (acks) { + int i = __ffs(acks); + + if (tps6586x->irq_en & (1 << i)) + handle_nested_irq( + irq_find_mapping(tps6586x->irq_domain, i)); + + acks &= ~(1 << i); + } + + return IRQ_HANDLED; +} + +static int tps6586x_irq_init(struct tps6586x *tps6586x, int irq, + int irq_base) +{ + int i, ret; + u8 tmp[4]; + int new_irq_base; + int irq_num = ARRAY_SIZE(tps6586x_irqs); + + tps6586x->irq = irq; + + mutex_init(&tps6586x->irq_lock); + for (i = 0; i < 5; i++) { + tps6586x->mask_reg[i] = 0xff; + tps6586x_write(tps6586x->dev, TPS6586X_INT_MASK1 + i, 0xff); + } + + tps6586x_reads(tps6586x->dev, TPS6586X_INT_ACK1, sizeof(tmp), tmp); + + if (irq_base > 0) { + new_irq_base = irq_alloc_descs(irq_base, 0, irq_num, -1); + if (new_irq_base < 0) { + dev_err(tps6586x->dev, + "Failed to alloc IRQs: %d\n", new_irq_base); + return new_irq_base; + } + } else { + new_irq_base = 0; + } + + tps6586x->irq_domain = irq_domain_add_simple(tps6586x->dev->of_node, + irq_num, new_irq_base, &tps6586x_domain_ops, + tps6586x); + if (!tps6586x->irq_domain) { + dev_err(tps6586x->dev, "Failed to create IRQ domain\n"); + return -ENOMEM; + } + ret = request_threaded_irq(irq, NULL, tps6586x_irq, IRQF_ONESHOT, + "tps6586x", tps6586x); + + if (!ret) + device_init_wakeup(tps6586x->dev, 1); + + return ret; +} + +static int tps6586x_add_subdevs(struct tps6586x *tps6586x, + struct tps6586x_platform_data *pdata) +{ + struct tps6586x_subdev_info *subdev; + struct platform_device *pdev; + int i, ret = 0; + + for (i = 0; i < pdata->num_subdevs; i++) { + subdev = &pdata->subdevs[i]; + + pdev = platform_device_alloc(subdev->name, subdev->id); + if (!pdev) { + ret = -ENOMEM; + goto failed; + } + + pdev->dev.parent = tps6586x->dev; + pdev->dev.platform_data = subdev->platform_data; + pdev->dev.of_node = subdev->of_node; + + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto failed; + } + } + return 0; + +failed: + tps6586x_remove_subdevs(tps6586x); + return ret; +} + +#ifdef CONFIG_OF +static struct tps6586x_platform_data *tps6586x_parse_dt(struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node; + struct tps6586x_platform_data *pdata; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Memory allocation failed\n"); + return NULL; + } + + pdata->num_subdevs = 0; + pdata->subdevs = NULL; + pdata->gpio_base = -1; + pdata->irq_base = -1; + pdata->pm_off = of_property_read_bool(np, "ti,system-power-controller"); + + return pdata; +} + +static const struct of_device_id tps6586x_of_match[] = { + { .compatible = "ti,tps6586x", }, + { }, +}; +#else +static struct tps6586x_platform_data *tps6586x_parse_dt(struct i2c_client *client) +{ + return NULL; +} +#endif + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Cache all interrupt mask register */ + if ((reg >= TPS6586X_INT_MASK1) && (reg <= TPS6586X_INT_MASK5)) + return false; + + return true; +} + +static const struct regmap_config tps6586x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPS6586X_MAX_REGISTER - 1, + .volatile_reg = is_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static struct device *tps6586x_dev; +static void tps6586x_power_off(void) +{ + if (tps6586x_clr_bits(tps6586x_dev, TPS6586X_SUPPLYENE, EXITSLREQ_BIT)) + return; + + tps6586x_set_bits(tps6586x_dev, TPS6586X_SUPPLYENE, SLEEP_MODE_BIT); +} + +static void tps6586x_print_version(struct i2c_client *client, int version) +{ + const char *name; + + switch (version) { + case TPS658621A: + name = "TPS658621A"; + break; + case TPS658621CD: + name = "TPS658621C/D"; + break; + case TPS658623: + name = "TPS658623"; + break; + case TPS658640: + case TPS658640v2: + name = "TPS658640"; + break; + case TPS658643: + name = "TPS658643"; + break; + default: + name = "TPS6586X"; + break; + } + + dev_info(&client->dev, "Found %s, VERSIONCRC is %02x\n", name, version); +} + +static int tps6586x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps6586x_platform_data *pdata = dev_get_platdata(&client->dev); + struct tps6586x *tps6586x; + int ret; + int version; + + if (!pdata && client->dev.of_node) + pdata = tps6586x_parse_dt(client); + + if (!pdata) { + dev_err(&client->dev, "tps6586x requires platform data\n"); + return -ENOTSUPP; + } + + version = i2c_smbus_read_byte_data(client, TPS6586X_VERSIONCRC); + if (version < 0) { + dev_err(&client->dev, "Chip ID read failed: %d\n", version); + return -EIO; + } + + tps6586x = devm_kzalloc(&client->dev, sizeof(*tps6586x), GFP_KERNEL); + if (!tps6586x) + return -ENOMEM; + + tps6586x->version = version; + tps6586x_print_version(client, tps6586x->version); + + tps6586x->client = client; + tps6586x->dev = &client->dev; + i2c_set_clientdata(client, tps6586x); + + tps6586x->regmap = devm_regmap_init_i2c(client, + &tps6586x_regmap_config); + if (IS_ERR(tps6586x->regmap)) { + ret = PTR_ERR(tps6586x->regmap); + dev_err(&client->dev, "regmap init failed: %d\n", ret); + return ret; + } + + + if (client->irq) { + ret = tps6586x_irq_init(tps6586x, client->irq, + pdata->irq_base); + if (ret) { + dev_err(&client->dev, "IRQ init failed: %d\n", ret); + return ret; + } + } + + ret = mfd_add_devices(tps6586x->dev, -1, + tps6586x_cell, ARRAY_SIZE(tps6586x_cell), + NULL, 0, tps6586x->irq_domain); + if (ret < 0) { + dev_err(&client->dev, "mfd_add_devices failed: %d\n", ret); + goto err_mfd_add; + } + + ret = tps6586x_add_subdevs(tps6586x, pdata); + if (ret) { + dev_err(&client->dev, "add devices failed: %d\n", ret); + goto err_add_devs; + } + + if (pdata->pm_off && !pm_power_off) { + tps6586x_dev = &client->dev; + pm_power_off = tps6586x_power_off; + } + + return 0; + +err_add_devs: + mfd_remove_devices(tps6586x->dev); +err_mfd_add: + if (client->irq) + free_irq(client->irq, tps6586x); + return ret; +} + +static int tps6586x_i2c_remove(struct i2c_client *client) +{ + struct tps6586x *tps6586x = i2c_get_clientdata(client); + + tps6586x_remove_subdevs(tps6586x); + mfd_remove_devices(tps6586x->dev); + if (client->irq) + free_irq(client->irq, tps6586x); + return 0; +} + +static const struct i2c_device_id tps6586x_id_table[] = { + { "tps6586x", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tps6586x_id_table); + +static struct i2c_driver tps6586x_driver = { + .driver = { + .name = "tps6586x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tps6586x_of_match), + }, + .probe = tps6586x_i2c_probe, + .remove = tps6586x_i2c_remove, + .id_table = tps6586x_id_table, +}; + +static int __init tps6586x_init(void) +{ + return i2c_add_driver(&tps6586x_driver); +} +subsys_initcall(tps6586x_init); + +static void __exit tps6586x_exit(void) +{ + i2c_del_driver(&tps6586x_driver); +} +module_exit(tps6586x_exit); + +MODULE_DESCRIPTION("TPS6586X core driver"); +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/tps65910.c b/kernel/drivers/mfd/tps65910.c new file mode 100644 index 000000000..7612d8985 --- /dev/null +++ b/kernel/drivers/mfd/tps65910.c @@ -0,0 +1,571 @@ +/* + * tps65910.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/mfd/core.h> +#include <linux/regmap.h> +#include <linux/mfd/tps65910.h> +#include <linux/of.h> +#include <linux/of_device.h> + +static struct resource rtc_resources[] = { + { + .start = TPS65910_IRQ_RTC_ALARM, + .end = TPS65910_IRQ_RTC_ALARM, + .flags = IORESOURCE_IRQ, + } +}; + +static const struct mfd_cell tps65910s[] = { + { + .name = "tps65910-gpio", + }, + { + .name = "tps65910-pmic", + }, + { + .name = "tps65910-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), + .resources = &rtc_resources[0], + }, + { + .name = "tps65910-power", + }, +}; + + +static const struct regmap_irq tps65911_irqs[] = { + /* INT_STS */ + [TPS65911_IRQ_PWRHOLD_F] = { + .mask = INT_MSK_PWRHOLD_F_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_VBAT_VMHI] = { + .mask = INT_MSK_VMBHI_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_PWRON] = { + .mask = INT_MSK_PWRON_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_PWRON_LP] = { + .mask = INT_MSK_PWRON_LP_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_PWRHOLD_R] = { + .mask = INT_MSK_PWRHOLD_R_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_HOTDIE] = { + .mask = INT_MSK_HOTDIE_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_RTC_ALARM] = { + .mask = INT_MSK_RTC_ALARM_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65911_IRQ_RTC_PERIOD] = { + .mask = INT_MSK_RTC_PERIOD_IT_MSK_MASK, + .reg_offset = 0, + }, + + /* INT_STS2 */ + [TPS65911_IRQ_GPIO0_R] = { + .mask = INT_MSK2_GPIO0_R_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO0_F] = { + .mask = INT_MSK2_GPIO0_F_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO1_R] = { + .mask = INT_MSK2_GPIO1_R_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO1_F] = { + .mask = INT_MSK2_GPIO1_F_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO2_R] = { + .mask = INT_MSK2_GPIO2_R_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO2_F] = { + .mask = INT_MSK2_GPIO2_F_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO3_R] = { + .mask = INT_MSK2_GPIO3_R_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65911_IRQ_GPIO3_F] = { + .mask = INT_MSK2_GPIO3_F_IT_MSK_MASK, + .reg_offset = 1, + }, + + /* INT_STS2 */ + [TPS65911_IRQ_GPIO4_R] = { + .mask = INT_MSK3_GPIO4_R_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_GPIO4_F] = { + .mask = INT_MSK3_GPIO4_F_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_GPIO5_R] = { + .mask = INT_MSK3_GPIO5_R_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_GPIO5_F] = { + .mask = INT_MSK3_GPIO5_F_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_WTCHDG] = { + .mask = INT_MSK3_WTCHDG_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_VMBCH2_H] = { + .mask = INT_MSK3_VMBCH2_H_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_VMBCH2_L] = { + .mask = INT_MSK3_VMBCH2_L_IT_MSK_MASK, + .reg_offset = 2, + }, + [TPS65911_IRQ_PWRDN] = { + .mask = INT_MSK3_PWRDN_IT_MSK_MASK, + .reg_offset = 2, + }, +}; + +static const struct regmap_irq tps65910_irqs[] = { + /* INT_STS */ + [TPS65910_IRQ_VBAT_VMBDCH] = { + .mask = TPS65910_INT_MSK_VMBDCH_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_VBAT_VMHI] = { + .mask = TPS65910_INT_MSK_VMBHI_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_PWRON] = { + .mask = TPS65910_INT_MSK_PWRON_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_PWRON_LP] = { + .mask = TPS65910_INT_MSK_PWRON_LP_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_PWRHOLD] = { + .mask = TPS65910_INT_MSK_PWRHOLD_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_HOTDIE] = { + .mask = TPS65910_INT_MSK_HOTDIE_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_RTC_ALARM] = { + .mask = TPS65910_INT_MSK_RTC_ALARM_IT_MSK_MASK, + .reg_offset = 0, + }, + [TPS65910_IRQ_RTC_PERIOD] = { + .mask = TPS65910_INT_MSK_RTC_PERIOD_IT_MSK_MASK, + .reg_offset = 0, + }, + + /* INT_STS2 */ + [TPS65910_IRQ_GPIO_R] = { + .mask = TPS65910_INT_MSK2_GPIO0_F_IT_MSK_MASK, + .reg_offset = 1, + }, + [TPS65910_IRQ_GPIO_F] = { + .mask = TPS65910_INT_MSK2_GPIO0_R_IT_MSK_MASK, + .reg_offset = 1, + }, +}; + +static struct regmap_irq_chip tps65911_irq_chip = { + .name = "tps65910", + .irqs = tps65911_irqs, + .num_irqs = ARRAY_SIZE(tps65911_irqs), + .num_regs = 3, + .irq_reg_stride = 2, + .status_base = TPS65910_INT_STS, + .mask_base = TPS65910_INT_MSK, + .ack_base = TPS65910_INT_STS, +}; + +static struct regmap_irq_chip tps65910_irq_chip = { + .name = "tps65910", + .irqs = tps65910_irqs, + .num_irqs = ARRAY_SIZE(tps65910_irqs), + .num_regs = 2, + .irq_reg_stride = 2, + .status_base = TPS65910_INT_STS, + .mask_base = TPS65910_INT_MSK, + .ack_base = TPS65910_INT_STS, +}; + +static int tps65910_irq_init(struct tps65910 *tps65910, int irq, + struct tps65910_platform_data *pdata) +{ + int ret = 0; + static struct regmap_irq_chip *tps6591x_irqs_chip; + + if (!irq) { + dev_warn(tps65910->dev, "No interrupt support, no core IRQ\n"); + return -EINVAL; + } + + if (!pdata) { + dev_warn(tps65910->dev, "No interrupt support, no pdata\n"); + return -EINVAL; + } + + switch (tps65910_chip_id(tps65910)) { + case TPS65910: + tps6591x_irqs_chip = &tps65910_irq_chip; + break; + case TPS65911: + tps6591x_irqs_chip = &tps65911_irq_chip; + break; + } + + tps65910->chip_irq = irq; + ret = regmap_add_irq_chip(tps65910->regmap, tps65910->chip_irq, + IRQF_ONESHOT, pdata->irq_base, + tps6591x_irqs_chip, &tps65910->irq_data); + if (ret < 0) { + dev_warn(tps65910->dev, "Failed to add irq_chip %d\n", ret); + tps65910->chip_irq = 0; + } + return ret; +} + +static int tps65910_irq_exit(struct tps65910 *tps65910) +{ + if (tps65910->chip_irq > 0) + regmap_del_irq_chip(tps65910->chip_irq, tps65910->irq_data); + return 0; +} + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + struct tps65910 *tps65910 = dev_get_drvdata(dev); + + /* + * Caching all regulator registers. + * All regualator register address range is same for + * TPS65910 and TPS65911 + */ + if ((reg >= TPS65910_VIO) && (reg <= TPS65910_VDAC)) { + /* Check for non-existing register */ + if (tps65910_chip_id(tps65910) == TPS65910) + if ((reg == TPS65911_VDDCTRL_OP) || + (reg == TPS65911_VDDCTRL_SR)) + return true; + return false; + } + return true; +} + +static const struct regmap_config tps65910_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = is_volatile_reg, + .max_register = TPS65910_MAX_REGISTER - 1, + .cache_type = REGCACHE_RBTREE, +}; + +static int tps65910_ck32k_init(struct tps65910 *tps65910, + struct tps65910_board *pmic_pdata) +{ + int ret; + + if (!pmic_pdata->en_ck32k_xtal) + return 0; + + ret = tps65910_reg_clear_bits(tps65910, TPS65910_DEVCTRL, + DEVCTRL_CK32K_CTRL_MASK); + if (ret < 0) { + dev_err(tps65910->dev, "clear ck32k_ctrl failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int tps65910_sleepinit(struct tps65910 *tps65910, + struct tps65910_board *pmic_pdata) +{ + struct device *dev = NULL; + int ret = 0; + + dev = tps65910->dev; + + if (!pmic_pdata->en_dev_slp) + return 0; + + /* enabling SLEEP device state */ + ret = tps65910_reg_set_bits(tps65910, TPS65910_DEVCTRL, + DEVCTRL_DEV_SLP_MASK); + if (ret < 0) { + dev_err(dev, "set dev_slp failed: %d\n", ret); + goto err_sleep_init; + } + + /* Return if there is no sleep keepon data. */ + if (!pmic_pdata->slp_keepon) + return 0; + + if (pmic_pdata->slp_keepon->therm_keepon) { + ret = tps65910_reg_set_bits(tps65910, + TPS65910_SLEEP_KEEP_RES_ON, + SLEEP_KEEP_RES_ON_THERM_KEEPON_MASK); + if (ret < 0) { + dev_err(dev, "set therm_keepon failed: %d\n", ret); + goto disable_dev_slp; + } + } + + if (pmic_pdata->slp_keepon->clkout32k_keepon) { + ret = tps65910_reg_set_bits(tps65910, + TPS65910_SLEEP_KEEP_RES_ON, + SLEEP_KEEP_RES_ON_CLKOUT32K_KEEPON_MASK); + if (ret < 0) { + dev_err(dev, "set clkout32k_keepon failed: %d\n", ret); + goto disable_dev_slp; + } + } + + if (pmic_pdata->slp_keepon->i2chs_keepon) { + ret = tps65910_reg_set_bits(tps65910, + TPS65910_SLEEP_KEEP_RES_ON, + SLEEP_KEEP_RES_ON_I2CHS_KEEPON_MASK); + if (ret < 0) { + dev_err(dev, "set i2chs_keepon failed: %d\n", ret); + goto disable_dev_slp; + } + } + + return 0; + +disable_dev_slp: + tps65910_reg_clear_bits(tps65910, TPS65910_DEVCTRL, + DEVCTRL_DEV_SLP_MASK); + +err_sleep_init: + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id tps65910_of_match[] = { + { .compatible = "ti,tps65910", .data = (void *)TPS65910}, + { .compatible = "ti,tps65911", .data = (void *)TPS65911}, + { }, +}; +MODULE_DEVICE_TABLE(of, tps65910_of_match); + +static struct tps65910_board *tps65910_parse_dt(struct i2c_client *client, + unsigned long *chip_id) +{ + struct device_node *np = client->dev.of_node; + struct tps65910_board *board_info; + unsigned int prop; + const struct of_device_id *match; + int ret = 0; + + match = of_match_device(tps65910_of_match, &client->dev); + if (!match) { + dev_err(&client->dev, "Failed to find matching dt id\n"); + return NULL; + } + + *chip_id = (unsigned long)match->data; + + board_info = devm_kzalloc(&client->dev, sizeof(*board_info), + GFP_KERNEL); + if (!board_info) { + dev_err(&client->dev, "Failed to allocate pdata\n"); + return NULL; + } + + ret = of_property_read_u32(np, "ti,vmbch-threshold", &prop); + if (!ret) + board_info->vmbch_threshold = prop; + + ret = of_property_read_u32(np, "ti,vmbch2-threshold", &prop); + if (!ret) + board_info->vmbch2_threshold = prop; + + prop = of_property_read_bool(np, "ti,en-ck32k-xtal"); + board_info->en_ck32k_xtal = prop; + + board_info->irq = client->irq; + board_info->irq_base = -1; + board_info->pm_off = of_property_read_bool(np, + "ti,system-power-controller"); + + return board_info; +} +#else +static inline +struct tps65910_board *tps65910_parse_dt(struct i2c_client *client, + unsigned long *chip_id) +{ + return NULL; +} +#endif + +static struct i2c_client *tps65910_i2c_client; +static void tps65910_power_off(void) +{ + struct tps65910 *tps65910; + + tps65910 = dev_get_drvdata(&tps65910_i2c_client->dev); + + if (tps65910_reg_set_bits(tps65910, TPS65910_DEVCTRL, + DEVCTRL_PWR_OFF_MASK) < 0) + return; + + tps65910_reg_clear_bits(tps65910, TPS65910_DEVCTRL, + DEVCTRL_DEV_ON_MASK); +} + +static int tps65910_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tps65910 *tps65910; + struct tps65910_board *pmic_plat_data; + struct tps65910_board *of_pmic_plat_data = NULL; + struct tps65910_platform_data *init_data; + unsigned long chip_id = id->driver_data; + int ret = 0; + + pmic_plat_data = dev_get_platdata(&i2c->dev); + + if (!pmic_plat_data && i2c->dev.of_node) { + pmic_plat_data = tps65910_parse_dt(i2c, &chip_id); + of_pmic_plat_data = pmic_plat_data; + } + + if (!pmic_plat_data) + return -EINVAL; + + init_data = devm_kzalloc(&i2c->dev, sizeof(*init_data), GFP_KERNEL); + if (init_data == NULL) + return -ENOMEM; + + tps65910 = devm_kzalloc(&i2c->dev, sizeof(*tps65910), GFP_KERNEL); + if (tps65910 == NULL) + return -ENOMEM; + + tps65910->of_plat_data = of_pmic_plat_data; + i2c_set_clientdata(i2c, tps65910); + tps65910->dev = &i2c->dev; + tps65910->i2c_client = i2c; + tps65910->id = chip_id; + + /* Work around silicon erratum SWCZ010: the tps65910 may miss the + * first I2C transfer. So issue a dummy transfer before the first + * real transfer. + */ + i2c_master_send(i2c, "", 1); + tps65910->regmap = devm_regmap_init_i2c(i2c, &tps65910_regmap_config); + if (IS_ERR(tps65910->regmap)) { + ret = PTR_ERR(tps65910->regmap); + dev_err(&i2c->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + init_data->irq = pmic_plat_data->irq; + init_data->irq_base = pmic_plat_data->irq_base; + + tps65910_irq_init(tps65910, init_data->irq, init_data); + tps65910_ck32k_init(tps65910, pmic_plat_data); + tps65910_sleepinit(tps65910, pmic_plat_data); + + if (pmic_plat_data->pm_off && !pm_power_off) { + tps65910_i2c_client = i2c; + pm_power_off = tps65910_power_off; + } + + ret = mfd_add_devices(tps65910->dev, -1, + tps65910s, ARRAY_SIZE(tps65910s), + NULL, 0, + regmap_irq_get_domain(tps65910->irq_data)); + if (ret < 0) { + dev_err(&i2c->dev, "mfd_add_devices failed: %d\n", ret); + tps65910_irq_exit(tps65910); + return ret; + } + + return ret; +} + +static int tps65910_i2c_remove(struct i2c_client *i2c) +{ + struct tps65910 *tps65910 = i2c_get_clientdata(i2c); + + tps65910_irq_exit(tps65910); + mfd_remove_devices(tps65910->dev); + + return 0; +} + +static const struct i2c_device_id tps65910_i2c_id[] = { + { "tps65910", TPS65910 }, + { "tps65911", TPS65911 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65910_i2c_id); + + +static struct i2c_driver tps65910_i2c_driver = { + .driver = { + .name = "tps65910", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tps65910_of_match), + }, + .probe = tps65910_i2c_probe, + .remove = tps65910_i2c_remove, + .id_table = tps65910_i2c_id, +}; + +static int __init tps65910_i2c_init(void) +{ + return i2c_add_driver(&tps65910_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps65910_i2c_init); + +static void __exit tps65910_i2c_exit(void) +{ + i2c_del_driver(&tps65910_i2c_driver); +} +module_exit(tps65910_i2c_exit); + +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_AUTHOR("Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS6591x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/tps65911-comparator.c b/kernel/drivers/mfd/tps65911-comparator.c new file mode 100644 index 000000000..c0789f81a --- /dev/null +++ b/kernel/drivers/mfd/tps65911-comparator.c @@ -0,0 +1,189 @@ +/* + * tps65910.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Jorge Eduardo Candelaria <jedu@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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> + +#define COMP 0 +#define COMP1 1 +#define COMP2 2 + +/* Comparator 1 voltage selection table in millivolts */ +static const u16 COMP_VSEL_TABLE[] = { + 0, 2500, 2500, 2500, 2500, 2550, 2600, 2650, + 2700, 2750, 2800, 2850, 2900, 2950, 3000, 3050, + 3100, 3150, 3200, 3250, 3300, 3350, 3400, 3450, + 3500, +}; + +struct comparator { + const char *name; + int reg; + int uV_max; + const u16 *vsel_table; +}; + +static struct comparator tps_comparators[] = { + { + .name = "COMP1", + .reg = TPS65911_VMBCH, + .uV_max = 3500, + .vsel_table = COMP_VSEL_TABLE, + }, + { + .name = "COMP2", + .reg = TPS65911_VMBCH2, + .uV_max = 3500, + .vsel_table = COMP_VSEL_TABLE, + }, +}; + +static int comp_threshold_set(struct tps65910 *tps65910, int id, int voltage) +{ + struct comparator tps_comp = tps_comparators[id]; + int curr_voltage = 0; + int ret; + u8 index = 0, val; + + if (id == COMP) + return 0; + + while (curr_voltage < tps_comp.uV_max) { + curr_voltage = tps_comp.vsel_table[index]; + if (curr_voltage >= voltage) + break; + else if (curr_voltage < voltage) + index ++; + } + + if (curr_voltage > tps_comp.uV_max) + return -EINVAL; + + val = index << 1; + ret = tps65910->write(tps65910, tps_comp.reg, 1, &val); + + return ret; +} + +static int comp_threshold_get(struct tps65910 *tps65910, int id) +{ + struct comparator tps_comp = tps_comparators[id]; + int ret; + u8 val; + + if (id == COMP) + return 0; + + ret = tps65910->read(tps65910, tps_comp.reg, 1, &val); + if (ret < 0) + return ret; + + val >>= 1; + return tps_comp.vsel_table[val]; +} + +static ssize_t comp_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tps65910 *tps65910 = dev_get_drvdata(dev->parent); + struct attribute comp_attr = attr->attr; + int id, uVolt; + + if (!strcmp(comp_attr.name, "comp1_threshold")) + id = COMP1; + else if (!strcmp(comp_attr.name, "comp2_threshold")) + id = COMP2; + else + return -EINVAL; + + uVolt = comp_threshold_get(tps65910, id); + + return sprintf(buf, "%d\n", uVolt); +} + +static DEVICE_ATTR(comp1_threshold, S_IRUGO, comp_threshold_show, NULL); +static DEVICE_ATTR(comp2_threshold, S_IRUGO, comp_threshold_show, NULL); + +static int tps65911_comparator_probe(struct platform_device *pdev) +{ + struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); + struct tps65910_board *pdata = dev_get_platdata(tps65910->dev); + int ret; + + ret = comp_threshold_set(tps65910, COMP1, pdata->vmbch_threshold); + if (ret < 0) { + dev_err(&pdev->dev, "cannot set COMP1 threshold\n"); + return ret; + } + + ret = comp_threshold_set(tps65910, COMP2, pdata->vmbch2_threshold); + if (ret < 0) { + dev_err(&pdev->dev, "cannot set COMP2 threshold\n"); + return ret; + } + + /* Create sysfs entry */ + ret = device_create_file(&pdev->dev, &dev_attr_comp1_threshold); + if (ret < 0) + dev_err(&pdev->dev, "failed to add COMP1 sysfs file\n"); + + ret = device_create_file(&pdev->dev, &dev_attr_comp2_threshold); + if (ret < 0) + dev_err(&pdev->dev, "failed to add COMP2 sysfs file\n"); + + return ret; +} + +static 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; +} + +static struct platform_driver tps65911_comparator_driver = { + .driver = { + .name = "tps65911-comparator", + }, + .probe = tps65911_comparator_probe, + .remove = tps65911_comparator_remove, +}; + +static int __init tps65911_comparator_init(void) +{ + return platform_driver_register(&tps65911_comparator_driver); +} +subsys_initcall(tps65911_comparator_init); + +static void __exit tps65911_comparator_exit(void) +{ + platform_driver_unregister(&tps65911_comparator_driver); +} +module_exit(tps65911_comparator_exit); + +MODULE_AUTHOR("Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS65911 comparator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65911-comparator"); diff --git a/kernel/drivers/mfd/tps65912-core.c b/kernel/drivers/mfd/tps65912-core.c new file mode 100644 index 000000000..1f82d60b1 --- /dev/null +++ b/kernel/drivers/mfd/tps65912-core.c @@ -0,0 +1,175 @@ +/* + * 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/slab.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65912.h> + +static const 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 = dev_get_platdata(tps65912->dev); + 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; + + 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, NULL); + if (ret < 0) + goto err; + + init_data->irq = pmic_plat_data->irq; + init_data->irq_base = pmic_plat_data->irq_base; + ret = tps65912_irq_init(tps65912, init_data->irq, init_data); + if (ret < 0) + goto err; + + kfree(init_data); + return ret; + +err: + kfree(init_data); + mfd_remove_devices(tps65912->dev); + return ret; +} + +void tps65912_device_exit(struct tps65912 *tps65912) +{ + mfd_remove_devices(tps65912->dev); + tps65912_irq_exit(tps65912); +} + +MODULE_AUTHOR("Margarita Olaya <magi@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS65912x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/tps65912-i2c.c b/kernel/drivers/mfd/tps65912-i2c.c new file mode 100644 index 000000000..6a6343ee9 --- /dev/null +++ b/kernel/drivers/mfd/tps65912-i2c.c @@ -0,0 +1,140 @@ +/* + * 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 = devm_kzalloc(&i2c->dev, + 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/kernel/drivers/mfd/tps65912-irq.c b/kernel/drivers/mfd/tps65912-irq.c new file mode 100644 index 000000000..fbecec7f1 --- /dev/null +++ b/kernel/drivers/mfd/tps65912-irq.c @@ -0,0 +1,223 @@ +/* + * 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/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/kernel/drivers/mfd/tps65912-spi.c b/kernel/drivers/mfd/tps65912-spi.c new file mode 100644 index 000000000..de60ad98b --- /dev/null +++ b/kernel/drivers/mfd/tps65912-spi.c @@ -0,0 +1,141 @@ +/* + * 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; + + tx_buf = spi_data; + + 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 tps65912_spi_probe(struct spi_device *spi) +{ + struct tps65912 *tps65912; + + tps65912 = devm_kzalloc(&spi->dev, + 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 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", + .owner = THIS_MODULE, + }, + .probe = tps65912_spi_probe, + .remove = 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/kernel/drivers/mfd/tps80031.c b/kernel/drivers/mfd/tps80031.c new file mode 100644 index 000000000..ed6c5b095 --- /dev/null +++ b/kernel/drivers/mfd/tps80031.c @@ -0,0 +1,573 @@ +/* + * tps80031.c -- TI TPS80031/TPS80032 mfd core driver. + * + * MFD core driver for TI TPS80031/TPS80032 Fully Integrated + * Power Management with Power Path and Battery Charger + * + * Copyright (c) 2012, NVIDIA Corporation. + * + * Author: Laxman Dewangan <ldewangan@nvidia.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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps80031.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +static struct resource tps80031_rtc_resources[] = { + { + .start = TPS80031_INT_RTC_ALARM, + .end = TPS80031_INT_RTC_ALARM, + .flags = IORESOURCE_IRQ, + }, +}; + +/* TPS80031 sub mfd devices */ +static const struct mfd_cell tps80031_cell[] = { + { + .name = "tps80031-pmic", + }, + { + .name = "tps80031-clock", + }, + { + .name = "tps80031-rtc", + .num_resources = ARRAY_SIZE(tps80031_rtc_resources), + .resources = tps80031_rtc_resources, + }, + { + .name = "tps80031-gpadc", + }, + { + .name = "tps80031-fuel-gauge", + }, + { + .name = "tps80031-charger", + }, +}; + +static int tps80031_slave_address[TPS80031_NUM_SLAVES] = { + TPS80031_I2C_ID0_ADDR, + TPS80031_I2C_ID1_ADDR, + TPS80031_I2C_ID2_ADDR, + TPS80031_I2C_ID3_ADDR, +}; + +struct tps80031_pupd_data { + u8 reg; + u8 pullup_bit; + u8 pulldown_bit; +}; + +#define TPS80031_IRQ(_reg, _mask) \ + { \ + .reg_offset = (TPS80031_INT_MSK_LINE_##_reg) - \ + TPS80031_INT_MSK_LINE_A, \ + .mask = BIT(_mask), \ + } + +static const struct regmap_irq tps80031_main_irqs[] = { + [TPS80031_INT_PWRON] = TPS80031_IRQ(A, 0), + [TPS80031_INT_RPWRON] = TPS80031_IRQ(A, 1), + [TPS80031_INT_SYS_VLOW] = TPS80031_IRQ(A, 2), + [TPS80031_INT_RTC_ALARM] = TPS80031_IRQ(A, 3), + [TPS80031_INT_RTC_PERIOD] = TPS80031_IRQ(A, 4), + [TPS80031_INT_HOT_DIE] = TPS80031_IRQ(A, 5), + [TPS80031_INT_VXX_SHORT] = TPS80031_IRQ(A, 6), + [TPS80031_INT_SPDURATION] = TPS80031_IRQ(A, 7), + [TPS80031_INT_WATCHDOG] = TPS80031_IRQ(B, 0), + [TPS80031_INT_BAT] = TPS80031_IRQ(B, 1), + [TPS80031_INT_SIM] = TPS80031_IRQ(B, 2), + [TPS80031_INT_MMC] = TPS80031_IRQ(B, 3), + [TPS80031_INT_RES] = TPS80031_IRQ(B, 4), + [TPS80031_INT_GPADC_RT] = TPS80031_IRQ(B, 5), + [TPS80031_INT_GPADC_SW2_EOC] = TPS80031_IRQ(B, 6), + [TPS80031_INT_CC_AUTOCAL] = TPS80031_IRQ(B, 7), + [TPS80031_INT_ID_WKUP] = TPS80031_IRQ(C, 0), + [TPS80031_INT_VBUSS_WKUP] = TPS80031_IRQ(C, 1), + [TPS80031_INT_ID] = TPS80031_IRQ(C, 2), + [TPS80031_INT_VBUS] = TPS80031_IRQ(C, 3), + [TPS80031_INT_CHRG_CTRL] = TPS80031_IRQ(C, 4), + [TPS80031_INT_EXT_CHRG] = TPS80031_IRQ(C, 5), + [TPS80031_INT_INT_CHRG] = TPS80031_IRQ(C, 6), + [TPS80031_INT_RES2] = TPS80031_IRQ(C, 7), +}; + +static struct regmap_irq_chip tps80031_irq_chip = { + .name = "tps80031", + .irqs = tps80031_main_irqs, + .num_irqs = ARRAY_SIZE(tps80031_main_irqs), + .num_regs = 3, + .status_base = TPS80031_INT_STS_A, + .mask_base = TPS80031_INT_MSK_LINE_A, +}; + +#define PUPD_DATA(_reg, _pulldown_bit, _pullup_bit) \ + { \ + .reg = TPS80031_CFG_INPUT_PUPD##_reg, \ + .pulldown_bit = _pulldown_bit, \ + .pullup_bit = _pullup_bit, \ + } + +static const struct tps80031_pupd_data tps80031_pupds[] = { + [TPS80031_PREQ1] = PUPD_DATA(1, BIT(0), BIT(1)), + [TPS80031_PREQ2A] = PUPD_DATA(1, BIT(2), BIT(3)), + [TPS80031_PREQ2B] = PUPD_DATA(1, BIT(4), BIT(5)), + [TPS80031_PREQ2C] = PUPD_DATA(1, BIT(6), BIT(7)), + [TPS80031_PREQ3] = PUPD_DATA(2, BIT(0), BIT(1)), + [TPS80031_NRES_WARM] = PUPD_DATA(2, 0, BIT(2)), + [TPS80031_PWM_FORCE] = PUPD_DATA(2, BIT(5), 0), + [TPS80031_CHRG_EXT_CHRG_STATZ] = PUPD_DATA(2, 0, BIT(6)), + [TPS80031_SIM] = PUPD_DATA(3, BIT(0), BIT(1)), + [TPS80031_MMC] = PUPD_DATA(3, BIT(2), BIT(3)), + [TPS80031_GPADC_START] = PUPD_DATA(3, BIT(4), 0), + [TPS80031_DVSI2C_SCL] = PUPD_DATA(4, 0, BIT(0)), + [TPS80031_DVSI2C_SDA] = PUPD_DATA(4, 0, BIT(1)), + [TPS80031_CTLI2C_SCL] = PUPD_DATA(4, 0, BIT(2)), + [TPS80031_CTLI2C_SDA] = PUPD_DATA(4, 0, BIT(3)), +}; +static struct tps80031 *tps80031_power_off_dev; + +int tps80031_ext_power_req_config(struct device *dev, + unsigned long ext_ctrl_flag, int preq_bit, + int state_reg_add, int trans_reg_add) +{ + u8 res_ass_reg = 0; + int preq_mask_bit = 0; + int ret; + + if (!(ext_ctrl_flag & TPS80031_EXT_PWR_REQ)) + return 0; + + if (ext_ctrl_flag & TPS80031_PWR_REQ_INPUT_PREQ1) { + res_ass_reg = TPS80031_PREQ1_RES_ASS_A + (preq_bit >> 3); + preq_mask_bit = 5; + } else if (ext_ctrl_flag & TPS80031_PWR_REQ_INPUT_PREQ2) { + res_ass_reg = TPS80031_PREQ2_RES_ASS_A + (preq_bit >> 3); + preq_mask_bit = 6; + } else if (ext_ctrl_flag & TPS80031_PWR_REQ_INPUT_PREQ3) { + res_ass_reg = TPS80031_PREQ3_RES_ASS_A + (preq_bit >> 3); + preq_mask_bit = 7; + } + + /* Configure REQ_ASS registers */ + ret = tps80031_set_bits(dev, TPS80031_SLAVE_ID1, res_ass_reg, + BIT(preq_bit & 0x7)); + if (ret < 0) { + dev_err(dev, "reg 0x%02x setbit failed, err = %d\n", + res_ass_reg, ret); + return ret; + } + + /* Unmask the PREQ */ + ret = tps80031_clr_bits(dev, TPS80031_SLAVE_ID1, + TPS80031_PHOENIX_MSK_TRANSITION, BIT(preq_mask_bit)); + if (ret < 0) { + dev_err(dev, "reg 0x%02x clrbit failed, err = %d\n", + TPS80031_PHOENIX_MSK_TRANSITION, ret); + return ret; + } + + /* Switch regulator control to resource now */ + if (ext_ctrl_flag & (TPS80031_PWR_REQ_INPUT_PREQ2 | + TPS80031_PWR_REQ_INPUT_PREQ3)) { + ret = tps80031_update(dev, TPS80031_SLAVE_ID1, state_reg_add, + 0x0, TPS80031_STATE_MASK); + if (ret < 0) + dev_err(dev, "reg 0x%02x update failed, err = %d\n", + state_reg_add, ret); + } else { + ret = tps80031_update(dev, TPS80031_SLAVE_ID1, trans_reg_add, + TPS80031_TRANS_SLEEP_OFF, + TPS80031_TRANS_SLEEP_MASK); + if (ret < 0) + dev_err(dev, "reg 0x%02x update failed, err = %d\n", + trans_reg_add, ret); + } + return ret; +} +EXPORT_SYMBOL_GPL(tps80031_ext_power_req_config); + +static void tps80031_power_off(void) +{ + dev_info(tps80031_power_off_dev->dev, "switching off PMU\n"); + tps80031_write(tps80031_power_off_dev->dev, TPS80031_SLAVE_ID1, + TPS80031_PHOENIX_DEV_ON, TPS80031_DEVOFF); +} + +static void tps80031_pupd_init(struct tps80031 *tps80031, + struct tps80031_platform_data *pdata) +{ + struct tps80031_pupd_init_data *pupd_init_data = pdata->pupd_init_data; + int data_size = pdata->pupd_init_data_size; + int i; + + for (i = 0; i < data_size; ++i) { + struct tps80031_pupd_init_data *pupd_init = &pupd_init_data[i]; + const struct tps80031_pupd_data *pupd = + &tps80031_pupds[pupd_init->input_pin]; + u8 update_value = 0; + u8 update_mask = pupd->pulldown_bit | pupd->pullup_bit; + + if (pupd_init->setting == TPS80031_PUPD_PULLDOWN) + update_value = pupd->pulldown_bit; + else if (pupd_init->setting == TPS80031_PUPD_PULLUP) + update_value = pupd->pullup_bit; + + tps80031_update(tps80031->dev, TPS80031_SLAVE_ID1, pupd->reg, + update_value, update_mask); + } +} + +static int tps80031_init_ext_control(struct tps80031 *tps80031, + struct tps80031_platform_data *pdata) +{ + struct device *dev = tps80031->dev; + int ret; + int i; + + /* Clear all external control for this rail */ + for (i = 0; i < 9; ++i) { + ret = tps80031_write(dev, TPS80031_SLAVE_ID1, + TPS80031_PREQ1_RES_ASS_A + i, 0); + if (ret < 0) { + dev_err(dev, "reg 0x%02x write failed, err = %d\n", + TPS80031_PREQ1_RES_ASS_A + i, ret); + return ret; + } + } + + /* Mask the PREQ */ + ret = tps80031_set_bits(dev, TPS80031_SLAVE_ID1, + TPS80031_PHOENIX_MSK_TRANSITION, 0x7 << 5); + if (ret < 0) { + dev_err(dev, "reg 0x%02x set_bits failed, err = %d\n", + TPS80031_PHOENIX_MSK_TRANSITION, ret); + return ret; + } + return ret; +} + +static int tps80031_irq_init(struct tps80031 *tps80031, int irq, int irq_base) +{ + struct device *dev = tps80031->dev; + int i, ret; + + /* + * The MASK register used for updating status register when + * interrupt occurs and LINE register used to pass the status + * to actual interrupt line. As per datasheet: + * When INT_MSK_LINE [i] is set to 1, the associated interrupt + * number i is INT line masked, which means that no interrupt is + * generated on the INT line. + * When INT_MSK_LINE [i] is set to 0, the associated interrupt + * number i is line enabled: An interrupt is generated on the + * INT line. + * In any case, the INT_STS [i] status bit may or may not be updated, + * only linked to the INT_MSK_STS [i] configuration register bit. + * + * When INT_MSK_STS [i] is set to 1, the associated interrupt number + * i is status masked, which means that no interrupt is stored in + * the INT_STS[i] status bit. Note that no interrupt number i is + * generated on the INT line, even if the INT_MSK_LINE [i] register + * bit is set to 0. + * When INT_MSK_STS [i] is set to 0, the associated interrupt number i + * is status enabled: An interrupt status is updated in the INT_STS [i] + * register. The interrupt may or may not be generated on the INT line, + * depending on the INT_MSK_LINE [i] configuration register bit. + */ + for (i = 0; i < 3; i++) + tps80031_write(dev, TPS80031_SLAVE_ID2, + TPS80031_INT_MSK_STS_A + i, 0x00); + + ret = regmap_add_irq_chip(tps80031->regmap[TPS80031_SLAVE_ID2], irq, + IRQF_ONESHOT, irq_base, + &tps80031_irq_chip, &tps80031->irq_data); + if (ret < 0) { + dev_err(dev, "add irq failed, err = %d\n", ret); + return ret; + } + return ret; +} + +static bool rd_wr_reg_id0(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS80031_SMPS1_CFG_FORCE ... TPS80031_SMPS2_CFG_VOLTAGE: + return true; + default: + return false; + } +} + +static bool rd_wr_reg_id1(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS80031_SECONDS_REG ... TPS80031_RTC_RESET_STATUS_REG: + case TPS80031_VALIDITY0 ... TPS80031_VALIDITY7: + case TPS80031_PHOENIX_START_CONDITION ... TPS80031_KEY_PRESS_DUR_CFG: + case TPS80031_SMPS4_CFG_TRANS ... TPS80031_SMPS3_CFG_VOLTAGE: + case TPS80031_BROADCAST_ADDR_ALL ... TPS80031_BROADCAST_ADDR_CLK_RST: + case TPS80031_VANA_CFG_TRANS ... TPS80031_LDO7_CFG_VOLTAGE: + case TPS80031_REGEN1_CFG_TRANS ... TPS80031_TMP_CFG_STATE: + case TPS80031_PREQ1_RES_ASS_A ... TPS80031_PREQ3_RES_ASS_C: + case TPS80031_SMPS_OFFSET ... TPS80031_BATDEBOUNCING: + case TPS80031_CFG_INPUT_PUPD1 ... TPS80031_CFG_SMPS_PD: + case TPS80031_BACKUP_REG: + return true; + default: + return false; + } +} + +static bool is_volatile_reg_id1(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS80031_SMPS4_CFG_TRANS ... TPS80031_SMPS3_CFG_VOLTAGE: + case TPS80031_VANA_CFG_TRANS ... TPS80031_LDO7_CFG_VOLTAGE: + case TPS80031_REGEN1_CFG_TRANS ... TPS80031_TMP_CFG_STATE: + case TPS80031_PREQ1_RES_ASS_A ... TPS80031_PREQ3_RES_ASS_C: + case TPS80031_SMPS_OFFSET ... TPS80031_BATDEBOUNCING: + case TPS80031_CFG_INPUT_PUPD1 ... TPS80031_CFG_SMPS_PD: + return true; + default: + return false; + } +} + +static bool rd_wr_reg_id2(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS80031_USB_VENDOR_ID_LSB ... TPS80031_USB_OTG_REVISION: + case TPS80031_GPADC_CTRL ... TPS80031_CTRL_P1: + case TPS80031_RTCH0_LSB ... TPS80031_GPCH0_MSB: + case TPS80031_TOGGLE1 ... TPS80031_VIBMODE: + case TPS80031_PWM1ON ... TPS80031_PWM2OFF: + case TPS80031_FG_REG_00 ... TPS80031_FG_REG_11: + case TPS80031_INT_STS_A ... TPS80031_INT_MSK_STS_C: + case TPS80031_CONTROLLER_CTRL2 ... TPS80031_LED_PWM_CTRL2: + return true; + default: + return false; + } +} + +static bool rd_wr_reg_id3(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS80031_GPADC_TRIM0 ... TPS80031_GPADC_TRIM18: + return true; + default: + return false; + } +} + +static const struct regmap_config tps80031_regmap_configs[] = { + { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rd_wr_reg_id0, + .readable_reg = rd_wr_reg_id0, + .max_register = TPS80031_MAX_REGISTER, + }, + { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rd_wr_reg_id1, + .readable_reg = rd_wr_reg_id1, + .volatile_reg = is_volatile_reg_id1, + .max_register = TPS80031_MAX_REGISTER, + }, + { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rd_wr_reg_id2, + .readable_reg = rd_wr_reg_id2, + .max_register = TPS80031_MAX_REGISTER, + }, + { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rd_wr_reg_id3, + .readable_reg = rd_wr_reg_id3, + .max_register = TPS80031_MAX_REGISTER, + }, +}; + +static int tps80031_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tps80031_platform_data *pdata = dev_get_platdata(&client->dev); + struct tps80031 *tps80031; + int ret; + uint8_t es_version; + uint8_t ep_ver; + int i; + + if (!pdata) { + dev_err(&client->dev, "tps80031 requires platform data\n"); + return -EINVAL; + } + + tps80031 = devm_kzalloc(&client->dev, sizeof(*tps80031), GFP_KERNEL); + if (!tps80031) { + dev_err(&client->dev, "Malloc failed for tps80031\n"); + return -ENOMEM; + } + + for (i = 0; i < TPS80031_NUM_SLAVES; i++) { + if (tps80031_slave_address[i] == client->addr) + tps80031->clients[i] = client; + else + tps80031->clients[i] = i2c_new_dummy(client->adapter, + tps80031_slave_address[i]); + if (!tps80031->clients[i]) { + dev_err(&client->dev, "can't attach client %d\n", i); + ret = -ENOMEM; + goto fail_client_reg; + } + + i2c_set_clientdata(tps80031->clients[i], tps80031); + tps80031->regmap[i] = devm_regmap_init_i2c(tps80031->clients[i], + &tps80031_regmap_configs[i]); + if (IS_ERR(tps80031->regmap[i])) { + ret = PTR_ERR(tps80031->regmap[i]); + dev_err(&client->dev, + "regmap %d init failed, err %d\n", i, ret); + goto fail_client_reg; + } + } + + ret = tps80031_read(&client->dev, TPS80031_SLAVE_ID3, + TPS80031_JTAGVERNUM, &es_version); + if (ret < 0) { + dev_err(&client->dev, + "Silicon version number read failed: %d\n", ret); + goto fail_client_reg; + } + + ret = tps80031_read(&client->dev, TPS80031_SLAVE_ID3, + TPS80031_EPROM_REV, &ep_ver); + if (ret < 0) { + dev_err(&client->dev, + "Silicon eeprom version read failed: %d\n", ret); + goto fail_client_reg; + } + + dev_info(&client->dev, "ES version 0x%02x and EPROM version 0x%02x\n", + es_version, ep_ver); + tps80031->es_version = es_version; + tps80031->dev = &client->dev; + i2c_set_clientdata(client, tps80031); + tps80031->chip_info = id->driver_data; + + ret = tps80031_irq_init(tps80031, client->irq, pdata->irq_base); + if (ret) { + dev_err(&client->dev, "IRQ init failed: %d\n", ret); + goto fail_client_reg; + } + + tps80031_pupd_init(tps80031, pdata); + + tps80031_init_ext_control(tps80031, pdata); + + ret = mfd_add_devices(tps80031->dev, -1, + tps80031_cell, ARRAY_SIZE(tps80031_cell), + NULL, 0, + regmap_irq_get_domain(tps80031->irq_data)); + if (ret < 0) { + dev_err(&client->dev, "mfd_add_devices failed: %d\n", ret); + goto fail_mfd_add; + } + + if (pdata->use_power_off && !pm_power_off) { + tps80031_power_off_dev = tps80031; + pm_power_off = tps80031_power_off; + } + return 0; + +fail_mfd_add: + regmap_del_irq_chip(client->irq, tps80031->irq_data); + +fail_client_reg: + for (i = 0; i < TPS80031_NUM_SLAVES; i++) { + if (tps80031->clients[i] && (tps80031->clients[i] != client)) + i2c_unregister_device(tps80031->clients[i]); + } + return ret; +} + +static int tps80031_remove(struct i2c_client *client) +{ + struct tps80031 *tps80031 = i2c_get_clientdata(client); + int i; + + if (tps80031_power_off_dev == tps80031) { + tps80031_power_off_dev = NULL; + pm_power_off = NULL; + } + + mfd_remove_devices(tps80031->dev); + + regmap_del_irq_chip(client->irq, tps80031->irq_data); + + for (i = 0; i < TPS80031_NUM_SLAVES; i++) { + if (tps80031->clients[i] != client) + i2c_unregister_device(tps80031->clients[i]); + } + return 0; +} + +static const struct i2c_device_id tps80031_id_table[] = { + { "tps80031", TPS80031 }, + { "tps80032", TPS80032 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps80031_id_table); + +static struct i2c_driver tps80031_driver = { + .driver = { + .name = "tps80031", + .owner = THIS_MODULE, + }, + .probe = tps80031_probe, + .remove = tps80031_remove, + .id_table = tps80031_id_table, +}; + +static int __init tps80031_init(void) +{ + return i2c_add_driver(&tps80031_driver); +} +subsys_initcall(tps80031_init); + +static void __exit tps80031_exit(void) +{ + i2c_del_driver(&tps80031_driver); +} +module_exit(tps80031_exit); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("TPS80031 core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/mfd/twl-core.c b/kernel/drivers/mfd/twl-core.c new file mode 100644 index 000000000..489674a24 --- /dev/null +++ b/kernel/drivers/mfd/twl-core.c @@ -0,0 +1,1278 @@ +/* + * twl_core.c - driver for TWL4030/TWL5030/TWL60X0/TPS659x0 PM + * and audio CODEC devices + * + * Copyright (C) 2005-2006 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim <x0khasim@ti.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> + +#include <linux/regulator/machine.h> + +#include <linux/i2c.h> +#include <linux/i2c/twl.h> + +/* Register descriptions for audio */ +#include <linux/mfd/twl4030-audio.h> + +#include "twl-core.h" + +/* + * The TWL4030 "Triton 2" is one of a family of a multi-function "Power + * Management and System Companion Device" chips originally designed for + * use in OMAP2 and OMAP 3 based systems. Its control interfaces use I2C, + * often at around 3 Mbit/sec, including for interrupt handling. + * + * This driver core provides genirq support for the interrupts emitted, + * by the various modules, and exports register access primitives. + * + * FIXME this driver currently requires use of the first interrupt line + * (and associated registers). + */ + +#define DRIVER_NAME "twl" + +/* Triton Core internal information (BEGIN) */ + +/* Base Address defns for twl4030_map[] */ + +/* subchip/slave 0 - USB ID */ +#define TWL4030_BASEADD_USB 0x0000 + +/* subchip/slave 1 - AUD ID */ +#define TWL4030_BASEADD_AUDIO_VOICE 0x0000 +#define TWL4030_BASEADD_GPIO 0x0098 +#define TWL4030_BASEADD_INTBR 0x0085 +#define TWL4030_BASEADD_PIH 0x0080 +#define TWL4030_BASEADD_TEST 0x004C + +/* subchip/slave 2 - AUX ID */ +#define TWL4030_BASEADD_INTERRUPTS 0x00B9 +#define TWL4030_BASEADD_LED 0x00EE +#define TWL4030_BASEADD_MADC 0x0000 +#define TWL4030_BASEADD_MAIN_CHARGE 0x0074 +#define TWL4030_BASEADD_PRECHARGE 0x00AA +#define TWL4030_BASEADD_PWM 0x00F8 +#define TWL4030_BASEADD_KEYPAD 0x00D2 + +#define TWL5031_BASEADD_ACCESSORY 0x0074 /* Replaces Main Charge */ +#define TWL5031_BASEADD_INTERRUPTS 0x00B9 /* Different than TWL4030's + one */ + +/* subchip/slave 3 - POWER ID */ +#define TWL4030_BASEADD_BACKUP 0x0014 +#define TWL4030_BASEADD_INT 0x002E +#define TWL4030_BASEADD_PM_MASTER 0x0036 + +#define TWL4030_BASEADD_PM_RECEIVER 0x005B +#define TWL4030_DCDC_GLOBAL_CFG 0x06 +#define SMARTREFLEX_ENABLE BIT(3) + +#define TWL4030_BASEADD_RTC 0x001C +#define TWL4030_BASEADD_SECURED_REG 0x0000 + +/* Triton Core internal information (END) */ + + +/* subchip/slave 0 0x48 - POWER */ +#define TWL6030_BASEADD_RTC 0x0000 +#define TWL6030_BASEADD_SECURED_REG 0x0017 +#define TWL6030_BASEADD_PM_MASTER 0x001F +#define TWL6030_BASEADD_PM_SLAVE_MISC 0x0030 /* PM_RECEIVER */ +#define TWL6030_BASEADD_PM_MISC 0x00E2 +#define TWL6030_BASEADD_PM_PUPD 0x00F0 + +/* subchip/slave 1 0x49 - FEATURE */ +#define TWL6030_BASEADD_USB 0x0000 +#define TWL6030_BASEADD_GPADC_CTRL 0x002E +#define TWL6030_BASEADD_AUX 0x0090 +#define TWL6030_BASEADD_PWM 0x00BA +#define TWL6030_BASEADD_GASGAUGE 0x00C0 +#define TWL6030_BASEADD_PIH 0x00D0 +#define TWL6030_BASEADD_CHARGER 0x00E0 +#define TWL6032_BASEADD_CHARGER 0x00DA +#define TWL6030_BASEADD_LED 0x00F4 + +/* subchip/slave 2 0x4A - DFT */ +#define TWL6030_BASEADD_DIEID 0x00C0 + +/* subchip/slave 3 0x4B - AUDIO */ +#define TWL6030_BASEADD_AUDIO 0x0000 +#define TWL6030_BASEADD_RSV 0x0000 +#define TWL6030_BASEADD_ZERO 0x0000 + +/* Few power values */ +#define R_CFG_BOOT 0x05 + +/* some fields in R_CFG_BOOT */ +#define HFCLK_FREQ_19p2_MHZ (1 << 0) +#define HFCLK_FREQ_26_MHZ (2 << 0) +#define HFCLK_FREQ_38p4_MHZ (3 << 0) +#define HIGH_PERF_SQ (1 << 3) +#define CK32K_LOWPWR_EN (1 << 7) + +/*----------------------------------------------------------------------*/ + +/* Structure for each TWL4030/TWL6030 Slave */ +struct twl_client { + struct i2c_client *client; + struct regmap *regmap; +}; + +/* mapping the module id to slave id and base address */ +struct twl_mapping { + unsigned char sid; /* Slave ID */ + unsigned char base; /* base address */ +}; + +struct twl_private { + bool ready; /* The core driver is ready to be used */ + u32 twl_idcode; /* TWL IDCODE Register value */ + unsigned int twl_id; + + struct twl_mapping *twl_map; + struct twl_client *twl_modules; +}; + +static struct twl_private *twl_priv; + +static struct twl_mapping twl4030_map[] = { + /* + * NOTE: don't change this table without updating the + * <linux/i2c/twl.h> defines for TWL4030_MODULE_* + * so they continue to match the order in this table. + */ + + /* Common IPs */ + { 0, TWL4030_BASEADD_USB }, + { 1, TWL4030_BASEADD_PIH }, + { 2, TWL4030_BASEADD_MAIN_CHARGE }, + { 3, TWL4030_BASEADD_PM_MASTER }, + { 3, TWL4030_BASEADD_PM_RECEIVER }, + + { 3, TWL4030_BASEADD_RTC }, + { 2, TWL4030_BASEADD_PWM }, + { 2, TWL4030_BASEADD_LED }, + { 3, TWL4030_BASEADD_SECURED_REG }, + + /* TWL4030 specific IPs */ + { 1, TWL4030_BASEADD_AUDIO_VOICE }, + { 1, TWL4030_BASEADD_GPIO }, + { 1, TWL4030_BASEADD_INTBR }, + { 1, TWL4030_BASEADD_TEST }, + { 2, TWL4030_BASEADD_KEYPAD }, + + { 2, TWL4030_BASEADD_MADC }, + { 2, TWL4030_BASEADD_INTERRUPTS }, + { 2, TWL4030_BASEADD_PRECHARGE }, + { 3, TWL4030_BASEADD_BACKUP }, + { 3, TWL4030_BASEADD_INT }, + + { 2, TWL5031_BASEADD_ACCESSORY }, + { 2, TWL5031_BASEADD_INTERRUPTS }, +}; + +static const struct reg_default twl4030_49_defaults[] = { + /* Audio Registers */ + { 0x01, 0x00}, /* CODEC_MODE */ + { 0x02, 0x00}, /* OPTION */ + /* 0x03 Unused */ + { 0x04, 0x00}, /* MICBIAS_CTL */ + { 0x05, 0x00}, /* ANAMICL */ + { 0x06, 0x00}, /* ANAMICR */ + { 0x07, 0x00}, /* AVADC_CTL */ + { 0x08, 0x00}, /* ADCMICSEL */ + { 0x09, 0x00}, /* DIGMIXING */ + { 0x0a, 0x0f}, /* ATXL1PGA */ + { 0x0b, 0x0f}, /* ATXR1PGA */ + { 0x0c, 0x0f}, /* AVTXL2PGA */ + { 0x0d, 0x0f}, /* AVTXR2PGA */ + { 0x0e, 0x00}, /* AUDIO_IF */ + { 0x0f, 0x00}, /* VOICE_IF */ + { 0x10, 0x3f}, /* ARXR1PGA */ + { 0x11, 0x3f}, /* ARXL1PGA */ + { 0x12, 0x3f}, /* ARXR2PGA */ + { 0x13, 0x3f}, /* ARXL2PGA */ + { 0x14, 0x25}, /* VRXPGA */ + { 0x15, 0x00}, /* VSTPGA */ + { 0x16, 0x00}, /* VRX2ARXPGA */ + { 0x17, 0x00}, /* AVDAC_CTL */ + { 0x18, 0x00}, /* ARX2VTXPGA */ + { 0x19, 0x32}, /* ARXL1_APGA_CTL*/ + { 0x1a, 0x32}, /* ARXR1_APGA_CTL*/ + { 0x1b, 0x32}, /* ARXL2_APGA_CTL*/ + { 0x1c, 0x32}, /* ARXR2_APGA_CTL*/ + { 0x1d, 0x00}, /* ATX2ARXPGA */ + { 0x1e, 0x00}, /* BT_IF */ + { 0x1f, 0x55}, /* BTPGA */ + { 0x20, 0x00}, /* BTSTPGA */ + { 0x21, 0x00}, /* EAR_CTL */ + { 0x22, 0x00}, /* HS_SEL */ + { 0x23, 0x00}, /* HS_GAIN_SET */ + { 0x24, 0x00}, /* HS_POPN_SET */ + { 0x25, 0x00}, /* PREDL_CTL */ + { 0x26, 0x00}, /* PREDR_CTL */ + { 0x27, 0x00}, /* PRECKL_CTL */ + { 0x28, 0x00}, /* PRECKR_CTL */ + { 0x29, 0x00}, /* HFL_CTL */ + { 0x2a, 0x00}, /* HFR_CTL */ + { 0x2b, 0x05}, /* ALC_CTL */ + { 0x2c, 0x00}, /* ALC_SET1 */ + { 0x2d, 0x00}, /* ALC_SET2 */ + { 0x2e, 0x00}, /* BOOST_CTL */ + { 0x2f, 0x00}, /* SOFTVOL_CTL */ + { 0x30, 0x13}, /* DTMF_FREQSEL */ + { 0x31, 0x00}, /* DTMF_TONEXT1H */ + { 0x32, 0x00}, /* DTMF_TONEXT1L */ + { 0x33, 0x00}, /* DTMF_TONEXT2H */ + { 0x34, 0x00}, /* DTMF_TONEXT2L */ + { 0x35, 0x79}, /* DTMF_TONOFF */ + { 0x36, 0x11}, /* DTMF_WANONOFF */ + { 0x37, 0x00}, /* I2S_RX_SCRAMBLE_H */ + { 0x38, 0x00}, /* I2S_RX_SCRAMBLE_M */ + { 0x39, 0x00}, /* I2S_RX_SCRAMBLE_L */ + { 0x3a, 0x06}, /* APLL_CTL */ + { 0x3b, 0x00}, /* DTMF_CTL */ + { 0x3c, 0x44}, /* DTMF_PGA_CTL2 (0x3C) */ + { 0x3d, 0x69}, /* DTMF_PGA_CTL1 (0x3D) */ + { 0x3e, 0x00}, /* MISC_SET_1 */ + { 0x3f, 0x00}, /* PCMBTMUX */ + /* 0x40 - 0x42 Unused */ + { 0x43, 0x00}, /* RX_PATH_SEL */ + { 0x44, 0x32}, /* VDL_APGA_CTL */ + { 0x45, 0x00}, /* VIBRA_CTL */ + { 0x46, 0x00}, /* VIBRA_SET */ + { 0x47, 0x00}, /* VIBRA_PWM_SET */ + { 0x48, 0x00}, /* ANAMIC_GAIN */ + { 0x49, 0x00}, /* MISC_SET_2 */ + /* End of Audio Registers */ +}; + +static bool twl4030_49_nop_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00: + case 0x03: + case 0x40: + case 0x41: + case 0x42: + return false; + default: + return true; + } +} + +static const struct regmap_range twl4030_49_volatile_ranges[] = { + regmap_reg_range(TWL4030_BASEADD_TEST, 0xff), +}; + +static const struct regmap_access_table twl4030_49_volatile_table = { + .yes_ranges = twl4030_49_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(twl4030_49_volatile_ranges), +}; + +static const struct regmap_config twl4030_regmap_config[4] = { + { + /* Address 0x48 */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + }, + { + /* Address 0x49 */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + + .readable_reg = twl4030_49_nop_reg, + .writeable_reg = twl4030_49_nop_reg, + + .volatile_table = &twl4030_49_volatile_table, + + .reg_defaults = twl4030_49_defaults, + .num_reg_defaults = ARRAY_SIZE(twl4030_49_defaults), + .cache_type = REGCACHE_RBTREE, + }, + { + /* Address 0x4a */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + }, + { + /* Address 0x4b */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + }, +}; + +static struct twl_mapping twl6030_map[] = { + /* + * NOTE: don't change this table without updating the + * <linux/i2c/twl.h> defines for TWL4030_MODULE_* + * so they continue to match the order in this table. + */ + + /* Common IPs */ + { 1, TWL6030_BASEADD_USB }, + { 1, TWL6030_BASEADD_PIH }, + { 1, TWL6030_BASEADD_CHARGER }, + { 0, TWL6030_BASEADD_PM_MASTER }, + { 0, TWL6030_BASEADD_PM_SLAVE_MISC }, + + { 0, TWL6030_BASEADD_RTC }, + { 1, TWL6030_BASEADD_PWM }, + { 1, TWL6030_BASEADD_LED }, + { 0, TWL6030_BASEADD_SECURED_REG }, + + /* TWL6030 specific IPs */ + { 0, TWL6030_BASEADD_ZERO }, + { 1, TWL6030_BASEADD_ZERO }, + { 2, TWL6030_BASEADD_ZERO }, + { 1, TWL6030_BASEADD_GPADC_CTRL }, + { 1, TWL6030_BASEADD_GASGAUGE }, +}; + +static const struct regmap_config twl6030_regmap_config[3] = { + { + /* Address 0x48 */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + }, + { + /* Address 0x49 */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + }, + { + /* Address 0x4a */ + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + }, +}; + +/*----------------------------------------------------------------------*/ + +static inline int twl_get_num_slaves(void) +{ + if (twl_class_is_4030()) + return 4; /* TWL4030 class have four slave address */ + else + return 3; /* TWL6030 class have three slave address */ +} + +static inline int twl_get_last_module(void) +{ + if (twl_class_is_4030()) + return TWL4030_MODULE_LAST; + else + return TWL6030_MODULE_LAST; +} + +/* Exported Functions */ + +unsigned int twl_rev(void) +{ + return twl_priv ? twl_priv->twl_id : 0; +} +EXPORT_SYMBOL(twl_rev); + +/** + * twl_get_regmap - Get the regmap associated with the given module + * @mod_no: module number + * + * Returns the regmap pointer or NULL in case of failure. + */ +static struct regmap *twl_get_regmap(u8 mod_no) +{ + int sid; + struct twl_client *twl; + + if (unlikely(!twl_priv || !twl_priv->ready)) { + pr_err("%s: not initialized\n", DRIVER_NAME); + return NULL; + } + if (unlikely(mod_no >= twl_get_last_module())) { + pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); + return NULL; + } + + sid = twl_priv->twl_map[mod_no].sid; + twl = &twl_priv->twl_modules[sid]; + + return twl->regmap; +} + +/** + * twl_i2c_write - Writes a n bit register in TWL4030/TWL5030/TWL60X0 + * @mod_no: module number + * @value: an array of num_bytes+1 containing data to write + * @reg: register address (just offset will do) + * @num_bytes: number of bytes to transfer + * + * Returns the result of operation - 0 is success + */ +int twl_i2c_write(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) +{ + struct regmap *regmap = twl_get_regmap(mod_no); + int ret; + + if (!regmap) + return -EPERM; + + ret = regmap_bulk_write(regmap, twl_priv->twl_map[mod_no].base + reg, + value, num_bytes); + + if (ret) + pr_err("%s: Write failed (mod %d, reg 0x%02x count %d)\n", + DRIVER_NAME, mod_no, reg, num_bytes); + + return ret; +} +EXPORT_SYMBOL(twl_i2c_write); + +/** + * twl_i2c_read - Reads a n bit register in TWL4030/TWL5030/TWL60X0 + * @mod_no: module number + * @value: an array of num_bytes containing data to be read + * @reg: register address (just offset will do) + * @num_bytes: number of bytes to transfer + * + * Returns result of operation - num_bytes is success else failure. + */ +int twl_i2c_read(u8 mod_no, u8 *value, u8 reg, unsigned num_bytes) +{ + struct regmap *regmap = twl_get_regmap(mod_no); + int ret; + + if (!regmap) + return -EPERM; + + ret = regmap_bulk_read(regmap, twl_priv->twl_map[mod_no].base + reg, + value, num_bytes); + + if (ret) + pr_err("%s: Read failed (mod %d, reg 0x%02x count %d)\n", + DRIVER_NAME, mod_no, reg, num_bytes); + + return ret; +} +EXPORT_SYMBOL(twl_i2c_read); + +/** + * twl_regcache_bypass - Configure the regcache bypass for the regmap associated + * with the module + * @mod_no: module number + * @enable: Regcache bypass state + * + * Returns 0 else failure. + */ +int twl_set_regcache_bypass(u8 mod_no, bool enable) +{ + struct regmap *regmap = twl_get_regmap(mod_no); + + if (!regmap) + return -EPERM; + + regcache_cache_bypass(regmap, enable); + + return 0; +} +EXPORT_SYMBOL(twl_set_regcache_bypass); + +/*----------------------------------------------------------------------*/ + +/** + * twl_read_idcode_register - API to read the IDCODE register. + * + * Unlocks the IDCODE register and read the 32 bit value. + */ +static int twl_read_idcode_register(void) +{ + int err; + + err = twl_i2c_write_u8(TWL4030_MODULE_INTBR, TWL_EEPROM_R_UNLOCK, + REG_UNLOCK_TEST_REG); + if (err) { + pr_err("TWL4030 Unable to unlock IDCODE registers -%d\n", err); + goto fail; + } + + err = twl_i2c_read(TWL4030_MODULE_INTBR, (u8 *)(&twl_priv->twl_idcode), + REG_IDCODE_7_0, 4); + if (err) { + pr_err("TWL4030: unable to read IDCODE -%d\n", err); + goto fail; + } + + err = twl_i2c_write_u8(TWL4030_MODULE_INTBR, 0x0, REG_UNLOCK_TEST_REG); + if (err) + pr_err("TWL4030 Unable to relock IDCODE registers -%d\n", err); +fail: + return err; +} + +/** + * twl_get_type - API to get TWL Si type. + * + * Api to get the TWL Si type from IDCODE value. + */ +int twl_get_type(void) +{ + return TWL_SIL_TYPE(twl_priv->twl_idcode); +} +EXPORT_SYMBOL_GPL(twl_get_type); + +/** + * twl_get_version - API to get TWL Si version. + * + * Api to get the TWL Si version from IDCODE value. + */ +int twl_get_version(void) +{ + return TWL_SIL_REV(twl_priv->twl_idcode); +} +EXPORT_SYMBOL_GPL(twl_get_version); + +/** + * twl_get_hfclk_rate - API to get TWL external HFCLK clock rate. + * + * Api to get the TWL HFCLK rate based on BOOT_CFG register. + */ +int twl_get_hfclk_rate(void) +{ + u8 ctrl; + int rate; + + twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &ctrl, R_CFG_BOOT); + + switch (ctrl & 0x3) { + case HFCLK_FREQ_19p2_MHZ: + rate = 19200000; + break; + case HFCLK_FREQ_26_MHZ: + rate = 26000000; + break; + case HFCLK_FREQ_38p4_MHZ: + rate = 38400000; + break; + default: + pr_err("TWL4030: HFCLK is not configured\n"); + rate = -EINVAL; + break; + } + + return rate; +} +EXPORT_SYMBOL_GPL(twl_get_hfclk_rate); + +static struct device * +add_numbered_child(unsigned mod_no, const char *name, int num, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq0, int irq1) +{ + struct platform_device *pdev; + struct twl_client *twl; + int status, sid; + + if (unlikely(mod_no >= twl_get_last_module())) { + pr_err("%s: invalid module number %d\n", DRIVER_NAME, mod_no); + return ERR_PTR(-EPERM); + } + sid = twl_priv->twl_map[mod_no].sid; + twl = &twl_priv->twl_modules[sid]; + + pdev = platform_device_alloc(name, num); + if (!pdev) { + dev_dbg(&twl->client->dev, "can't alloc dev\n"); + status = -ENOMEM; + goto err; + } + + pdev->dev.parent = &twl->client->dev; + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add platform_data\n"); + goto err; + } + } + + if (irq0) { + struct resource r[2] = { + { .start = irq0, .flags = IORESOURCE_IRQ, }, + { .start = irq1, .flags = IORESOURCE_IRQ, }, + }; + + status = platform_device_add_resources(pdev, r, irq1 ? 2 : 1); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add irqs\n"); + goto err; + } + } + + status = platform_device_add(pdev); + if (status == 0) + device_init_wakeup(&pdev->dev, can_wakeup); + +err: + if (status < 0) { + platform_device_put(pdev); + dev_err(&twl->client->dev, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; +} + +static inline struct device *add_child(unsigned mod_no, const char *name, + void *pdata, unsigned pdata_len, + bool can_wakeup, int irq0, int irq1) +{ + return add_numbered_child(mod_no, name, -1, pdata, pdata_len, + can_wakeup, irq0, irq1); +} + +static struct device * +add_regulator_linked(int num, struct regulator_init_data *pdata, + struct regulator_consumer_supply *consumers, + unsigned num_consumers, unsigned long features) +{ + struct twl_regulator_driver_data drv_data; + + /* regulator framework demands init_data ... */ + if (!pdata) + return NULL; + + if (consumers) { + pdata->consumer_supplies = consumers; + pdata->num_consumer_supplies = num_consumers; + } + + if (pdata->driver_data) { + /* If we have existing drv_data, just add the flags */ + struct twl_regulator_driver_data *tmp; + tmp = pdata->driver_data; + tmp->features |= features; + } else { + /* add new driver data struct, used only during init */ + drv_data.features = features; + drv_data.set_voltage = NULL; + drv_data.get_voltage = NULL; + drv_data.data = NULL; + pdata->driver_data = &drv_data; + } + + /* NOTE: we currently ignore regulator IRQs, e.g. for short circuits */ + return add_numbered_child(TWL_MODULE_PM_MASTER, "twl_reg", num, + pdata, sizeof(*pdata), false, 0, 0); +} + +static struct device * +add_regulator(int num, struct regulator_init_data *pdata, + unsigned long features) +{ + return add_regulator_linked(num, pdata, NULL, 0, features); +} + +/* + * NOTE: We know the first 8 IRQs after pdata->base_irq are + * for the PIH, and the next are for the PWR_INT SIH, since + * that's how twl_init_irq() sets things up. + */ + +static int +add_children(struct twl4030_platform_data *pdata, unsigned irq_base, + unsigned long features) +{ + struct device *child; + + if (IS_ENABLED(CONFIG_GPIO_TWL4030) && pdata->gpio) { + child = add_child(TWL4030_MODULE_GPIO, "twl4030_gpio", + pdata->gpio, sizeof(*pdata->gpio), + false, irq_base + GPIO_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_KEYBOARD_TWL4030) && pdata->keypad) { + child = add_child(TWL4030_MODULE_KEYPAD, "twl4030_keypad", + pdata->keypad, sizeof(*pdata->keypad), + true, irq_base + KEYPAD_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_TWL4030_MADC) && pdata->madc && + twl_class_is_4030()) { + child = add_child(TWL4030_MODULE_MADC, "twl4030_madc", + pdata->madc, sizeof(*pdata->madc), + true, irq_base + MADC_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_RTC_DRV_TWL4030)) { + /* + * REVISIT platform_data here currently might expose the + * "msecure" line ... but for now we just expect board + * setup to tell the chip "it's always ok to SET_TIME". + * Eventually, Linux might become more aware of such + * HW security concerns, and "least privilege". + */ + child = add_child(TWL_MODULE_RTC, "twl_rtc", NULL, 0, + true, irq_base + RTC_INTR_OFFSET, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_PWM_TWL)) { + child = add_child(TWL_MODULE_PWM, "twl-pwm", NULL, 0, + false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_PWM_TWL_LED)) { + child = add_child(TWL_MODULE_LED, "twl-pwmled", NULL, 0, + false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_TWL4030_USB) && pdata->usb && + twl_class_is_4030()) { + + static struct regulator_consumer_supply usb1v5 = { + .supply = "usb1v5", + }; + static struct regulator_consumer_supply usb1v8 = { + .supply = "usb1v8", + }; + static struct regulator_consumer_supply usb3v1[] = { + { .supply = "usb3v1" }, + { .supply = "bci3v1" }, + }; + + /* First add the regulators so that they can be used by transceiver */ + if (IS_ENABLED(CONFIG_REGULATOR_TWL4030)) { + /* this is a template that gets copied */ + struct regulator_init_data usb_fixed = { + .constraints.valid_modes_mask = + REGULATOR_MODE_NORMAL + | REGULATOR_MODE_STANDBY, + .constraints.valid_ops_mask = + REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_STATUS, + }; + + child = add_regulator_linked(TWL4030_REG_VUSB1V5, + &usb_fixed, &usb1v5, 1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator_linked(TWL4030_REG_VUSB1V8, + &usb_fixed, &usb1v8, 1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator_linked(TWL4030_REG_VUSB3V1, + &usb_fixed, usb3v1, 2, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + } + + child = add_child(TWL_MODULE_USB, "twl4030_usb", + pdata->usb, sizeof(*pdata->usb), true, + /* irq0 = USB_PRES, irq1 = USB */ + irq_base + USB_PRES_INTR_OFFSET, + irq_base + USB_INTR_OFFSET); + + if (IS_ERR(child)) + return PTR_ERR(child); + + /* we need to connect regulators to this transceiver */ + if (IS_ENABLED(CONFIG_REGULATOR_TWL4030) && child) { + usb1v5.dev_name = dev_name(child); + usb1v8.dev_name = dev_name(child); + usb3v1[0].dev_name = dev_name(child); + } + } + + if (IS_ENABLED(CONFIG_TWL4030_WATCHDOG) && twl_class_is_4030()) { + child = add_child(TWL_MODULE_PM_RECEIVER, "twl4030_wdt", NULL, + 0, false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_INPUT_TWL4030_PWRBUTTON) && twl_class_is_4030()) { + child = add_child(TWL_MODULE_PM_MASTER, "twl4030_pwrbutton", + NULL, 0, true, irq_base + 8 + 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_MFD_TWL4030_AUDIO) && pdata->audio && + twl_class_is_4030()) { + child = add_child(TWL4030_MODULE_AUDIO_VOICE, "twl4030-audio", + pdata->audio, sizeof(*pdata->audio), + false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* twl4030 regulators */ + if (IS_ENABLED(CONFIG_REGULATOR_TWL4030) && twl_class_is_4030()) { + child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VIO, pdata->vio, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VDD1, pdata->vdd1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VDD2, pdata->vdd2, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VMMC1, pdata->vmmc1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VDAC, pdata->vdac, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator((features & TWL4030_VAUX2) + ? TWL4030_REG_VAUX2_4030 + : TWL4030_REG_VAUX2, + pdata->vaux2, features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VINTANA1, pdata->vintana1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VINTANA2, pdata->vintana2, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VINTDIG, pdata->vintdig, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* maybe add LDOs that are omitted on cost-reduced parts */ + if (IS_ENABLED(CONFIG_REGULATOR_TWL4030) && !(features & TPS_SUBSET) + && twl_class_is_4030()) { + child = add_regulator(TWL4030_REG_VPLL2, pdata->vpll2, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VMMC2, pdata->vmmc2, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VSIM, pdata->vsim, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VAUX1, pdata->vaux1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VAUX3, pdata->vaux3, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL4030_REG_VAUX4, pdata->vaux4, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_CHARGER_TWL4030) && pdata->bci && + !(features & (TPS_SUBSET | TWL5031))) { + child = add_child(TWL_MODULE_MAIN_CHARGE, "twl4030_bci", + pdata->bci, sizeof(*pdata->bci), false, + /* irq0 = CHG_PRES, irq1 = BCI */ + irq_base + BCI_PRES_INTR_OFFSET, + irq_base + BCI_INTR_OFFSET); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + if (IS_ENABLED(CONFIG_TWL4030_POWER) && pdata->power) { + child = add_child(TWL_MODULE_PM_MASTER, "twl4030_power", + pdata->power, sizeof(*pdata->power), false, + 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + return 0; +} + +/*----------------------------------------------------------------------*/ + +/* + * These three functions initialize the on-chip clock framework, + * letting it generate the right frequencies for USB, MADC, and + * other purposes. + */ +static inline int __init protect_pm_master(void) +{ + int e = 0; + + e = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); + return e; +} + +static inline int __init unprotect_pm_master(void) +{ + int e = 0; + + e |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + e |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + + return e; +} + +static void clocks_init(struct device *dev, + struct twl4030_clock_init_data *clock) +{ + int e = 0; + struct clk *osc; + u32 rate; + u8 ctrl = HFCLK_FREQ_26_MHZ; + + osc = clk_get(dev, "fck"); + if (IS_ERR(osc)) { + printk(KERN_WARNING "Skipping twl internal clock init and " + "using bootloader value (unknown osc rate)\n"); + return; + } + + rate = clk_get_rate(osc); + clk_put(osc); + + switch (rate) { + case 19200000: + ctrl = HFCLK_FREQ_19p2_MHZ; + break; + case 26000000: + ctrl = HFCLK_FREQ_26_MHZ; + break; + case 38400000: + ctrl = HFCLK_FREQ_38p4_MHZ; + break; + } + + ctrl |= HIGH_PERF_SQ; + if (clock && clock->ck32k_lowpwr_enable) + ctrl |= CK32K_LOWPWR_EN; + + e |= unprotect_pm_master(); + /* effect->MADC+USB ck en */ + e |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, ctrl, R_CFG_BOOT); + e |= protect_pm_master(); + + if (e < 0) + pr_err("%s: clock init err [%d]\n", DRIVER_NAME, e); +} + +/*----------------------------------------------------------------------*/ + + +static int twl_remove(struct i2c_client *client) +{ + unsigned i, num_slaves; + int status; + + if (twl_class_is_4030()) + status = twl4030_exit_irq(); + else + status = twl6030_exit_irq(); + + if (status < 0) + return status; + + num_slaves = twl_get_num_slaves(); + for (i = 0; i < num_slaves; i++) { + struct twl_client *twl = &twl_priv->twl_modules[i]; + + if (twl->client && twl->client != client) + i2c_unregister_device(twl->client); + twl->client = NULL; + } + twl_priv->ready = false; + return 0; +} + +static struct of_dev_auxdata twl_auxdata_lookup[] = { + OF_DEV_AUXDATA("ti,twl4030-gpio", 0, "twl4030-gpio", NULL), + { /* sentinel */ }, +}; + +/* NOTE: This driver only handles a single twl4030/tps659x0 chip */ +static int +twl_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct twl4030_platform_data *pdata = dev_get_platdata(&client->dev); + struct device_node *node = client->dev.of_node; + struct platform_device *pdev; + const struct regmap_config *twl_regmap_config; + int irq_base = 0; + int status; + unsigned i, num_slaves; + + if (!node && !pdata) { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + + if (twl_priv) { + dev_dbg(&client->dev, "only one instance of %s allowed\n", + DRIVER_NAME); + return -EBUSY; + } + + pdev = platform_device_alloc(DRIVER_NAME, -1); + if (!pdev) { + dev_err(&client->dev, "can't alloc pdev\n"); + return -ENOMEM; + } + + status = platform_device_add(pdev); + if (status) { + platform_device_put(pdev); + return status; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + dev_dbg(&client->dev, "can't talk I2C?\n"); + status = -EIO; + goto free; + } + + twl_priv = devm_kzalloc(&client->dev, sizeof(struct twl_private), + GFP_KERNEL); + if (!twl_priv) { + status = -ENOMEM; + goto free; + } + + if ((id->driver_data) & TWL6030_CLASS) { + twl_priv->twl_id = TWL6030_CLASS_ID; + twl_priv->twl_map = &twl6030_map[0]; + /* The charger base address is different in twl6032 */ + if ((id->driver_data) & TWL6032_SUBCLASS) + twl_priv->twl_map[TWL_MODULE_MAIN_CHARGE].base = + TWL6032_BASEADD_CHARGER; + twl_regmap_config = twl6030_regmap_config; + } else { + twl_priv->twl_id = TWL4030_CLASS_ID; + twl_priv->twl_map = &twl4030_map[0]; + twl_regmap_config = twl4030_regmap_config; + } + + num_slaves = twl_get_num_slaves(); + twl_priv->twl_modules = devm_kzalloc(&client->dev, + sizeof(struct twl_client) * num_slaves, + GFP_KERNEL); + if (!twl_priv->twl_modules) { + status = -ENOMEM; + goto free; + } + + for (i = 0; i < num_slaves; i++) { + struct twl_client *twl = &twl_priv->twl_modules[i]; + + if (i == 0) { + twl->client = client; + } else { + twl->client = i2c_new_dummy(client->adapter, + client->addr + i); + if (!twl->client) { + dev_err(&client->dev, + "can't attach client %d\n", i); + status = -ENOMEM; + goto fail; + } + } + + twl->regmap = devm_regmap_init_i2c(twl->client, + &twl_regmap_config[i]); + if (IS_ERR(twl->regmap)) { + status = PTR_ERR(twl->regmap); + dev_err(&client->dev, + "Failed to allocate regmap %d, err: %d\n", i, + status); + goto fail; + } + } + + twl_priv->ready = true; + + /* setup clock framework */ + clocks_init(&pdev->dev, pdata ? pdata->clock : NULL); + + /* read TWL IDCODE Register */ + if (twl_class_is_4030()) { + status = twl_read_idcode_register(); + WARN(status < 0, "Error: reading twl_idcode register value\n"); + } + + /* Maybe init the T2 Interrupt subsystem */ + if (client->irq) { + if (twl_class_is_4030()) { + twl4030_init_chip_irq(id->name); + irq_base = twl4030_init_irq(&client->dev, client->irq); + } else { + irq_base = twl6030_init_irq(&client->dev, client->irq); + } + + if (irq_base < 0) { + status = irq_base; + goto fail; + } + } + + /* + * Disable TWL4030/TWL5030 I2C Pull-up on I2C1 and I2C4(SR) interface. + * Program I2C_SCL_CTRL_PU(bit 0)=0, I2C_SDA_CTRL_PU (bit 2)=0, + * SR_I2C_SCL_CTRL_PU(bit 4)=0 and SR_I2C_SDA_CTRL_PU(bit 6)=0. + * + * Also, always enable SmartReflex bit as that's needed for omaps to + * to do anything over I2C4 for voltage scaling even if SmartReflex + * is disabled. Without the SmartReflex bit omap sys_clkreq idle + * signal will never trigger for retention idle. + */ + if (twl_class_is_4030()) { + u8 temp; + + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &temp, REG_GPPUPDCTR1); + temp &= ~(SR_I2C_SDA_CTRL_PU | SR_I2C_SCL_CTRL_PU | \ + I2C_SDA_CTRL_PU | I2C_SCL_CTRL_PU); + twl_i2c_write_u8(TWL4030_MODULE_INTBR, temp, REG_GPPUPDCTR1); + + twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &temp, + TWL4030_DCDC_GLOBAL_CFG); + temp |= SMARTREFLEX_ENABLE; + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, temp, + TWL4030_DCDC_GLOBAL_CFG); + } + + if (node) { + if (pdata) + twl_auxdata_lookup[0].platform_data = pdata->gpio; + status = of_platform_populate(node, NULL, twl_auxdata_lookup, + &client->dev); + } else { + status = add_children(pdata, irq_base, id->driver_data); + } + +fail: + if (status < 0) + twl_remove(client); +free: + if (status < 0) + platform_device_unregister(pdev); + + return status; +} + +static const struct i2c_device_id twl_ids[] = { + { "twl4030", TWL4030_VAUX2 }, /* "Triton 2" */ + { "twl5030", 0 }, /* T2 updated */ + { "twl5031", TWL5031 }, /* TWL5030 updated */ + { "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" */ + { "twl6032", TWL6030_CLASS | TWL6032_SUBCLASS }, /* "Phoenix lite" */ + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(i2c, twl_ids); + +/* One Client Driver , 4 Clients */ +static struct i2c_driver twl_driver = { + .driver.name = DRIVER_NAME, + .id_table = twl_ids, + .probe = twl_probe, + .remove = twl_remove, +}; + +module_i2c_driver(twl_driver); + +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("I2C Core interface for TWL"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/twl-core.h b/kernel/drivers/mfd/twl-core.h new file mode 100644 index 000000000..6ff99dce7 --- /dev/null +++ b/kernel/drivers/mfd/twl-core.h @@ -0,0 +1,10 @@ +#ifndef __TWL_CORE_H__ +#define __TWL_CORE_H__ + +extern int twl6030_init_irq(struct device *dev, int irq_num); +extern int twl6030_exit_irq(void); +extern int twl4030_init_irq(struct device *dev, int irq_num); +extern int twl4030_exit_irq(void); +extern int twl4030_init_chip_irq(const char *chip); + +#endif /* __TWL_CORE_H__ */ diff --git a/kernel/drivers/mfd/twl4030-audio.c b/kernel/drivers/mfd/twl4030-audio.c new file mode 100644 index 000000000..0a1606480 --- /dev/null +++ b/kernel/drivers/mfd/twl4030-audio.c @@ -0,0 +1,298 @@ +/* + * 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/of.h> +#include <linux/of_platform.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(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) { + 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 bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata, + struct device_node *node) +{ + if (pdata && pdata->codec) + return true; + + if (of_find_node_by_name(node, "codec")) + return true; + + return false; +} + +static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata, + struct device_node *node) +{ + int vibra; + + if (pdata && pdata->vibra) + return true; + + if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra) + return true; + + return false; +} + +static int twl4030_audio_probe(struct platform_device *pdev) +{ + struct twl4030_audio *audio; + struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *node = pdev->dev.of_node; + struct mfd_cell *cell = NULL; + int ret, childs = 0; + u8 val; + + if (!pdata && !node) { + dev_err(&pdev->dev, "Platform data is missing\n"); + return -EINVAL; + } + + audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio), + GFP_KERNEL); + if (!audio) + return -ENOMEM; + + mutex_init(&audio->mutex); + audio->audio_mclk = twl_get_hfclk_rate(); + + /* Configure APLL_INFREQ and disable APLL if enabled */ + switch (audio->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 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 (twl4030_audio_has_codec(pdata, node)) { + cell = &audio->cells[childs]; + cell->name = "twl4030-codec"; + if (pdata) { + cell->platform_data = pdata->codec; + cell->pdata_size = sizeof(*pdata->codec); + } + childs++; + } + if (twl4030_audio_has_vibra(pdata, node)) { + cell = &audio->cells[childs]; + cell->name = "twl4030-vibra"; + if (pdata) { + cell->platform_data = pdata->vibra; + cell->pdata_size = sizeof(*pdata->vibra); + } + childs++; + } + + platform_set_drvdata(pdev, audio); + twl4030_audio_dev = pdev; + + if (childs) + ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, + childs, NULL, 0, NULL); + else { + dev_err(&pdev->dev, "No platform data found for childs\n"); + ret = -ENODEV; + } + + if (ret) + twl4030_audio_dev = NULL; + + return ret; +} + +static int twl4030_audio_remove(struct platform_device *pdev) +{ + mfd_remove_devices(&pdev->dev); + twl4030_audio_dev = NULL; + + return 0; +} + +static const struct of_device_id twl4030_audio_of_match[] = { + {.compatible = "ti,twl4030-audio", }, + { }, +}; +MODULE_DEVICE_TABLE(of, twl4030_audio_of_match); + +static struct platform_driver twl4030_audio_driver = { + .driver = { + .name = "twl4030-audio", + .of_match_table = twl4030_audio_of_match, + }, + .probe = twl4030_audio_probe, + .remove = twl4030_audio_remove, +}; + +module_platform_driver(twl4030_audio_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030-audio"); diff --git a/kernel/drivers/mfd/twl4030-irq.c b/kernel/drivers/mfd/twl4030-irq.c new file mode 100644 index 000000000..1b772ef76 --- /dev/null +++ b/kernel/drivers/mfd/twl4030-irq.c @@ -0,0 +1,797 @@ +/* + * twl4030-irq.c - TWL4030/TPS659x0 irq support + * + * Copyright (C) 2005-2006 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim <x0khasim@ti.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/irqdomain.h> +#include <linux/i2c/twl.h> + +#include "twl-core.h" + +/* + * TWL4030 IRQ handling has two stages in hardware, and thus in software. + * The Primary Interrupt Handler (PIH) stage exposes status bits saying + * which Secondary Interrupt Handler (SIH) stage is raising an interrupt. + * SIH modules are more traditional IRQ components, which support per-IRQ + * enable/disable and trigger controls; they do most of the work. + * + * These chips are designed to support IRQ handling from two different + * I2C masters. Each has a dedicated IRQ line, and dedicated IRQ status + * and mask registers in the PIH and SIH modules. + * + * We set up IRQs starting at a platform-specified base, always starting + * with PIH and the SIH for PWR_INT and then usually adding GPIO: + * base + 0 .. base + 7 PIH + * base + 8 .. base + 15 SIH for PWR_INT + * base + 16 .. base + 33 SIH for GPIO + */ +#define TWL4030_CORE_NR_IRQS 8 +#define TWL4030_PWR_NR_IRQS 8 + +/* PIH register offsets */ +#define REG_PIH_ISR_P1 0x01 +#define REG_PIH_ISR_P2 0x02 +#define REG_PIH_SIR 0x03 /* for testing */ + +/* Linux could (eventually) use either IRQ line */ +static int irq_line; + +struct sih { + char name[8]; + u8 module; /* module id */ + u8 control_offset; /* for SIH_CTRL */ + bool set_cor; + + u8 bits; /* valid in isr/imr */ + u8 bytes_ixr; /* bytelen of ISR/IMR/SIR */ + + u8 edr_offset; + u8 bytes_edr; /* bytelen of EDR */ + + u8 irq_lines; /* number of supported irq lines */ + + /* SIR ignored -- set interrupt, for testing only */ + struct sih_irq_data { + u8 isr_offset; + u8 imr_offset; + } mask[2]; + /* + 2 bytes padding */ +}; + +static const struct sih *sih_modules; +static int nr_sih_modules; + +#define SIH_INITIALIZER(modname, nbits) \ + .module = TWL4030_MODULE_ ## modname, \ + .control_offset = TWL4030_ ## modname ## _SIH_CTRL, \ + .bits = nbits, \ + .bytes_ixr = DIV_ROUND_UP(nbits, 8), \ + .edr_offset = TWL4030_ ## modname ## _EDR, \ + .bytes_edr = DIV_ROUND_UP((2*(nbits)), 8), \ + .irq_lines = 2, \ + .mask = { { \ + .isr_offset = TWL4030_ ## modname ## _ISR1, \ + .imr_offset = TWL4030_ ## modname ## _IMR1, \ + }, \ + { \ + .isr_offset = TWL4030_ ## modname ## _ISR2, \ + .imr_offset = TWL4030_ ## modname ## _IMR2, \ + }, }, + +/* register naming policies are inconsistent ... */ +#define TWL4030_INT_PWR_EDR TWL4030_INT_PWR_EDR1 +#define TWL4030_MODULE_KEYPAD_KEYP TWL4030_MODULE_KEYPAD +#define TWL4030_MODULE_INT_PWR TWL4030_MODULE_INT + + +/* + * Order in this table matches order in PIH_ISR. That is, + * BIT(n) in PIH_ISR is sih_modules[n]. + */ +/* sih_modules_twl4030 is used both in twl4030 and twl5030 */ +static const struct sih sih_modules_twl4030[6] = { + [0] = { + .name = "gpio", + .module = TWL4030_MODULE_GPIO, + .control_offset = REG_GPIO_SIH_CTRL, + .set_cor = true, + .bits = TWL4030_GPIO_MAX, + .bytes_ixr = 3, + /* Note: *all* of these IRQs default to no-trigger */ + .edr_offset = REG_GPIO_EDR1, + .bytes_edr = 5, + .irq_lines = 2, + .mask = { { + .isr_offset = REG_GPIO_ISR1A, + .imr_offset = REG_GPIO_IMR1A, + }, { + .isr_offset = REG_GPIO_ISR1B, + .imr_offset = REG_GPIO_IMR1B, + }, }, + }, + [1] = { + .name = "keypad", + .set_cor = true, + SIH_INITIALIZER(KEYPAD_KEYP, 4) + }, + [2] = { + .name = "bci", + .module = TWL4030_MODULE_INTERRUPTS, + .control_offset = TWL4030_INTERRUPTS_BCISIHCTRL, + .set_cor = true, + .bits = 12, + .bytes_ixr = 2, + .edr_offset = TWL4030_INTERRUPTS_BCIEDR1, + /* Note: most of these IRQs default to no-trigger */ + .bytes_edr = 3, + .irq_lines = 2, + .mask = { { + .isr_offset = TWL4030_INTERRUPTS_BCIISR1A, + .imr_offset = TWL4030_INTERRUPTS_BCIIMR1A, + }, { + .isr_offset = TWL4030_INTERRUPTS_BCIISR1B, + .imr_offset = TWL4030_INTERRUPTS_BCIIMR1B, + }, }, + }, + [3] = { + .name = "madc", + SIH_INITIALIZER(MADC, 4) + }, + [4] = { + /* USB doesn't use the same SIH organization */ + .name = "usb", + }, + [5] = { + .name = "power", + .set_cor = true, + SIH_INITIALIZER(INT_PWR, 8) + }, + /* there are no SIH modules #6 or #7 ... */ +}; + +static const struct sih sih_modules_twl5031[8] = { + [0] = { + .name = "gpio", + .module = TWL4030_MODULE_GPIO, + .control_offset = REG_GPIO_SIH_CTRL, + .set_cor = true, + .bits = TWL4030_GPIO_MAX, + .bytes_ixr = 3, + /* Note: *all* of these IRQs default to no-trigger */ + .edr_offset = REG_GPIO_EDR1, + .bytes_edr = 5, + .irq_lines = 2, + .mask = { { + .isr_offset = REG_GPIO_ISR1A, + .imr_offset = REG_GPIO_IMR1A, + }, { + .isr_offset = REG_GPIO_ISR1B, + .imr_offset = REG_GPIO_IMR1B, + }, }, + }, + [1] = { + .name = "keypad", + .set_cor = true, + SIH_INITIALIZER(KEYPAD_KEYP, 4) + }, + [2] = { + .name = "bci", + .module = TWL5031_MODULE_INTERRUPTS, + .control_offset = TWL5031_INTERRUPTS_BCISIHCTRL, + .bits = 7, + .bytes_ixr = 1, + .edr_offset = TWL5031_INTERRUPTS_BCIEDR1, + /* Note: most of these IRQs default to no-trigger */ + .bytes_edr = 2, + .irq_lines = 2, + .mask = { { + .isr_offset = TWL5031_INTERRUPTS_BCIISR1, + .imr_offset = TWL5031_INTERRUPTS_BCIIMR1, + }, { + .isr_offset = TWL5031_INTERRUPTS_BCIISR2, + .imr_offset = TWL5031_INTERRUPTS_BCIIMR2, + }, }, + }, + [3] = { + .name = "madc", + SIH_INITIALIZER(MADC, 4) + }, + [4] = { + /* USB doesn't use the same SIH organization */ + .name = "usb", + }, + [5] = { + .name = "power", + .set_cor = true, + SIH_INITIALIZER(INT_PWR, 8) + }, + [6] = { + /* + * ECI/DBI doesn't use the same SIH organization. + * For example, it supports only one interrupt output line. + * That is, the interrupts are seen on both INT1 and INT2 lines. + */ + .name = "eci_dbi", + .module = TWL5031_MODULE_ACCESSORY, + .bits = 9, + .bytes_ixr = 2, + .irq_lines = 1, + .mask = { { + .isr_offset = TWL5031_ACIIDR_LSB, + .imr_offset = TWL5031_ACIIMR_LSB, + }, }, + + }, + [7] = { + /* Audio accessory */ + .name = "audio", + .module = TWL5031_MODULE_ACCESSORY, + .control_offset = TWL5031_ACCSIHCTRL, + .bits = 2, + .bytes_ixr = 1, + .edr_offset = TWL5031_ACCEDR1, + /* Note: most of these IRQs default to no-trigger */ + .bytes_edr = 1, + .irq_lines = 2, + .mask = { { + .isr_offset = TWL5031_ACCISR1, + .imr_offset = TWL5031_ACCIMR1, + }, { + .isr_offset = TWL5031_ACCISR2, + .imr_offset = TWL5031_ACCIMR2, + }, }, + }, +}; + +#undef TWL4030_MODULE_KEYPAD_KEYP +#undef TWL4030_MODULE_INT_PWR +#undef TWL4030_INT_PWR_EDR + +/*----------------------------------------------------------------------*/ + +static unsigned twl4030_irq_base; + +/* + * handle_twl4030_pih() is the desc->handle method for the twl4030 interrupt. + * This is a chained interrupt, so there is no desc->action method for it. + * Now we need to query the interrupt controller in the twl4030 to determine + * which module is generating the interrupt request. However, we can't do i2c + * transactions in interrupt context, so we must defer that work to a kernel + * thread. All we do here is acknowledge and mask the interrupt and wakeup + * the kernel thread. + */ +static irqreturn_t handle_twl4030_pih(int irq, void *devid) +{ + irqreturn_t ret; + u8 pih_isr; + + ret = twl_i2c_read_u8(TWL_MODULE_PIH, &pih_isr, + REG_PIH_ISR_P1); + if (ret) { + pr_warn("twl4030: I2C error %d reading PIH ISR\n", ret); + return IRQ_NONE; + } + + while (pih_isr) { + unsigned long pending = __ffs(pih_isr); + unsigned int irq; + + pih_isr &= ~BIT(pending); + irq = pending + twl4030_irq_base; + handle_nested_irq(irq); + } + + return IRQ_HANDLED; +} + +/*----------------------------------------------------------------------*/ + +/* + * twl4030_init_sih_modules() ... start from a known state where no + * IRQs will be coming in, and where we can quickly enable them then + * handle them as they arrive. Mask all IRQs: maybe init SIH_CTRL. + * + * NOTE: we don't touch EDR registers here; they stay with hardware + * defaults or whatever the last value was. Note that when both EDR + * bits for an IRQ are clear, that's as if its IMR bit is set... + */ +static int twl4030_init_sih_modules(unsigned line) +{ + const struct sih *sih; + u8 buf[4]; + int i; + int status; + + /* line 0 == int1_n signal; line 1 == int2_n signal */ + if (line > 1) + return -EINVAL; + + irq_line = line; + + /* disable all interrupts on our line */ + memset(buf, 0xff, sizeof(buf)); + sih = sih_modules; + for (i = 0; i < nr_sih_modules; i++, sih++) { + /* skip USB -- it's funky */ + if (!sih->bytes_ixr) + continue; + + /* Not all the SIH modules support multiple interrupt lines */ + if (sih->irq_lines <= line) + continue; + + status = twl_i2c_write(sih->module, buf, + sih->mask[line].imr_offset, sih->bytes_ixr); + if (status < 0) + pr_err("twl4030: err %d initializing %s %s\n", + status, sih->name, "IMR"); + + /* + * Maybe disable "exclusive" mode; buffer second pending irq; + * set Clear-On-Read (COR) bit. + * + * NOTE that sometimes COR polarity is documented as being + * inverted: for MADC, COR=1 means "clear on write". + * And for PWR_INT it's not documented... + */ + if (sih->set_cor) { + status = twl_i2c_write_u8(sih->module, + TWL4030_SIH_CTRL_COR_MASK, + sih->control_offset); + if (status < 0) + pr_err("twl4030: err %d initializing %s %s\n", + status, sih->name, "SIH_CTRL"); + } + } + + sih = sih_modules; + for (i = 0; i < nr_sih_modules; i++, sih++) { + u8 rxbuf[4]; + int j; + + /* skip USB */ + if (!sih->bytes_ixr) + continue; + + /* Not all the SIH modules support multiple interrupt lines */ + if (sih->irq_lines <= line) + continue; + + /* + * Clear pending interrupt status. Either the read was + * enough, or we need to write those bits. Repeat, in + * case an IRQ is pending (PENDDIS=0) ... that's not + * uncommon with PWR_INT.PWRON. + */ + for (j = 0; j < 2; j++) { + status = twl_i2c_read(sih->module, rxbuf, + sih->mask[line].isr_offset, sih->bytes_ixr); + if (status < 0) + pr_warn("twl4030: err %d initializing %s %s\n", + status, sih->name, "ISR"); + + if (!sih->set_cor) { + status = twl_i2c_write(sih->module, buf, + sih->mask[line].isr_offset, + sih->bytes_ixr); + if (status < 0) + pr_warn("twl4030: write failed: %d\n", + status); + } + /* + * else COR=1 means read sufficed. + * (for most SIH modules...) + */ + } + } + + return 0; +} + +static inline void activate_irq(int irq) +{ +#ifdef CONFIG_ARM + /* + * ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(irq, IRQF_VALID); +#else + /* same effect on other architectures */ + irq_set_noprobe(irq); +#endif +} + +/*----------------------------------------------------------------------*/ + +struct sih_agent { + int irq_base; + const struct sih *sih; + + u32 imr; + bool imr_change_pending; + + u32 edge_change; + + struct mutex irq_lock; + char *irq_name; +}; + +/*----------------------------------------------------------------------*/ + +/* + * All irq_chip methods get issued from code holding irq_desc[irq].lock, + * which can't perform the underlying I2C operations (because they sleep). + * So we must hand them off to a thread (workqueue) and cope with asynch + * completion, potentially including some re-ordering, of these requests. + */ + +static void twl4030_sih_mask(struct irq_data *data) +{ + struct sih_agent *agent = irq_data_get_irq_chip_data(data); + + agent->imr |= BIT(data->irq - agent->irq_base); + agent->imr_change_pending = true; +} + +static void twl4030_sih_unmask(struct irq_data *data) +{ + struct sih_agent *agent = irq_data_get_irq_chip_data(data); + + agent->imr &= ~BIT(data->irq - agent->irq_base); + agent->imr_change_pending = true; +} + +static int twl4030_sih_set_type(struct irq_data *data, unsigned trigger) +{ + struct sih_agent *agent = irq_data_get_irq_chip_data(data); + + if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + return -EINVAL; + + if (irqd_get_trigger_type(data) != trigger) + agent->edge_change |= BIT(data->irq - agent->irq_base); + + return 0; +} + +static void twl4030_sih_bus_lock(struct irq_data *data) +{ + struct sih_agent *agent = irq_data_get_irq_chip_data(data); + + mutex_lock(&agent->irq_lock); +} + +static void twl4030_sih_bus_sync_unlock(struct irq_data *data) +{ + struct sih_agent *agent = irq_data_get_irq_chip_data(data); + const struct sih *sih = agent->sih; + int status; + + if (agent->imr_change_pending) { + union { + u32 word; + u8 bytes[4]; + } imr; + + /* byte[0] gets overwritten as we write ... */ + imr.word = cpu_to_le32(agent->imr); + agent->imr_change_pending = false; + + /* write the whole mask ... simpler than subsetting it */ + status = twl_i2c_write(sih->module, imr.bytes, + sih->mask[irq_line].imr_offset, + sih->bytes_ixr); + if (status) + pr_err("twl4030: %s, %s --> %d\n", __func__, + "write", status); + } + + if (agent->edge_change) { + u32 edge_change; + u8 bytes[6]; + + edge_change = agent->edge_change; + agent->edge_change = 0; + + /* + * Read, reserving first byte for write scratch. Yes, this + * could be cached for some speedup ... but be careful about + * any processor on the other IRQ line, EDR registers are + * shared. + */ + status = twl_i2c_read(sih->module, bytes, + sih->edr_offset, sih->bytes_edr); + if (status) { + pr_err("twl4030: %s, %s --> %d\n", __func__, + "read", status); + return; + } + + /* Modify only the bits we know must change */ + while (edge_change) { + int i = fls(edge_change) - 1; + int byte = i >> 2; + int off = (i & 0x3) * 2; + unsigned int type; + + bytes[byte] &= ~(0x03 << off); + + type = irq_get_trigger_type(i + agent->irq_base); + if (type & IRQ_TYPE_EDGE_RISING) + bytes[byte] |= BIT(off + 1); + if (type & IRQ_TYPE_EDGE_FALLING) + bytes[byte] |= BIT(off + 0); + + edge_change &= ~BIT(i); + } + + /* Write */ + status = twl_i2c_write(sih->module, bytes, + sih->edr_offset, sih->bytes_edr); + if (status) + pr_err("twl4030: %s, %s --> %d\n", __func__, + "write", status); + } + + mutex_unlock(&agent->irq_lock); +} + +static struct irq_chip twl4030_sih_irq_chip = { + .name = "twl4030", + .irq_mask = twl4030_sih_mask, + .irq_unmask = twl4030_sih_unmask, + .irq_set_type = twl4030_sih_set_type, + .irq_bus_lock = twl4030_sih_bus_lock, + .irq_bus_sync_unlock = twl4030_sih_bus_sync_unlock, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +/*----------------------------------------------------------------------*/ + +static inline int sih_read_isr(const struct sih *sih) +{ + int status; + union { + u8 bytes[4]; + u32 word; + } isr; + + /* FIXME need retry-on-error ... */ + + isr.word = 0; + status = twl_i2c_read(sih->module, isr.bytes, + sih->mask[irq_line].isr_offset, sih->bytes_ixr); + + return (status < 0) ? status : le32_to_cpu(isr.word); +} + +/* + * Generic handler for SIH interrupts ... we "know" this is called + * in task context, with IRQs enabled. + */ +static irqreturn_t handle_twl4030_sih(int irq, void *data) +{ + struct sih_agent *agent = irq_get_handler_data(irq); + const struct sih *sih = agent->sih; + int isr; + + /* reading ISR acks the IRQs, using clear-on-read mode */ + isr = sih_read_isr(sih); + + if (isr < 0) { + pr_err("twl4030: %s SIH, read ISR error %d\n", + sih->name, isr); + /* REVISIT: recover; eventually mask it all, etc */ + return IRQ_HANDLED; + } + + while (isr) { + irq = fls(isr); + irq--; + isr &= ~BIT(irq); + + if (irq < sih->bits) + handle_nested_irq(agent->irq_base + irq); + else + pr_err("twl4030: %s SIH, invalid ISR bit %d\n", + sih->name, irq); + } + return IRQ_HANDLED; +} + +/* returns the first IRQ used by this SIH bank, or negative errno */ +int twl4030_sih_setup(struct device *dev, int module, int irq_base) +{ + int sih_mod; + const struct sih *sih = NULL; + struct sih_agent *agent; + int i, irq; + int status = -EINVAL; + + /* only support modules with standard clear-on-read for now */ + for (sih_mod = 0, sih = sih_modules; sih_mod < nr_sih_modules; + sih_mod++, sih++) { + if (sih->module == module && sih->set_cor) { + status = 0; + break; + } + } + + if (status < 0) + return status; + + agent = kzalloc(sizeof(*agent), GFP_KERNEL); + if (!agent) + return -ENOMEM; + + agent->irq_base = irq_base; + agent->sih = sih; + agent->imr = ~0; + mutex_init(&agent->irq_lock); + + for (i = 0; i < sih->bits; i++) { + irq = irq_base + i; + + irq_set_chip_data(irq, agent); + irq_set_chip_and_handler(irq, &twl4030_sih_irq_chip, + handle_edge_irq); + irq_set_nested_thread(irq, 1); + activate_irq(irq); + } + + /* replace generic PIH handler (handle_simple_irq) */ + irq = sih_mod + twl4030_irq_base; + irq_set_handler_data(irq, agent); + agent->irq_name = kasprintf(GFP_KERNEL, "twl4030_%s", sih->name); + status = request_threaded_irq(irq, NULL, handle_twl4030_sih, + IRQF_EARLY_RESUME, + agent->irq_name ?: sih->name, NULL); + + dev_info(dev, "%s (irq %d) chaining IRQs %d..%d\n", sih->name, + irq, irq_base, irq_base + i - 1); + + return status < 0 ? status : irq_base; +} + +/* FIXME need a call to reverse twl4030_sih_setup() ... */ + +/*----------------------------------------------------------------------*/ + +/* FIXME pass in which interrupt line we'll use ... */ +#define twl_irq_line 0 + +int twl4030_init_irq(struct device *dev, int irq_num) +{ + static struct irq_chip twl4030_irq_chip; + int status, i; + int irq_base, irq_end, nr_irqs; + struct device_node *node = dev->of_node; + + /* + * TWL core and pwr interrupts must be contiguous because + * the hwirqs numbers are defined contiguously from 1 to 15. + * Create only one domain for both. + */ + nr_irqs = TWL4030_PWR_NR_IRQS + TWL4030_CORE_NR_IRQS; + + irq_base = irq_alloc_descs(-1, 0, nr_irqs, 0); + if (IS_ERR_VALUE(irq_base)) { + dev_err(dev, "Fail to allocate IRQ descs\n"); + return irq_base; + } + + irq_domain_add_legacy(node, nr_irqs, irq_base, 0, + &irq_domain_simple_ops, NULL); + + irq_end = irq_base + TWL4030_CORE_NR_IRQS; + + /* + * Mask and clear all TWL4030 interrupts since initially we do + * not have any TWL4030 module interrupt handlers present + */ + status = twl4030_init_sih_modules(twl_irq_line); + if (status < 0) + return status; + + twl4030_irq_base = irq_base; + + /* + * Install an irq handler for each of the SIH modules; + * clone dummy irq_chip since PIH can't *do* anything + */ + twl4030_irq_chip = dummy_irq_chip; + twl4030_irq_chip.name = "twl4030"; + + twl4030_sih_irq_chip.irq_ack = dummy_irq_chip.irq_ack; + + for (i = irq_base; i < irq_end; i++) { + irq_set_chip_and_handler(i, &twl4030_irq_chip, + handle_simple_irq); + irq_set_nested_thread(i, 1); + activate_irq(i); + } + + dev_info(dev, "%s (irq %d) chaining IRQs %d..%d\n", "PIH", + irq_num, irq_base, irq_end); + + /* ... and the PWR_INT module ... */ + status = twl4030_sih_setup(dev, TWL4030_MODULE_INT, irq_end); + if (status < 0) { + dev_err(dev, "sih_setup PWR INT --> %d\n", status); + goto fail; + } + + /* install an irq handler to demultiplex the TWL4030 interrupt */ + status = request_threaded_irq(irq_num, NULL, handle_twl4030_pih, + IRQF_ONESHOT, + "TWL4030-PIH", NULL); + if (status < 0) { + dev_err(dev, "could not claim irq%d: %d\n", irq_num, status); + goto fail_rqirq; + } + enable_irq_wake(irq_num); + + return irq_base; +fail_rqirq: + /* clean up twl4030_sih_setup */ +fail: + for (i = irq_base; i < irq_end; i++) { + irq_set_nested_thread(i, 0); + irq_set_chip_and_handler(i, NULL, NULL); + } + + return status; +} + +int twl4030_exit_irq(void) +{ + /* FIXME undo twl_init_irq() */ + if (twl4030_irq_base) { + pr_err("twl4030: can't yet clean up IRQs?\n"); + return -ENOSYS; + } + return 0; +} + +int twl4030_init_chip_irq(const char *chip) +{ + if (!strcmp(chip, "twl5031")) { + sih_modules = sih_modules_twl5031; + nr_sih_modules = ARRAY_SIZE(sih_modules_twl5031); + } else { + sih_modules = sih_modules_twl4030; + nr_sih_modules = ARRAY_SIZE(sih_modules_twl4030); + } + + return 0; +} diff --git a/kernel/drivers/mfd/twl4030-power.c b/kernel/drivers/mfd/twl4030-power.c new file mode 100644 index 000000000..f440aed61 --- /dev/null +++ b/kernel/drivers/mfd/twl4030-power.c @@ -0,0 +1,948 @@ +/* + * linux/drivers/i2c/chips/twl4030-power.c + * + * Handle TWL4030 Power initialization + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2006 Texas Instruments, Inc + * + * Written by Kalle Jokiniemi + * Peter De Schrijver <peter.de-schrijver@nokia.com> + * Several fixes by Amit Kucheria <amit.kucheria@verdurent.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/i2c/twl.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <asm/mach-types.h> + +static u8 twl4030_start_script_address = 0x2b; + +/* Register bits for P1, P2 and P3_SW_EVENTS */ +#define PWR_STOPON_PRWON BIT(6) +#define PWR_STOPON_SYSEN BIT(5) +#define PWR_ENABLE_WARMRESET BIT(4) +#define PWR_LVL_WAKEUP BIT(3) +#define PWR_DEVACT BIT(2) +#define PWR_DEVSLP BIT(1) +#define PWR_DEVOFF BIT(0) + +/* Register bits for CFG_P1_TRANSITION (also for P2 and P3) */ +#define STARTON_SWBUG BIT(7) /* Start on watchdog */ +#define STARTON_VBUS BIT(5) /* Start on VBUS */ +#define STARTON_VBAT BIT(4) /* Start on battery insert */ +#define STARTON_RTC BIT(3) /* Start on RTC */ +#define STARTON_USB BIT(2) /* Start on USB host */ +#define STARTON_CHG BIT(1) /* Start on charger */ +#define STARTON_PWON BIT(0) /* Start on PWRON button */ + +#define SEQ_OFFSYNC (1 << 0) + +#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) +#define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b) + +/* resource - hfclk */ +#define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6) + +/* PM events */ +#define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46) +#define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47) +#define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48) +#define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36) +#define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37) +#define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38) + +#define END_OF_SCRIPT 0x3f + +#define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55) +#define R_SEQ_ADD_S2A12 PHY_TO_OFF_PM_MASTER(0x56) +#define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57) +#define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58) +#define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59) +#define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) + +/* resource configuration registers + <RESOURCE>_DEV_GRP at address 'n+0' + <RESOURCE>_TYPE at address 'n+1' + <RESOURCE>_REMAP at address 'n+2' + <RESOURCE>_DEDICATED at address 'n+3' +*/ +#define DEV_GRP_OFFSET 0 +#define TYPE_OFFSET 1 +#define REMAP_OFFSET 2 +#define DEDICATED_OFFSET 3 + +/* Bit positions in the registers */ + +/* <RESOURCE>_DEV_GRP */ +#define DEV_GRP_SHIFT 5 +#define DEV_GRP_MASK (7 << DEV_GRP_SHIFT) + +/* <RESOURCE>_TYPE */ +#define TYPE_SHIFT 0 +#define TYPE_MASK (7 << TYPE_SHIFT) +#define TYPE2_SHIFT 3 +#define TYPE2_MASK (3 << TYPE2_SHIFT) + +/* <RESOURCE>_REMAP */ +#define SLEEP_STATE_SHIFT 0 +#define SLEEP_STATE_MASK (0xf << SLEEP_STATE_SHIFT) +#define OFF_STATE_SHIFT 4 +#define OFF_STATE_MASK (0xf << OFF_STATE_SHIFT) + +static u8 res_config_addrs[] = { + [RES_VAUX1] = 0x17, + [RES_VAUX2] = 0x1b, + [RES_VAUX3] = 0x1f, + [RES_VAUX4] = 0x23, + [RES_VMMC1] = 0x27, + [RES_VMMC2] = 0x2b, + [RES_VPLL1] = 0x2f, + [RES_VPLL2] = 0x33, + [RES_VSIM] = 0x37, + [RES_VDAC] = 0x3b, + [RES_VINTANA1] = 0x3f, + [RES_VINTANA2] = 0x43, + [RES_VINTDIG] = 0x47, + [RES_VIO] = 0x4b, + [RES_VDD1] = 0x55, + [RES_VDD2] = 0x63, + [RES_VUSB_1V5] = 0x71, + [RES_VUSB_1V8] = 0x74, + [RES_VUSB_3V1] = 0x77, + [RES_VUSBCP] = 0x7a, + [RES_REGEN] = 0x7f, + [RES_NRES_PWRON] = 0x82, + [RES_CLKEN] = 0x85, + [RES_SYSEN] = 0x88, + [RES_HFCLKOUT] = 0x8b, + [RES_32KCLKOUT] = 0x8e, + [RES_RESET] = 0x91, + [RES_MAIN_REF] = 0x94, +}; + +/* + * Usable values for .remap_sleep and .remap_off + * Based on table "5.3.3 Resource Operating modes" + */ +enum { + TWL_REMAP_OFF = 0, + TWL_REMAP_SLEEP = 8, + TWL_REMAP_ACTIVE = 9, +}; + +/* + * Macros to configure the PM register states for various resources. + * Note that we can make MSG_SINGULAR etc private to this driver once + * omap3 has been made DT only. + */ +#define TWL_DFLT_DELAY 2 /* typically 2 32 KiHz cycles */ +#define TWL_DEV_GRP_P123 (DEV_GRP_P1 | DEV_GRP_P2 | DEV_GRP_P3) +#define TWL_RESOURCE_SET(res, state) \ + { MSG_SINGULAR(DEV_GRP_NULL, (res), (state)), TWL_DFLT_DELAY } +#define TWL_RESOURCE_ON(res) TWL_RESOURCE_SET(res, RES_STATE_ACTIVE) +#define TWL_RESOURCE_OFF(res) TWL_RESOURCE_SET(res, RES_STATE_OFF) +#define TWL_RESOURCE_RESET(res) TWL_RESOURCE_SET(res, RES_STATE_WRST) +/* + * It seems that type1 and type2 is just the resource init order + * number for the type1 and type2 group. + */ +#define TWL_RESOURCE_SET_ACTIVE(res, state) \ + { MSG_SINGULAR(DEV_GRP_NULL, (res), RES_STATE_ACTIVE), (state) } +#define TWL_RESOURCE_GROUP_RESET(group, type1, type2) \ + { MSG_BROADCAST(DEV_GRP_NULL, (group), (type1), (type2), \ + RES_STATE_WRST), TWL_DFLT_DELAY } +#define TWL_RESOURCE_GROUP_SLEEP(group, type, type2) \ + { MSG_BROADCAST(DEV_GRP_NULL, (group), (type), (type2), \ + RES_STATE_SLEEP), TWL_DFLT_DELAY } +#define TWL_RESOURCE_GROUP_ACTIVE(group, type, type2) \ + { MSG_BROADCAST(DEV_GRP_NULL, (group), (type), (type2), \ + RES_STATE_ACTIVE), TWL_DFLT_DELAY } +#define TWL_REMAP_SLEEP(res, devgrp, typ, typ2) \ + { .resource = (res), .devgroup = (devgrp), \ + .type = (typ), .type2 = (typ2), \ + .remap_off = TWL_REMAP_OFF, \ + .remap_sleep = TWL_REMAP_SLEEP, } +#define TWL_REMAP_OFF(res, devgrp, typ, typ2) \ + { .resource = (res), .devgroup = (devgrp), \ + .type = (typ), .type2 = (typ2), \ + .remap_off = TWL_REMAP_OFF, .remap_sleep = TWL_REMAP_OFF, } + +static int twl4030_write_script_byte(u8 address, u8 byte) +{ + int err; + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_MEMORY_ADDRESS); + if (err) + goto out; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, byte, R_MEMORY_DATA); +out: + return err; +} + +static int twl4030_write_script_ins(u8 address, u16 pmb_message, + u8 delay, u8 next) +{ + int err; + + address *= 4; + err = twl4030_write_script_byte(address++, pmb_message >> 8); + if (err) + goto out; + err = twl4030_write_script_byte(address++, pmb_message & 0xff); + if (err) + goto out; + err = twl4030_write_script_byte(address++, delay); + if (err) + goto out; + err = twl4030_write_script_byte(address++, next); +out: + return err; +} + +static int twl4030_write_script(u8 address, struct twl4030_ins *script, + int len) +{ + int err = -EINVAL; + + for (; len; len--, address++, script++) { + if (len == 1) { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + END_OF_SCRIPT); + if (err) + break; + } else { + err = twl4030_write_script_ins(address, + script->pmb_message, + script->delay, + address + 1); + if (err) + break; + } + } + return err; +} + +static int twl4030_config_wakeup3_sequence(u8 address) +{ + int err; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P3 */ + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_S2A3); + if (err) + goto out; + + /* P3 LVL_WAKEUP should be on LEVEL */ + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_P3_SW_EVENTS); + if (err) + goto out; + data |= PWR_LVL_WAKEUP; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 wakeup sequence for P3 config error\n"); + return err; +} + +static int twl4030_config_wakeup12_sequence(u8 address) +{ + int err = 0; + u8 data; + + /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */ + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_S2A12); + if (err) + goto out; + + /* P1/P2 LVL_WAKEUP should be on LEVEL */ + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_P1_SW_EVENTS); + if (err) + goto out; + + data |= PWR_LVL_WAKEUP; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_P2_SW_EVENTS); + if (err) + goto out; + + data |= PWR_LVL_WAKEUP; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_P2_SW_EVENTS); + if (err) + goto out; + + if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { + /* Disabling AC charger effect on sleep-active transitions */ + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, + R_CFG_P1_TRANSITION); + if (err) + goto out; + data &= ~(1<<1); + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, + R_CFG_P1_TRANSITION); + if (err) + goto out; + } + +out: + if (err) + pr_err("TWL4030 wakeup sequence for P1 and P2" \ + "config error\n"); + return err; +} + +static int twl4030_config_sleep_sequence(u8 address) +{ + int err; + + /* Set ACTIVE to SLEEP SEQ address in T2 memory*/ + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_A2S); + + if (err) + pr_err("TWL4030 sleep sequence config error\n"); + + return err; +} + +static int twl4030_config_warmreset_sequence(u8 address) +{ + int err; + u8 rd_data; + + /* Set WARM RESET SEQ address for P1 */ + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_WARM); + if (err) + goto out; + + /* P1/P2/P3 enable WARMRESET */ + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &rd_data, R_P1_SW_EVENTS); + if (err) + goto out; + + rd_data |= PWR_ENABLE_WARMRESET; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, rd_data, R_P1_SW_EVENTS); + if (err) + goto out; + + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &rd_data, R_P2_SW_EVENTS); + if (err) + goto out; + + rd_data |= PWR_ENABLE_WARMRESET; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, rd_data, R_P2_SW_EVENTS); + if (err) + goto out; + + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &rd_data, R_P3_SW_EVENTS); + if (err) + goto out; + + rd_data |= PWR_ENABLE_WARMRESET; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, rd_data, R_P3_SW_EVENTS); +out: + if (err) + pr_err("TWL4030 warmreset seq config error\n"); + return err; +} + +static int twl4030_configure_resource(struct twl4030_resconfig *rconfig) +{ + int rconfig_addr; + int err; + u8 type; + u8 grp; + u8 remap; + + if (rconfig->resource > TOTAL_RESOURCES) { + pr_err("TWL4030 Resource %d does not exist\n", + rconfig->resource); + return -EINVAL; + } + + rconfig_addr = res_config_addrs[rconfig->resource]; + + /* Set resource group */ + err = twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &grp, + rconfig_addr + DEV_GRP_OFFSET); + if (err) { + pr_err("TWL4030 Resource %d group could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->devgroup != TWL4030_RESCONFIG_UNDEF) { + grp &= ~DEV_GRP_MASK; + grp |= rconfig->devgroup << DEV_GRP_SHIFT; + err = twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, + grp, rconfig_addr + DEV_GRP_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program devgroup\n"); + return err; + } + } + + /* Set resource types */ + err = twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &type, + rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 Resource %d type could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->type != TWL4030_RESCONFIG_UNDEF) { + type &= ~TYPE_MASK; + type |= rconfig->type << TYPE_SHIFT; + } + + if (rconfig->type2 != TWL4030_RESCONFIG_UNDEF) { + type &= ~TYPE2_MASK; + type |= rconfig->type2 << TYPE2_SHIFT; + } + + err = twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, + type, rconfig_addr + TYPE_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program resource type\n"); + return err; + } + + /* Set remap states */ + err = twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &remap, + rconfig_addr + REMAP_OFFSET); + if (err < 0) { + pr_err("TWL4030 Resource %d remap could not be read\n", + rconfig->resource); + return err; + } + + if (rconfig->remap_off != TWL4030_RESCONFIG_UNDEF) { + remap &= ~OFF_STATE_MASK; + remap |= rconfig->remap_off << OFF_STATE_SHIFT; + } + + if (rconfig->remap_sleep != TWL4030_RESCONFIG_UNDEF) { + remap &= ~SLEEP_STATE_MASK; + remap |= rconfig->remap_sleep << SLEEP_STATE_SHIFT; + } + + err = twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, + remap, + rconfig_addr + REMAP_OFFSET); + if (err < 0) { + pr_err("TWL4030 failed to program remap\n"); + return err; + } + + return 0; +} + +static int load_twl4030_script(struct twl4030_script *tscript, + u8 address) +{ + int err; + static int order; + + /* Make sure the script isn't going beyond last valid address (0x3f) */ + if ((address + tscript->size) > END_OF_SCRIPT) { + pr_err("TWL4030 scripts too big error\n"); + return -EINVAL; + } + + err = twl4030_write_script(address, tscript->script, tscript->size); + if (err) + goto out; + + if (tscript->flags & TWL4030_WRST_SCRIPT) { + err = twl4030_config_warmreset_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_WAKEUP12_SCRIPT) { + /* Reset any existing sleep script to avoid hangs on reboot */ + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_A2S); + if (err) + goto out; + + err = twl4030_config_wakeup12_sequence(address); + if (err) + goto out; + order = 1; + } + if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) { + err = twl4030_config_wakeup3_sequence(address); + if (err) + goto out; + } + if (tscript->flags & TWL4030_SLEEP_SCRIPT) { + if (!order) + pr_warning("TWL4030: Bad order of scripts (sleep "\ + "script before wakeup) Leads to boot"\ + "failure on some boards\n"); + err = twl4030_config_sleep_sequence(address); + } +out: + return err; +} + +int twl4030_remove_script(u8 flags) +{ + int err = 0; + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + if (err) { + pr_err("twl4030: unable to unlock PROTECT_KEY\n"); + return err; + } + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + if (err) { + pr_err("twl4030: unable to unlock PROTECT_KEY\n"); + return err; + } + + if (flags & TWL4030_WRST_SCRIPT) { + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_WARM); + if (err) + return err; + } + if (flags & TWL4030_WAKEUP12_SCRIPT) { + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_S2A12); + if (err) + return err; + } + if (flags & TWL4030_WAKEUP3_SCRIPT) { + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_S2A3); + if (err) + return err; + } + if (flags & TWL4030_SLEEP_SCRIPT) { + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, + R_SEQ_ADD_A2S); + if (err) + return err; + } + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); + if (err) + pr_err("TWL4030 Unable to relock registers\n"); + + return err; +} + +static int +twl4030_power_configure_scripts(const struct twl4030_power_data *pdata) +{ + int err; + int i; + u8 address = twl4030_start_script_address; + + for (i = 0; i < pdata->num; i++) { + err = load_twl4030_script(pdata->scripts[i], address); + if (err) + return err; + address += pdata->scripts[i]->size; + } + + return 0; +} + +static void twl4030_patch_rconfig(struct twl4030_resconfig *common, + struct twl4030_resconfig *board) +{ + while (common->resource) { + struct twl4030_resconfig *b = board; + + while (b->resource) { + if (b->resource == common->resource) { + *common = *b; + break; + } + b++; + } + common++; + } +} + +static int +twl4030_power_configure_resources(const struct twl4030_power_data *pdata) +{ + struct twl4030_resconfig *resconfig = pdata->resource_config; + struct twl4030_resconfig *boardconf = pdata->board_config; + int err; + + if (resconfig) { + if (boardconf) + twl4030_patch_rconfig(resconfig, boardconf); + + while (resconfig->resource) { + err = twl4030_configure_resource(resconfig); + if (err) + return err; + resconfig++; + } + } + + return 0; +} + +static int twl4030_starton_mask_and_set(u8 bitmask, u8 bitvalues) +{ + u8 regs[3] = { TWL4030_PM_MASTER_CFG_P1_TRANSITION, + TWL4030_PM_MASTER_CFG_P2_TRANSITION, + TWL4030_PM_MASTER_CFG_P3_TRANSITION, }; + u8 val; + int i, err; + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + if (err) + goto relock; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, + TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + if (err) + goto relock; + + for (i = 0; i < sizeof(regs); i++) { + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, + &val, regs[i]); + if (err) + break; + val = (~bitmask & val) | (bitmask & bitvalues); + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, + val, regs[i]); + if (err) + break; + } + + if (err) + pr_err("TWL4030 Register access failed: %i\n", err); + +relock: + return twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); +} + +/* + * In master mode, start the power off sequence. + * After a successful execution, TWL shuts down the power to the SoC + * and all peripherals connected to it. + */ +void twl4030_power_off(void) +{ + int err; + + /* Disable start on charger or VBUS as it can break poweroff */ + err = twl4030_starton_mask_and_set(STARTON_VBUS | STARTON_CHG, 0); + if (err) + pr_err("TWL4030 Unable to configure start-up\n"); + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, PWR_DEVOFF, + TWL4030_PM_MASTER_P1_SW_EVENTS); + if (err) + pr_err("TWL4030 Unable to power off\n"); +} + +static bool twl4030_power_use_poweroff(const struct twl4030_power_data *pdata, + struct device_node *node) +{ + if (pdata && pdata->use_poweroff) + return true; + + if (of_property_read_bool(node, "ti,system-power-controller")) + return true; + + if (of_property_read_bool(node, "ti,use_poweroff")) + return true; + + return false; +} + +#ifdef CONFIG_OF + +/* Generic warm reset configuration for omap3 */ + +static struct twl4030_ins omap3_wrst_seq[] = { + TWL_RESOURCE_OFF(RES_NRES_PWRON), + TWL_RESOURCE_OFF(RES_RESET), + TWL_RESOURCE_RESET(RES_MAIN_REF), + TWL_RESOURCE_GROUP_RESET(RES_GRP_ALL, RES_TYPE_R0, RES_TYPE2_R2), + TWL_RESOURCE_RESET(RES_VUSB_3V1), + TWL_RESOURCE_GROUP_RESET(RES_GRP_ALL, RES_TYPE_R0, RES_TYPE2_R1), + TWL_RESOURCE_GROUP_RESET(RES_GRP_RC, RES_TYPE_ALL, RES_TYPE2_R0), + TWL_RESOURCE_ON(RES_RESET), + TWL_RESOURCE_ON(RES_NRES_PWRON), +}; + +static struct twl4030_script omap3_wrst_script = { + .script = omap3_wrst_seq, + .size = ARRAY_SIZE(omap3_wrst_seq), + .flags = TWL4030_WRST_SCRIPT, +}; + +static struct twl4030_script *omap3_reset_scripts[] = { + &omap3_wrst_script, +}; + +static struct twl4030_resconfig omap3_rconfig[] = { + TWL_REMAP_SLEEP(RES_HFCLKOUT, DEV_GRP_P3, -1, -1), + TWL_REMAP_SLEEP(RES_VDD1, DEV_GRP_P1, -1, -1), + TWL_REMAP_SLEEP(RES_VDD2, DEV_GRP_P1, -1, -1), + { 0, 0 }, +}; + +static struct twl4030_power_data omap3_reset = { + .scripts = omap3_reset_scripts, + .num = ARRAY_SIZE(omap3_reset_scripts), + .resource_config = omap3_rconfig, +}; + +/* Recommended generic default idle configuration for off-idle */ + +/* Broadcast message to put res to sleep */ +static struct twl4030_ins omap3_idle_sleep_on_seq[] = { + TWL_RESOURCE_GROUP_SLEEP(RES_GRP_ALL, RES_TYPE_ALL, 0), +}; + +static struct twl4030_script omap3_idle_sleep_on_script = { + .script = omap3_idle_sleep_on_seq, + .size = ARRAY_SIZE(omap3_idle_sleep_on_seq), + .flags = TWL4030_SLEEP_SCRIPT, +}; + +/* Broadcast message to put res to active */ +static struct twl4030_ins omap3_idle_wakeup_p12_seq[] = { + TWL_RESOURCE_GROUP_ACTIVE(RES_GRP_ALL, RES_TYPE_ALL, 0), +}; + +static struct twl4030_script omap3_idle_wakeup_p12_script = { + .script = omap3_idle_wakeup_p12_seq, + .size = ARRAY_SIZE(omap3_idle_wakeup_p12_seq), + .flags = TWL4030_WAKEUP12_SCRIPT, +}; + +/* Broadcast message to put res to active */ +static struct twl4030_ins omap3_idle_wakeup_p3_seq[] = { + TWL_RESOURCE_SET_ACTIVE(RES_CLKEN, 0x37), + TWL_RESOURCE_GROUP_ACTIVE(RES_GRP_ALL, RES_TYPE_ALL, 0), +}; + +static struct twl4030_script omap3_idle_wakeup_p3_script = { + .script = omap3_idle_wakeup_p3_seq, + .size = ARRAY_SIZE(omap3_idle_wakeup_p3_seq), + .flags = TWL4030_WAKEUP3_SCRIPT, +}; + +static struct twl4030_script *omap3_idle_scripts[] = { + &omap3_idle_wakeup_p12_script, + &omap3_idle_wakeup_p3_script, + &omap3_wrst_script, + &omap3_idle_sleep_on_script, +}; + +/* + * Recommended configuration based on "Recommended Sleep + * Sequences for the Zoom Platform": + * http://omappedia.com/wiki/File:Recommended_Sleep_Sequences_Zoom.pdf + * Note that the type1 and type2 seem to be just the init order number + * for type1 and type2 groups as specified in the document mentioned + * above. + */ +static struct twl4030_resconfig omap3_idle_rconfig[] = { + TWL_REMAP_SLEEP(RES_VAUX1, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VAUX2, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VAUX3, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VAUX4, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VMMC1, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VMMC2, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_OFF(RES_VPLL1, DEV_GRP_P1, 3, 1), + TWL_REMAP_SLEEP(RES_VPLL2, DEV_GRP_P1, 0, 0), + TWL_REMAP_SLEEP(RES_VSIM, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VDAC, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VINTANA1, TWL_DEV_GRP_P123, 1, 2), + TWL_REMAP_SLEEP(RES_VINTANA2, TWL_DEV_GRP_P123, 0, 2), + TWL_REMAP_SLEEP(RES_VINTDIG, TWL_DEV_GRP_P123, 1, 2), + TWL_REMAP_SLEEP(RES_VIO, TWL_DEV_GRP_P123, 2, 2), + TWL_REMAP_OFF(RES_VDD1, DEV_GRP_P1, 4, 1), + TWL_REMAP_OFF(RES_VDD2, DEV_GRP_P1, 3, 1), + TWL_REMAP_SLEEP(RES_VUSB_1V5, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VUSB_1V8, TWL4030_RESCONFIG_UNDEF, 0, 0), + TWL_REMAP_SLEEP(RES_VUSB_3V1, TWL_DEV_GRP_P123, 0, 0), + /* Resource #20 USB charge pump skipped */ + TWL_REMAP_SLEEP(RES_REGEN, TWL_DEV_GRP_P123, 2, 1), + TWL_REMAP_SLEEP(RES_NRES_PWRON, TWL_DEV_GRP_P123, 0, 1), + TWL_REMAP_SLEEP(RES_CLKEN, TWL_DEV_GRP_P123, 3, 2), + TWL_REMAP_SLEEP(RES_SYSEN, TWL_DEV_GRP_P123, 6, 1), + TWL_REMAP_SLEEP(RES_HFCLKOUT, DEV_GRP_P3, 0, 2), + TWL_REMAP_SLEEP(RES_32KCLKOUT, TWL_DEV_GRP_P123, 0, 0), + TWL_REMAP_SLEEP(RES_RESET, TWL_DEV_GRP_P123, 6, 0), + TWL_REMAP_SLEEP(RES_MAIN_REF, TWL_DEV_GRP_P123, 0, 0), + { /* Terminator */ }, +}; + +static struct twl4030_power_data omap3_idle = { + .scripts = omap3_idle_scripts, + .num = ARRAY_SIZE(omap3_idle_scripts), + .resource_config = omap3_idle_rconfig, +}; + +/* Disable 32 KiHz oscillator during idle */ +static struct twl4030_resconfig osc_off_rconfig[] = { + TWL_REMAP_OFF(RES_CLKEN, DEV_GRP_P1 | DEV_GRP_P3, 3, 2), + { /* Terminator */ }, +}; + +static struct twl4030_power_data osc_off_idle = { + .scripts = omap3_idle_scripts, + .num = ARRAY_SIZE(omap3_idle_scripts), + .resource_config = omap3_idle_rconfig, + .board_config = osc_off_rconfig, +}; + +static const struct of_device_id twl4030_power_of_match[] = { + { + .compatible = "ti,twl4030-power", + }, + { + .compatible = "ti,twl4030-power-reset", + .data = &omap3_reset, + }, + { + .compatible = "ti,twl4030-power-idle", + .data = &omap3_idle, + }, + { + .compatible = "ti,twl4030-power-idle-osc-off", + .data = &osc_off_idle, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, twl4030_power_of_match); +#endif /* CONFIG_OF */ + +static int twl4030_power_probe(struct platform_device *pdev) +{ + const struct twl4030_power_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *node = pdev->dev.of_node; + const struct of_device_id *match; + int err = 0; + int err2 = 0; + u8 val; + + if (!pdata && !node) { + dev_err(&pdev->dev, "Platform data is missing\n"); + return -EINVAL; + } + + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + err |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, + TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + + if (err) { + pr_err("TWL4030 Unable to unlock registers\n"); + return err; + } + + match = of_match_device(of_match_ptr(twl4030_power_of_match), + &pdev->dev); + if (match && match->data) + pdata = match->data; + + if (pdata) { + err = twl4030_power_configure_scripts(pdata); + if (err) { + pr_err("TWL4030 failed to load scripts\n"); + goto relock; + } + err = twl4030_power_configure_resources(pdata); + if (err) { + pr_err("TWL4030 failed to configure resource\n"); + goto relock; + } + } + + /* Board has to be wired properly to use this feature */ + if (twl4030_power_use_poweroff(pdata, node) && !pm_power_off) { + /* Default for SEQ_OFFSYNC is set, lets ensure this */ + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &val, + TWL4030_PM_MASTER_CFG_P123_TRANSITION); + if (err) { + pr_warning("TWL4030 Unable to read registers\n"); + + } else if (!(val & SEQ_OFFSYNC)) { + val |= SEQ_OFFSYNC; + err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, val, + TWL4030_PM_MASTER_CFG_P123_TRANSITION); + if (err) { + pr_err("TWL4030 Unable to setup SEQ_OFFSYNC\n"); + goto relock; + } + } + + pm_power_off = twl4030_power_off; + } + +relock: + err2 = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); + if (err2) { + pr_err("TWL4030 Unable to relock registers\n"); + return err2; + } + + return err; +} + +static int twl4030_power_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver twl4030_power_driver = { + .driver = { + .name = "twl4030_power", + .of_match_table = of_match_ptr(twl4030_power_of_match), + }, + .probe = twl4030_power_probe, + .remove = twl4030_power_remove, +}; + +module_platform_driver(twl4030_power_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Power management for TWL4030"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030_power"); diff --git a/kernel/drivers/mfd/twl6030-irq.c b/kernel/drivers/mfd/twl6030-irq.c new file mode 100644 index 000000000..2807e1a95 --- /dev/null +++ b/kernel/drivers/mfd/twl6030-irq.c @@ -0,0 +1,486 @@ +/* + * twl6030-irq.c - TWL6030 irq support + * + * Copyright (C) 2005-2009 Texas Instruments, Inc. + * + * Modifications to defer interrupt handling to a kernel thread: + * Copyright (C) 2006 MontaVista Software, Inc. + * + * Based on tlv320aic23.c: + * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> + * + * Code cleanup and modifications to IRQ handler. + * by syed khasim <x0khasim@ti.com> + * + * TWL6030 specific code and IRQ handling changes by + * Jagadeesh Bhaskar Pakaravoor <j-pakaravoor@ti.com> + * Balaji T K <balajitk@ti.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kthread.h> +#include <linux/i2c/twl.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include <linux/of.h> +#include <linux/irqdomain.h> +#include <linux/of_device.h> + +#include "twl-core.h" + +/* + * TWL6030 (unlike its predecessors, which had two level interrupt handling) + * three interrupt registers INT_STS_A, INT_STS_B and INT_STS_C. + * It exposes status bits saying who has raised an interrupt. There are + * three mask registers that corresponds to these status registers, that + * enables/disables these interrupts. + * + * We set up IRQs starting at a platform-specified base. An interrupt map table, + * specifies mapping between interrupt number and the associated module. + */ +#define TWL6030_NR_IRQS 20 + +static int twl6030_interrupt_mapping[24] = { + PWR_INTR_OFFSET, /* Bit 0 PWRON */ + PWR_INTR_OFFSET, /* Bit 1 RPWRON */ + PWR_INTR_OFFSET, /* Bit 2 BAT_VLOW */ + RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */ + RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */ + HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */ + SMPSLDO_INTR_OFFSET, /* Bit 6 VXXX_SHORT */ + SMPSLDO_INTR_OFFSET, /* Bit 7 VMMC_SHORT */ + + SMPSLDO_INTR_OFFSET, /* Bit 8 VUSIM_SHORT */ + BATDETECT_INTR_OFFSET, /* Bit 9 BAT */ + SIMDETECT_INTR_OFFSET, /* Bit 10 SIM */ + MMCDETECT_INTR_OFFSET, /* Bit 11 MMC */ + RSV_INTR_OFFSET, /* Bit 12 Reserved */ + MADC_INTR_OFFSET, /* Bit 13 GPADC_RT_EOC */ + MADC_INTR_OFFSET, /* Bit 14 GPADC_SW_EOC */ + GASGAUGE_INTR_OFFSET, /* Bit 15 CC_AUTOCAL */ + + USBOTG_INTR_OFFSET, /* Bit 16 ID_WKUP */ + USBOTG_INTR_OFFSET, /* Bit 17 VBUS_WKUP */ + USBOTG_INTR_OFFSET, /* Bit 18 ID */ + USB_PRES_INTR_OFFSET, /* Bit 19 VBUS */ + CHARGER_INTR_OFFSET, /* Bit 20 CHRG_CTRL */ + CHARGERFAULT_INTR_OFFSET, /* Bit 21 EXT_CHRG */ + CHARGERFAULT_INTR_OFFSET, /* Bit 22 INT_CHRG */ + RSV_INTR_OFFSET, /* Bit 23 Reserved */ +}; + +static int twl6032_interrupt_mapping[24] = { + PWR_INTR_OFFSET, /* Bit 0 PWRON */ + PWR_INTR_OFFSET, /* Bit 1 RPWRON */ + PWR_INTR_OFFSET, /* Bit 2 SYS_VLOW */ + RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */ + RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */ + HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */ + SMPSLDO_INTR_OFFSET, /* Bit 6 VXXX_SHORT */ + PWR_INTR_OFFSET, /* Bit 7 SPDURATION */ + + PWR_INTR_OFFSET, /* Bit 8 WATCHDOG */ + BATDETECT_INTR_OFFSET, /* Bit 9 BAT */ + SIMDETECT_INTR_OFFSET, /* Bit 10 SIM */ + MMCDETECT_INTR_OFFSET, /* Bit 11 MMC */ + MADC_INTR_OFFSET, /* Bit 12 GPADC_RT_EOC */ + MADC_INTR_OFFSET, /* Bit 13 GPADC_SW_EOC */ + GASGAUGE_INTR_OFFSET, /* Bit 14 CC_EOC */ + GASGAUGE_INTR_OFFSET, /* Bit 15 CC_AUTOCAL */ + + USBOTG_INTR_OFFSET, /* Bit 16 ID_WKUP */ + USBOTG_INTR_OFFSET, /* Bit 17 VBUS_WKUP */ + USBOTG_INTR_OFFSET, /* Bit 18 ID */ + USB_PRES_INTR_OFFSET, /* Bit 19 VBUS */ + CHARGER_INTR_OFFSET, /* Bit 20 CHRG_CTRL */ + CHARGERFAULT_INTR_OFFSET, /* Bit 21 EXT_CHRG */ + CHARGERFAULT_INTR_OFFSET, /* Bit 22 INT_CHRG */ + RSV_INTR_OFFSET, /* Bit 23 Reserved */ +}; + +/*----------------------------------------------------------------------*/ + +struct twl6030_irq { + unsigned int irq_base; + int twl_irq; + bool irq_wake_enabled; + atomic_t wakeirqs; + struct notifier_block pm_nb; + struct irq_chip irq_chip; + struct irq_domain *irq_domain; + const int *irq_mapping_tbl; +}; + +static struct twl6030_irq *twl6030_irq; + +static int twl6030_irq_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, void *unused) +{ + int chained_wakeups; + struct twl6030_irq *pdata = container_of(notifier, struct twl6030_irq, + pm_nb); + + switch (pm_event) { + case PM_SUSPEND_PREPARE: + chained_wakeups = atomic_read(&pdata->wakeirqs); + + if (chained_wakeups && !pdata->irq_wake_enabled) { + if (enable_irq_wake(pdata->twl_irq)) + pr_err("twl6030 IRQ wake enable failed\n"); + else + pdata->irq_wake_enabled = true; + } else if (!chained_wakeups && pdata->irq_wake_enabled) { + disable_irq_wake(pdata->twl_irq); + pdata->irq_wake_enabled = false; + } + + disable_irq(pdata->twl_irq); + break; + + case PM_POST_SUSPEND: + enable_irq(pdata->twl_irq); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + +/* +* Threaded irq handler for the twl6030 interrupt. +* We query the interrupt controller in the twl6030 to determine +* which module is generating the interrupt request and call +* handle_nested_irq for that module. +*/ +static irqreturn_t twl6030_irq_thread(int irq, void *data) +{ + int i, ret; + union { + u8 bytes[4]; + __le32 int_sts; + } sts; + u32 int_sts; /* sts.int_sts converted to CPU endianness */ + struct twl6030_irq *pdata = data; + + /* read INT_STS_A, B and C in one shot using a burst read */ + ret = twl_i2c_read(TWL_MODULE_PIH, sts.bytes, REG_INT_STS_A, 3); + if (ret) { + pr_warn("twl6030_irq: I2C error %d reading PIH ISR\n", ret); + return IRQ_HANDLED; + } + + sts.bytes[3] = 0; /* Only 24 bits are valid*/ + + /* + * Since VBUS status bit is not reliable for VBUS disconnect + * use CHARGER VBUS detection status bit instead. + */ + if (sts.bytes[2] & 0x10) + sts.bytes[2] |= 0x08; + + int_sts = le32_to_cpu(sts.int_sts); + for (i = 0; int_sts; int_sts >>= 1, i++) + if (int_sts & 0x1) { + int module_irq = + irq_find_mapping(pdata->irq_domain, + pdata->irq_mapping_tbl[i]); + if (module_irq) + handle_nested_irq(module_irq); + else + pr_err("twl6030_irq: Unmapped PIH ISR %u detected\n", + i); + pr_debug("twl6030_irq: PIH ISR %u, virq%u\n", + i, module_irq); + } + + /* + * NOTE: + * Simulation confirms that documentation is wrong w.r.t the + * interrupt status clear operation. A single *byte* write to + * any one of STS_A to STS_C register results in all three + * STS registers being reset. Since it does not matter which + * value is written, all three registers are cleared on a + * single byte write, so we just use 0x0 to clear. + */ + ret = twl_i2c_write_u8(TWL_MODULE_PIH, 0x00, REG_INT_STS_A); + if (ret) + pr_warn("twl6030_irq: I2C error in clearing PIH ISR\n"); + + return IRQ_HANDLED; +} + +/*----------------------------------------------------------------------*/ + +static int twl6030_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct twl6030_irq *pdata = irq_get_chip_data(d->irq); + + if (on) + atomic_inc(&pdata->wakeirqs); + else + atomic_dec(&pdata->wakeirqs); + + return 0; +} + +int twl6030_interrupt_unmask(u8 bit_mask, u8 offset) +{ + int ret; + u8 unmask_value; + + ret = twl_i2c_read_u8(TWL_MODULE_PIH, &unmask_value, + REG_INT_STS_A + offset); + unmask_value &= (~(bit_mask)); + ret |= twl_i2c_write_u8(TWL_MODULE_PIH, unmask_value, + REG_INT_STS_A + offset); /* unmask INT_MSK_A/B/C */ + return ret; +} +EXPORT_SYMBOL(twl6030_interrupt_unmask); + +int twl6030_interrupt_mask(u8 bit_mask, u8 offset) +{ + int ret; + u8 mask_value; + + ret = twl_i2c_read_u8(TWL_MODULE_PIH, &mask_value, + REG_INT_STS_A + offset); + mask_value |= (bit_mask); + ret |= twl_i2c_write_u8(TWL_MODULE_PIH, mask_value, + REG_INT_STS_A + offset); /* mask INT_MSK_A/B/C */ + return ret; +} +EXPORT_SYMBOL(twl6030_interrupt_mask); + +int twl6030_mmc_card_detect_config(void) +{ + int ret; + u8 reg_val = 0; + + /* Unmasking the Card detect Interrupt line for MMC1 from Phoenix */ + twl6030_interrupt_unmask(TWL6030_MMCDETECT_INT_MASK, + REG_INT_MSK_LINE_B); + twl6030_interrupt_unmask(TWL6030_MMCDETECT_INT_MASK, + REG_INT_MSK_STS_B); + /* + * Initially Configuring MMC_CTRL for receiving interrupts & + * Card status on TWL6030 for MMC1 + */ + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, ®_val, TWL6030_MMCCTRL); + if (ret < 0) { + pr_err("twl6030: Failed to read MMCCTRL, error %d\n", ret); + return ret; + } + reg_val &= ~VMMC_AUTO_OFF; + reg_val |= SW_FC; + ret = twl_i2c_write_u8(TWL6030_MODULE_ID0, reg_val, TWL6030_MMCCTRL); + if (ret < 0) { + pr_err("twl6030: Failed to write MMCCTRL, error %d\n", ret); + return ret; + } + + /* Configuring PullUp-PullDown register */ + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, ®_val, + TWL6030_CFG_INPUT_PUPD3); + if (ret < 0) { + pr_err("twl6030: Failed to read CFG_INPUT_PUPD3, error %d\n", + ret); + return ret; + } + reg_val &= ~(MMC_PU | MMC_PD); + ret = twl_i2c_write_u8(TWL6030_MODULE_ID0, reg_val, + TWL6030_CFG_INPUT_PUPD3); + if (ret < 0) { + pr_err("twl6030: Failed to write CFG_INPUT_PUPD3, error %d\n", + ret); + return ret; + } + + return irq_find_mapping(twl6030_irq->irq_domain, + MMCDETECT_INTR_OFFSET); +} +EXPORT_SYMBOL(twl6030_mmc_card_detect_config); + +int twl6030_mmc_card_detect(struct device *dev, int slot) +{ + int ret = -EIO; + u8 read_reg = 0; + struct platform_device *pdev = to_platform_device(dev); + + if (pdev->id) { + /* TWL6030 provide's Card detect support for + * only MMC1 controller. + */ + pr_err("Unknown MMC controller %d in %s\n", pdev->id, __func__); + return ret; + } + /* + * BIT0 of MMC_CTRL on TWL6030 provides card status for MMC1 + * 0 - Card not present ,1 - Card present + */ + ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, &read_reg, + TWL6030_MMCCTRL); + if (ret >= 0) + ret = read_reg & STS_MMC; + return ret; +} +EXPORT_SYMBOL(twl6030_mmc_card_detect); + +static int twl6030_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct twl6030_irq *pdata = d->host_data; + + irq_set_chip_data(virq, pdata); + irq_set_chip_and_handler(virq, &pdata->irq_chip, handle_simple_irq); + irq_set_nested_thread(virq, true); + irq_set_parent(virq, pdata->twl_irq); + +#ifdef CONFIG_ARM + /* + * ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(virq, IRQF_VALID); +#else + /* same effect on other architectures */ + irq_set_noprobe(virq); +#endif + + return 0; +} + +static void twl6030_irq_unmap(struct irq_domain *d, unsigned int virq) +{ +#ifdef CONFIG_ARM + set_irq_flags(virq, 0); +#endif + irq_set_chip_and_handler(virq, NULL, NULL); + irq_set_chip_data(virq, NULL); +} + +static struct irq_domain_ops twl6030_irq_domain_ops = { + .map = twl6030_irq_map, + .unmap = twl6030_irq_unmap, + .xlate = irq_domain_xlate_onetwocell, +}; + +static const struct of_device_id twl6030_of_match[] = { + {.compatible = "ti,twl6030", &twl6030_interrupt_mapping}, + {.compatible = "ti,twl6032", &twl6032_interrupt_mapping}, + { }, +}; + +int twl6030_init_irq(struct device *dev, int irq_num) +{ + struct device_node *node = dev->of_node; + int nr_irqs; + int status; + u8 mask[3]; + const struct of_device_id *of_id; + + of_id = of_match_device(twl6030_of_match, dev); + if (!of_id || !of_id->data) { + dev_err(dev, "Unknown TWL device model\n"); + return -EINVAL; + } + + nr_irqs = TWL6030_NR_IRQS; + + twl6030_irq = devm_kzalloc(dev, sizeof(*twl6030_irq), GFP_KERNEL); + if (!twl6030_irq) { + dev_err(dev, "twl6030_irq: Memory allocation failed\n"); + return -ENOMEM; + } + + mask[0] = 0xFF; + mask[1] = 0xFF; + mask[2] = 0xFF; + + /* mask all int lines */ + status = twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_MSK_LINE_A, 3); + /* mask all int sts */ + status |= twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_MSK_STS_A, 3); + /* clear INT_STS_A,B,C */ + status |= twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_STS_A, 3); + + if (status < 0) { + dev_err(dev, "I2C err writing TWL_MODULE_PIH: %d\n", status); + return status; + } + + /* + * install an irq handler for each of the modules; + * clone dummy irq_chip since PIH can't *do* anything + */ + twl6030_irq->irq_chip = dummy_irq_chip; + twl6030_irq->irq_chip.name = "twl6030"; + twl6030_irq->irq_chip.irq_set_type = NULL; + twl6030_irq->irq_chip.irq_set_wake = twl6030_irq_set_wake; + + twl6030_irq->pm_nb.notifier_call = twl6030_irq_pm_notifier; + atomic_set(&twl6030_irq->wakeirqs, 0); + twl6030_irq->irq_mapping_tbl = of_id->data; + + twl6030_irq->irq_domain = + irq_domain_add_linear(node, nr_irqs, + &twl6030_irq_domain_ops, twl6030_irq); + if (!twl6030_irq->irq_domain) { + dev_err(dev, "Can't add irq_domain\n"); + return -ENOMEM; + } + + dev_info(dev, "PIH (irq %d) nested IRQs\n", irq_num); + + /* install an irq handler to demultiplex the TWL6030 interrupt */ + status = request_threaded_irq(irq_num, NULL, twl6030_irq_thread, + IRQF_ONESHOT, "TWL6030-PIH", twl6030_irq); + if (status < 0) { + dev_err(dev, "could not claim irq %d: %d\n", irq_num, status); + goto fail_irq; + } + + twl6030_irq->twl_irq = irq_num; + register_pm_notifier(&twl6030_irq->pm_nb); + return 0; + +fail_irq: + irq_domain_remove(twl6030_irq->irq_domain); + return status; +} + +int twl6030_exit_irq(void) +{ + if (twl6030_irq && twl6030_irq->twl_irq) { + unregister_pm_notifier(&twl6030_irq->pm_nb); + free_irq(twl6030_irq->twl_irq, NULL); + /* + * TODO: IRQ domain and allocated nested IRQ descriptors + * should be freed somehow here. Now It can't be done, because + * child devices will not be deleted during removing of + * TWL Core driver and they will still contain allocated + * virt IRQs in their Resources tables. + * The same prevents us from using devm_request_threaded_irq() + * in this module. + */ + } + return 0; +} + diff --git a/kernel/drivers/mfd/twl6040.c b/kernel/drivers/mfd/twl6040.c new file mode 100644 index 000000000..c5265c126 --- /dev/null +++ b/kernel/drivers/mfd/twl6040.c @@ -0,0 +1,816 @@ +/* + * 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/err.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/mfd/core.h> +#include <linux/mfd/twl6040.h> +#include <linux/regulator/consumer.h> + +#define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1) +#define TWL6040_NUM_SUPPLIES (2) + +static const struct reg_default twl6040_defaults[] = { + { 0x01, 0x4B }, /* REG_ASICID (ro) */ + { 0x02, 0x00 }, /* REG_ASICREV (ro) */ + { 0x03, 0x00 }, /* REG_INTID */ + { 0x04, 0x00 }, /* REG_INTMR */ + { 0x05, 0x00 }, /* REG_NCPCTRL */ + { 0x06, 0x00 }, /* REG_LDOCTL */ + { 0x07, 0x60 }, /* REG_HPPLLCTL */ + { 0x08, 0x00 }, /* REG_LPPLLCTL */ + { 0x09, 0x4A }, /* REG_LPPLLDIV */ + { 0x0A, 0x00 }, /* REG_AMICBCTL */ + { 0x0B, 0x00 }, /* REG_DMICBCTL */ + { 0x0C, 0x00 }, /* REG_MICLCTL */ + { 0x0D, 0x00 }, /* REG_MICRCTL */ + { 0x0E, 0x00 }, /* REG_MICGAIN */ + { 0x0F, 0x1B }, /* REG_LINEGAIN */ + { 0x10, 0x00 }, /* REG_HSLCTL */ + { 0x11, 0x00 }, /* REG_HSRCTL */ + { 0x12, 0x00 }, /* REG_HSGAIN */ + { 0x13, 0x00 }, /* REG_EARCTL */ + { 0x14, 0x00 }, /* REG_HFLCTL */ + { 0x15, 0x00 }, /* REG_HFLGAIN */ + { 0x16, 0x00 }, /* REG_HFRCTL */ + { 0x17, 0x00 }, /* REG_HFRGAIN */ + { 0x18, 0x00 }, /* REG_VIBCTLL */ + { 0x19, 0x00 }, /* REG_VIBDATL */ + { 0x1A, 0x00 }, /* REG_VIBCTLR */ + { 0x1B, 0x00 }, /* REG_VIBDATR */ + { 0x1C, 0x00 }, /* REG_HKCTL1 */ + { 0x1D, 0x00 }, /* REG_HKCTL2 */ + { 0x1E, 0x00 }, /* REG_GPOCTL */ + { 0x1F, 0x00 }, /* REG_ALB */ + { 0x20, 0x00 }, /* REG_DLB */ + /* 0x28, REG_TRIM1 */ + /* 0x29, REG_TRIM2 */ + /* 0x2A, REG_TRIM3 */ + /* 0x2B, REG_HSOTRIM */ + /* 0x2C, REG_HFOTRIM */ + { 0x2D, 0x08 }, /* REG_ACCCTL */ + { 0x2E, 0x00 }, /* REG_STATUS (ro) */ +}; + +static struct reg_default twl6040_patch[] = { + /* + * Select I2C bus access to dual access registers + * Interrupt register is cleared on read + * Select fast mode for i2c (400KHz) + */ + { TWL6040_REG_ACCCTL, + TWL6040_I2CSEL | TWL6040_INTCLRMODE | TWL6040_I2CMODE(1) }, +}; + + +static bool twl6040_has_vibra(struct device_node *node) +{ +#ifdef CONFIG_OF + if (of_find_node_by_name(node, "vibra")) + return true; +#endif + return false; +} + +int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg) +{ + int ret; + unsigned int val; + + ret = regmap_read(twl6040->regmap, reg, &val); + if (ret < 0) + return ret; + + return val; +} +EXPORT_SYMBOL(twl6040_reg_read); + +int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val) +{ + int ret; + + ret = regmap_write(twl6040->regmap, reg, val); + + return ret; +} +EXPORT_SYMBOL(twl6040_reg_write); + +int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask) +{ + return regmap_update_bits(twl6040->regmap, reg, mask, mask); +} +EXPORT_SYMBOL(twl6040_set_bits); + +int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask) +{ + return regmap_update_bits(twl6040->regmap, reg, mask, 0); +} +EXPORT_SYMBOL(twl6040_clear_bits); + +/* twl6040 codec manual power-up sequence */ +static int twl6040_power_up_manual(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); + + dev_err(twl6040->dev, "manual power-up failed\n"); + return ret; +} + +/* twl6040 manual power-down sequence */ +static void twl6040_power_down_manual(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_readyint_handler(int irq, void *data) +{ + struct twl6040 *twl6040 = data; + + complete(&twl6040->ready); + + return IRQ_HANDLED; +} + +static irqreturn_t twl6040_thint_handler(int irq, void *data) +{ + struct twl6040 *twl6040 = data; + u8 status; + + status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS); + if (status & TWL6040_TSHUTDET) { + dev_warn(twl6040->dev, "Thermal shutdown, powering-off"); + twl6040_power(twl6040, 0); + } else { + dev_warn(twl6040->dev, "Leaving thermal shutdown, powering-on"); + twl6040_power(twl6040, 1); + } + + return IRQ_HANDLED; +} + +static int twl6040_power_up_automatic(struct twl6040 *twl6040) +{ + int time_left; + + gpio_set_value(twl6040->audpwron, 1); + + time_left = wait_for_completion_timeout(&twl6040->ready, + msecs_to_jiffies(144)); + if (!time_left) { + u8 intid; + + dev_warn(twl6040->dev, "timeout waiting for READYINT\n"); + intid = twl6040_reg_read(twl6040, TWL6040_REG_INTID); + if (!(intid & TWL6040_READYINT)) { + dev_err(twl6040->dev, "automatic power-up failed\n"); + gpio_set_value(twl6040->audpwron, 0); + return -ETIMEDOUT; + } + } + + return 0; +} + +int twl6040_power(struct twl6040 *twl6040, int on) +{ + int ret = 0; + + mutex_lock(&twl6040->mutex); + + if (on) { + /* already powered-up */ + if (twl6040->power_count++) + goto out; + + clk_prepare_enable(twl6040->clk32k); + + /* Allow writes to the chip */ + regcache_cache_only(twl6040->regmap, false); + + if (gpio_is_valid(twl6040->audpwron)) { + /* use automatic power-up sequence */ + ret = twl6040_power_up_automatic(twl6040); + if (ret) { + twl6040->power_count = 0; + goto out; + } + } else { + /* use manual power-up sequence */ + ret = twl6040_power_up_manual(twl6040); + if (ret) { + twl6040->power_count = 0; + goto out; + } + } + + /* Sync with the HW */ + regcache_sync(twl6040->regmap); + + /* Default PLL configuration after power up */ + twl6040->pll = TWL6040_SYSCLK_SEL_LPPLL; + twl6040->sysclk = 19200000; + twl6040->mclk = 32768; + } else { + /* already powered-down */ + if (!twl6040->power_count) { + dev_err(twl6040->dev, + "device is already powered-off\n"); + ret = -EPERM; + goto out; + } + + if (--twl6040->power_count) + goto out; + + if (gpio_is_valid(twl6040->audpwron)) { + /* use AUDPWRON line */ + gpio_set_value(twl6040->audpwron, 0); + + /* power-down sequence latency */ + usleep_range(500, 700); + } else { + /* use manual power-down sequence */ + twl6040_power_down_manual(twl6040); + } + + /* Set regmap to cache only and mark it as dirty */ + regcache_cache_only(twl6040->regmap, true); + regcache_mark_dirty(twl6040->regmap); + + twl6040->sysclk = 0; + twl6040->mclk = 0; + + clk_disable_unprepare(twl6040->clk32k); + } + +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); + + /* Force full reconfiguration when switching between PLL */ + if (pll_id != twl6040->pll) { + twl6040->sysclk = 0; + twl6040->mclk = 0; + } + + switch (pll_id) { + case TWL6040_SYSCLK_SEL_LPPLL: + /* low-power PLL divider */ + /* Change the sysclk configuration only if it has been canged */ + if (twl6040->sysclk != freq_out) { + switch (freq_out) { + case 17640000: + lppllctl |= TWL6040_LPLLFIN; + break; + case 19200000: + lppllctl &= ~TWL6040_LPLLFIN; + break; + default: + dev_err(twl6040->dev, + "freq_out %d not supported\n", + freq_out); + ret = -EINVAL; + goto pll_out; + } + twl6040_reg_write(twl6040, TWL6040_REG_LPPLLCTL, + lppllctl); + } + + /* The PLL in use has not been change, we can exit */ + if (twl6040->pll == pll_id) + break; + + 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, + "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, + "freq_out %d not supported\n", freq_out); + ret = -EINVAL; + goto pll_out; + } + + if (twl6040->mclk != freq_in) { + hppllctl &= ~TWL6040_MCLK_MSK; + + switch (freq_in) { + case 12000000: + /* PLL enabled, active mode */ + hppllctl |= TWL6040_MCLK_12000KHZ | + TWL6040_HPLLENA; + break; + case 19200000: + /* PLL enabled, bypass mode */ + hppllctl |= TWL6040_MCLK_19200KHZ | + TWL6040_HPLLBP | TWL6040_HPLLENA; + break; + case 26000000: + /* PLL enabled, active mode */ + hppllctl |= TWL6040_MCLK_26000KHZ | + TWL6040_HPLLENA; + break; + case 38400000: + /* PLL enabled, bypass mode */ + hppllctl |= TWL6040_MCLK_38400KHZ | + TWL6040_HPLLBP | TWL6040_HPLLENA; + break; + default: + dev_err(twl6040->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, "unknown pll id %d\n", pll_id); + ret = -EINVAL; + goto pll_out; + } + + twl6040->sysclk = freq_out; + twl6040->mclk = freq_in; + 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); + +/* Get the combined status of the vibra control register */ +int twl6040_get_vibralr_status(struct twl6040 *twl6040) +{ + unsigned int reg; + int ret; + u8 status; + + ret = regmap_read(twl6040->regmap, TWL6040_REG_VIBCTLL, ®); + if (ret != 0) + return ret; + status = reg; + + ret = regmap_read(twl6040->regmap, TWL6040_REG_VIBCTLR, ®); + if (ret != 0) + return ret; + status |= reg; + + status &= (TWL6040_VIBENA | TWL6040_VIBSEL); + + return status; +} +EXPORT_SYMBOL(twl6040_get_vibralr_status); + +static struct resource twl6040_vibra_rsrc[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource twl6040_codec_rsrc[] = { + { + .flags = IORESOURCE_IRQ, + }, +}; + +static bool twl6040_readable_reg(struct device *dev, unsigned int reg) +{ + /* Register 0 is not readable */ + if (!reg) + return false; + return true; +} + +static bool twl6040_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TWL6040_REG_ASICID: + case TWL6040_REG_ASICREV: + case TWL6040_REG_INTID: + case TWL6040_REG_LPPLLCTL: + case TWL6040_REG_HPPLLCTL: + case TWL6040_REG_STATUS: + return true; + default: + return false; + } +} + +static bool twl6040_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TWL6040_REG_ASICID: + case TWL6040_REG_ASICREV: + case TWL6040_REG_STATUS: + return false; + default: + return true; + } +} + +static const struct regmap_config twl6040_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = twl6040_defaults, + .num_reg_defaults = ARRAY_SIZE(twl6040_defaults), + + .max_register = TWL6040_REG_STATUS, /* 0x2e */ + + .readable_reg = twl6040_readable_reg, + .volatile_reg = twl6040_volatile_reg, + .writeable_reg = twl6040_writeable_reg, + + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_irq twl6040_irqs[] = { + { .reg_offset = 0, .mask = TWL6040_THINT, }, + { .reg_offset = 0, .mask = TWL6040_PLUGINT | TWL6040_UNPLUGINT, }, + { .reg_offset = 0, .mask = TWL6040_HOOKINT, }, + { .reg_offset = 0, .mask = TWL6040_HFINT, }, + { .reg_offset = 0, .mask = TWL6040_VIBINT, }, + { .reg_offset = 0, .mask = TWL6040_READYINT, }, +}; + +static struct regmap_irq_chip twl6040_irq_chip = { + .name = "twl6040", + .irqs = twl6040_irqs, + .num_irqs = ARRAY_SIZE(twl6040_irqs), + + .num_regs = 1, + .status_base = TWL6040_REG_INTID, + .mask_base = TWL6040_REG_INTMR, +}; + +static int twl6040_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *node = client->dev.of_node; + struct twl6040 *twl6040; + struct mfd_cell *cell = NULL; + int irq, ret, children = 0; + + if (!node) { + dev_err(&client->dev, "of node is missing\n"); + return -EINVAL; + } + + /* In order to operate correctly we need valid interrupt config */ + if (!client->irq) { + dev_err(&client->dev, "Invalid IRQ configuration\n"); + return -EINVAL; + } + + twl6040 = devm_kzalloc(&client->dev, sizeof(struct twl6040), + GFP_KERNEL); + if (!twl6040) + return -ENOMEM; + + twl6040->regmap = devm_regmap_init_i2c(client, &twl6040_regmap_config); + if (IS_ERR(twl6040->regmap)) + return PTR_ERR(twl6040->regmap); + + i2c_set_clientdata(client, twl6040); + + twl6040->clk32k = devm_clk_get(&client->dev, "clk32k"); + if (IS_ERR(twl6040->clk32k)) { + dev_info(&client->dev, "clk32k is not handled\n"); + twl6040->clk32k = NULL; + } + + twl6040->supplies[0].supply = "vio"; + twl6040->supplies[1].supply = "v2v1"; + ret = devm_regulator_bulk_get(&client->dev, TWL6040_NUM_SUPPLIES, + twl6040->supplies); + if (ret != 0) { + dev_err(&client->dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(TWL6040_NUM_SUPPLIES, twl6040->supplies); + if (ret != 0) { + dev_err(&client->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + twl6040->dev = &client->dev; + twl6040->irq = client->irq; + + mutex_init(&twl6040->mutex); + init_completion(&twl6040->ready); + + regmap_register_patch(twl6040->regmap, twl6040_patch, + ARRAY_SIZE(twl6040_patch)); + + twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV); + if (twl6040->rev < 0) { + dev_err(&client->dev, "Failed to read revision register: %d\n", + twl6040->rev); + ret = twl6040->rev; + goto gpio_err; + } + + /* ERRATA: Automatic power-up is not possible in ES1.0 */ + if (twl6040_get_revid(twl6040) > TWL6040_REV_ES1_0) + twl6040->audpwron = of_get_named_gpio(node, + "ti,audpwron-gpio", 0); + else + twl6040->audpwron = -EINVAL; + + if (gpio_is_valid(twl6040->audpwron)) { + ret = devm_gpio_request_one(&client->dev, twl6040->audpwron, + GPIOF_OUT_INIT_LOW, "audpwron"); + if (ret) + goto gpio_err; + + /* Clear any pending interrupt */ + twl6040_reg_read(twl6040, TWL6040_REG_INTID); + } + + ret = regmap_add_irq_chip(twl6040->regmap, twl6040->irq, IRQF_ONESHOT, + 0, &twl6040_irq_chip, &twl6040->irq_data); + if (ret < 0) + goto gpio_err; + + twl6040->irq_ready = regmap_irq_get_virq(twl6040->irq_data, + TWL6040_IRQ_READY); + twl6040->irq_th = regmap_irq_get_virq(twl6040->irq_data, + TWL6040_IRQ_TH); + + ret = devm_request_threaded_irq(twl6040->dev, twl6040->irq_ready, NULL, + twl6040_readyint_handler, IRQF_ONESHOT, + "twl6040_irq_ready", twl6040); + if (ret) { + dev_err(twl6040->dev, "READY IRQ request failed: %d\n", ret); + goto readyirq_err; + } + + ret = devm_request_threaded_irq(twl6040->dev, twl6040->irq_th, NULL, + twl6040_thint_handler, IRQF_ONESHOT, + "twl6040_irq_th", twl6040); + if (ret) { + dev_err(twl6040->dev, "Thermal IRQ request failed: %d\n", ret); + goto readyirq_err; + } + + /* + * The main functionality of twl6040 to provide audio on OMAP4+ systems. + * We can add the ASoC codec child whenever this driver has been loaded. + */ + irq = regmap_irq_get_virq(twl6040->irq_data, 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); + children++; + + /* Vibra input driver support */ + if (twl6040_has_vibra(node)) { + irq = regmap_irq_get_virq(twl6040->irq_data, 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); + children++; + } + + /* GPO support */ + cell = &twl6040->cells[children]; + cell->name = "twl6040-gpo"; + children++; + + /* The chip is powered down so mark regmap to cache only and dirty */ + regcache_cache_only(twl6040->regmap, true); + regcache_mark_dirty(twl6040->regmap); + + ret = mfd_add_devices(&client->dev, -1, twl6040->cells, children, + NULL, 0, NULL); + if (ret) + goto readyirq_err; + + return 0; + +readyirq_err: + regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); +gpio_err: + regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); + return ret; +} + +static int twl6040_remove(struct i2c_client *client) +{ + struct twl6040 *twl6040 = i2c_get_clientdata(client); + + if (twl6040->power_count) + twl6040_power(twl6040, 0); + + regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); + + mfd_remove_devices(&client->dev); + + regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); + + return 0; +} + +static const struct i2c_device_id twl6040_i2c_id[] = { + { "twl6040", 0, }, + { "twl6041", 0, }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, twl6040_i2c_id); + +static struct i2c_driver twl6040_driver = { + .driver = { + .name = "twl6040", + .owner = THIS_MODULE, + }, + .probe = twl6040_probe, + .remove = twl6040_remove, + .id_table = twl6040_i2c_id, +}; + +module_i2c_driver(twl6040_driver); + +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"); diff --git a/kernel/drivers/mfd/ucb1400_core.c b/kernel/drivers/mfd/ucb1400_core.c new file mode 100644 index 000000000..ebb20edf9 --- /dev/null +++ b/kernel/drivers/mfd/ucb1400_core.c @@ -0,0 +1,163 @@ +/* + * Core functions for: + * Philips UCB1400 multifunction chip + * + * Based on ucb1400_ts.c: + * Author: Nicolas Pitre + * Created: September 25, 2006 + * Copyright: MontaVista Software, Inc. + * + * Spliting done by: Marek Vasut <marek.vasut@gmail.com> + * If something doesn't work and it worked before spliting, e-mail me, + * dont bother Nicolas please ;-) + * + * 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 code is heavily based on ucb1x00-*.c copyrighted by Russell King + * covering the UCB1100, UCB1200 and UCB1300.. Support for the UCB1400 has + * been made separate from ucb1x00-core/ucb1x00-ts on Russell's request. + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ucb1400.h> + +unsigned int ucb1400_adc_read(struct snd_ac97 *ac97, u16 adc_channel, + int adcsync) +{ + unsigned int val; + + if (adcsync) + adc_channel |= UCB_ADC_SYNC_ENA; + + ucb1400_reg_write(ac97, UCB_ADC_CR, UCB_ADC_ENA | adc_channel); + ucb1400_reg_write(ac97, UCB_ADC_CR, UCB_ADC_ENA | adc_channel | + UCB_ADC_START); + + while (!((val = ucb1400_reg_read(ac97, UCB_ADC_DATA)) + & UCB_ADC_DAT_VALID)) + schedule_timeout_uninterruptible(1); + + return val & UCB_ADC_DAT_MASK; +} +EXPORT_SYMBOL_GPL(ucb1400_adc_read); + +static int ucb1400_core_probe(struct device *dev) +{ + int err; + struct ucb1400 *ucb; + struct ucb1400_ts ucb_ts; + struct ucb1400_gpio ucb_gpio; + struct snd_ac97 *ac97; + struct ucb1400_pdata *pdata = dev_get_platdata(dev); + + memset(&ucb_ts, 0, sizeof(ucb_ts)); + memset(&ucb_gpio, 0, sizeof(ucb_gpio)); + + ucb = kzalloc(sizeof(struct ucb1400), GFP_KERNEL); + if (!ucb) { + err = -ENOMEM; + goto err; + } + + dev_set_drvdata(dev, ucb); + + ac97 = to_ac97_t(dev); + + ucb_ts.id = ucb1400_reg_read(ac97, UCB_ID); + if (ucb_ts.id != UCB_ID_1400) { + err = -ENODEV; + goto err0; + } + + /* GPIO */ + ucb_gpio.ac97 = ac97; + if (pdata) { + ucb_gpio.gpio_setup = pdata->gpio_setup; + ucb_gpio.gpio_teardown = pdata->gpio_teardown; + ucb_gpio.gpio_offset = pdata->gpio_offset; + } + ucb->ucb1400_gpio = platform_device_alloc("ucb1400_gpio", -1); + if (!ucb->ucb1400_gpio) { + err = -ENOMEM; + goto err0; + } + err = platform_device_add_data(ucb->ucb1400_gpio, &ucb_gpio, + sizeof(ucb_gpio)); + if (err) + goto err1; + err = platform_device_add(ucb->ucb1400_gpio); + if (err) + goto err1; + + /* TOUCHSCREEN */ + ucb_ts.ac97 = ac97; + + if (pdata != NULL && pdata->irq >= 0) + ucb_ts.irq = pdata->irq; + else + ucb_ts.irq = -1; + + ucb->ucb1400_ts = platform_device_alloc("ucb1400_ts", -1); + if (!ucb->ucb1400_ts) { + err = -ENOMEM; + goto err2; + } + err = platform_device_add_data(ucb->ucb1400_ts, &ucb_ts, + sizeof(ucb_ts)); + if (err) + goto err3; + err = platform_device_add(ucb->ucb1400_ts); + if (err) + goto err3; + + return 0; + +err3: + platform_device_put(ucb->ucb1400_ts); +err2: + platform_device_del(ucb->ucb1400_gpio); +err1: + platform_device_put(ucb->ucb1400_gpio); +err0: + kfree(ucb); +err: + return err; +} + +static int ucb1400_core_remove(struct device *dev) +{ + struct ucb1400 *ucb = dev_get_drvdata(dev); + + platform_device_unregister(ucb->ucb1400_ts); + platform_device_unregister(ucb->ucb1400_gpio); + + kfree(ucb); + return 0; +} + +static struct device_driver ucb1400_core_driver = { + .name = "ucb1400_core", + .bus = &ac97_bus_type, + .probe = ucb1400_core_probe, + .remove = ucb1400_core_remove, +}; + +static int __init ucb1400_core_init(void) +{ + return driver_register(&ucb1400_core_driver); +} + +static void __exit ucb1400_core_exit(void) +{ + driver_unregister(&ucb1400_core_driver); +} + +module_init(ucb1400_core_init); +module_exit(ucb1400_core_exit); + +MODULE_DESCRIPTION("Philips UCB1400 driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/ucb1x00-assabet.c b/kernel/drivers/mfd/ucb1x00-assabet.c new file mode 100644 index 000000000..b63c0756a --- /dev/null +++ b/kernel/drivers/mfd/ucb1x00-assabet.c @@ -0,0 +1,106 @@ +/* + * linux/drivers/mfd/ucb1x00-assabet.c + * + * Copyright (C) 2001-2003 Russell King, All Rights Reserved. + * + * 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. + * + * We handle the machine-specific bits of the UCB1x00 driver here. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/gpio_keys.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <linux/proc_fs.h> +#include <linux/mfd/ucb1x00.h> + +#define UCB1X00_ATTR(name,input)\ +static ssize_t name##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ucb1x00 *ucb = classdev_to_ucb1x00(dev); \ + int val; \ + ucb1x00_adc_enable(ucb); \ + val = ucb1x00_adc_read(ucb, input, UCB_NOSYNC); \ + ucb1x00_adc_disable(ucb); \ + return sprintf(buf, "%d\n", val); \ +} \ +static DEVICE_ATTR(name,0444,name##_show,NULL) + +UCB1X00_ATTR(vbatt, UCB_ADC_INP_AD1); +UCB1X00_ATTR(vcharger, UCB_ADC_INP_AD0); +UCB1X00_ATTR(batt_temp, UCB_ADC_INP_AD2); + +static int ucb1x00_assabet_add(struct ucb1x00_dev *dev) +{ + struct ucb1x00 *ucb = dev->ucb; + struct platform_device *pdev; + struct gpio_keys_platform_data keys; + static struct gpio_keys_button buttons[6]; + unsigned i; + + memset(buttons, 0, sizeof(buttons)); + memset(&keys, 0, sizeof(keys)); + + for (i = 0; i < ARRAY_SIZE(buttons); i++) { + buttons[i].code = BTN_0 + i; + buttons[i].gpio = ucb->gpio.base + i; + buttons[i].type = EV_KEY; + buttons[i].can_disable = true; + } + + keys.buttons = buttons; + keys.nbuttons = ARRAY_SIZE(buttons); + keys.poll_interval = 50; + keys.name = "ucb1x00"; + + pdev = platform_device_register_data(&ucb->dev, "gpio-keys", -1, + &keys, sizeof(keys)); + + device_create_file(&ucb->dev, &dev_attr_vbatt); + device_create_file(&ucb->dev, &dev_attr_vcharger); + device_create_file(&ucb->dev, &dev_attr_batt_temp); + + dev->priv = pdev; + return 0; +} + +static void ucb1x00_assabet_remove(struct ucb1x00_dev *dev) +{ + struct platform_device *pdev = dev->priv; + + if (!IS_ERR(pdev)) + platform_device_unregister(pdev); + + device_remove_file(&dev->ucb->dev, &dev_attr_batt_temp); + device_remove_file(&dev->ucb->dev, &dev_attr_vcharger); + device_remove_file(&dev->ucb->dev, &dev_attr_vbatt); +} + +static struct ucb1x00_driver ucb1x00_assabet_driver = { + .add = ucb1x00_assabet_add, + .remove = ucb1x00_assabet_remove, +}; + +static int __init ucb1x00_assabet_init(void) +{ + return ucb1x00_register_driver(&ucb1x00_assabet_driver); +} + +static void __exit ucb1x00_assabet_exit(void) +{ + ucb1x00_unregister_driver(&ucb1x00_assabet_driver); +} + +module_init(ucb1x00_assabet_init); +module_exit(ucb1x00_assabet_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("Assabet noddy testing only example ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/ucb1x00-core.c b/kernel/drivers/mfd/ucb1x00-core.c new file mode 100644 index 000000000..58ea9fdd3 --- /dev/null +++ b/kernel/drivers/mfd/ucb1x00-core.c @@ -0,0 +1,787 @@ +/* + * linux/drivers/mfd/ucb1x00-core.c + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * + * 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. + * + * The UCB1x00 core driver provides basic services for handling IO, + * the ADC, interrupts, and accessing registers. It is designed + * such that everything goes through this layer, thereby providing + * a consistent locking methodology, as well as allowing the drivers + * to be used on other non-MCP-enabled hardware platforms. + * + * Note that all locks are private to this file. Nothing else may + * touch them. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/mfd/ucb1x00.h> +#include <linux/pm.h> +#include <linux/gpio.h> + +static DEFINE_MUTEX(ucb1x00_mutex); +static LIST_HEAD(ucb1x00_drivers); +static LIST_HEAD(ucb1x00_devices); + +/** + * ucb1x00_io_set_dir - set IO direction + * @ucb: UCB1x00 structure describing chip + * @in: bitfield of IO pins to be set as inputs + * @out: bitfield of IO pins to be set as outputs + * + * Set the IO direction of the ten general purpose IO pins on + * the UCB1x00 chip. The @in bitfield has priority over the + * @out bitfield, in that if you specify a pin as both input + * and output, it will end up as an input. + * + * ucb1x00_enable must have been called to enable the comms + * before using this function. + * + * This function takes a spinlock, disabling interrupts. + */ +void ucb1x00_io_set_dir(struct ucb1x00 *ucb, unsigned int in, unsigned int out) +{ + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_dir |= out; + ucb->io_dir &= ~in; + + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + spin_unlock_irqrestore(&ucb->io_lock, flags); +} + +/** + * ucb1x00_io_write - set or clear IO outputs + * @ucb: UCB1x00 structure describing chip + * @set: bitfield of IO pins to set to logic '1' + * @clear: bitfield of IO pins to set to logic '0' + * + * Set the IO output state of the specified IO pins. The value + * is retained if the pins are subsequently configured as inputs. + * The @clear bitfield has priority over the @set bitfield - + * outputs will be cleared. + * + * ucb1x00_enable must have been called to enable the comms + * before using this function. + * + * This function takes a spinlock, disabling interrupts. + */ +void ucb1x00_io_write(struct ucb1x00 *ucb, unsigned int set, unsigned int clear) +{ + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_out |= set; + ucb->io_out &= ~clear; + + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + spin_unlock_irqrestore(&ucb->io_lock, flags); +} + +/** + * ucb1x00_io_read - read the current state of the IO pins + * @ucb: UCB1x00 structure describing chip + * + * Return a bitfield describing the logic state of the ten + * general purpose IO pins. + * + * ucb1x00_enable must have been called to enable the comms + * before using this function. + * + * This function does not take any mutexes or spinlocks. + */ +unsigned int ucb1x00_io_read(struct ucb1x00 *ucb) +{ + return ucb1x00_reg_read(ucb, UCB_IO_DATA); +} + +static void ucb1x00_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + if (value) + ucb->io_out |= 1 << offset; + else + ucb->io_out &= ~(1 << offset); + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + ucb1x00_disable(ucb); + spin_unlock_irqrestore(&ucb->io_lock, flags); +} + +static int ucb1x00_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned val; + + ucb1x00_enable(ucb); + val = ucb1x00_reg_read(ucb, UCB_IO_DATA); + ucb1x00_disable(ucb); + + return val & (1 << offset); +} + +static int ucb1x00_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned long flags; + + spin_lock_irqsave(&ucb->io_lock, flags); + ucb->io_dir &= ~(1 << offset); + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + ucb1x00_disable(ucb); + spin_unlock_irqrestore(&ucb->io_lock, flags); + + return 0; +} + +static int ucb1x00_gpio_direction_output(struct gpio_chip *chip, unsigned offset + , int value) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + unsigned long flags; + unsigned old, mask = 1 << offset; + + spin_lock_irqsave(&ucb->io_lock, flags); + old = ucb->io_out; + if (value) + ucb->io_out |= mask; + else + ucb->io_out &= ~mask; + + ucb1x00_enable(ucb); + if (old != ucb->io_out) + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + + if (!(ucb->io_dir & mask)) { + ucb->io_dir |= mask; + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + } + ucb1x00_disable(ucb); + spin_unlock_irqrestore(&ucb->io_lock, flags); + + return 0; +} + +static int ucb1x00_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + + return ucb->irq_base > 0 ? ucb->irq_base + offset : -ENXIO; +} + +/* + * UCB1300 data sheet says we must: + * 1. enable ADC => 5us (including reference startup time) + * 2. select input => 51*tsibclk => 4.3us + * 3. start conversion => 102*tsibclk => 8.5us + * (tsibclk = 1/11981000) + * Period between SIB 128-bit frames = 10.7us + */ + +/** + * ucb1x00_adc_enable - enable the ADC converter + * @ucb: UCB1x00 structure describing chip + * + * Enable the ucb1x00 and ADC converter on the UCB1x00 for use. + * Any code wishing to use the ADC converter must call this + * function prior to using it. + * + * This function takes the ADC mutex to prevent two or more + * concurrent uses, and therefore may sleep. As a result, it + * can only be called from process context, not interrupt + * context. + * + * You should release the ADC as soon as possible using + * ucb1x00_adc_disable. + */ +void ucb1x00_adc_enable(struct ucb1x00 *ucb) +{ + mutex_lock(&ucb->adc_mutex); + + ucb->adc_cr |= UCB_ADC_ENA; + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr); +} + +/** + * ucb1x00_adc_read - read the specified ADC channel + * @ucb: UCB1x00 structure describing chip + * @adc_channel: ADC channel mask + * @sync: wait for syncronisation pulse. + * + * Start an ADC conversion and wait for the result. Note that + * synchronised ADC conversions (via the ADCSYNC pin) must wait + * until the trigger is asserted and the conversion is finished. + * + * This function currently spins waiting for the conversion to + * complete (2 frames max without sync). + * + * If called for a synchronised ADC conversion, it may sleep + * with the ADC mutex held. + */ +unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync) +{ + unsigned int val; + + if (sync) + adc_channel |= UCB_ADC_SYNC_ENA; + + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr | adc_channel); + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr | adc_channel | UCB_ADC_START); + + for (;;) { + val = ucb1x00_reg_read(ucb, UCB_ADC_DATA); + if (val & UCB_ADC_DAT_VAL) + break; + /* yield to other processes */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + + return UCB_ADC_DAT(val); +} + +/** + * ucb1x00_adc_disable - disable the ADC converter + * @ucb: UCB1x00 structure describing chip + * + * Disable the ADC converter and release the ADC mutex. + */ +void ucb1x00_adc_disable(struct ucb1x00 *ucb) +{ + ucb->adc_cr &= ~UCB_ADC_ENA; + ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr); + ucb1x00_disable(ucb); + + mutex_unlock(&ucb->adc_mutex); +} + +/* + * UCB1x00 Interrupt handling. + * + * The UCB1x00 can generate interrupts when the SIBCLK is stopped. + * Since we need to read an internal register, we must re-enable + * SIBCLK to talk to the chip. We leave the clock running until + * we have finished processing all interrupts from the chip. + */ +static void ucb1x00_irq(unsigned int irq, struct irq_desc *desc) +{ + struct ucb1x00 *ucb = irq_desc_get_handler_data(desc); + unsigned int isr, i; + + ucb1x00_enable(ucb); + isr = ucb1x00_reg_read(ucb, UCB_IE_STATUS); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, isr); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + for (i = 0; i < 16 && isr; i++, isr >>= 1, irq++) + if (isr & 1) + generic_handle_irq(ucb->irq_base + i); + ucb1x00_disable(ucb); +} + +static void ucb1x00_irq_update(struct ucb1x00 *ucb, unsigned mask) +{ + ucb1x00_enable(ucb); + if (ucb->irq_ris_enbl & mask) + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_mask); + if (ucb->irq_fal_enbl & mask) + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_mask); + ucb1x00_disable(ucb); +} + +static void ucb1x00_irq_noop(struct irq_data *data) +{ +} + +static void ucb1x00_irq_mask(struct irq_data *data) +{ + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + unsigned mask = 1 << (data->irq - ucb->irq_base); + + raw_spin_lock(&ucb->irq_lock); + ucb->irq_mask &= ~mask; + ucb1x00_irq_update(ucb, mask); + raw_spin_unlock(&ucb->irq_lock); +} + +static void ucb1x00_irq_unmask(struct irq_data *data) +{ + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + unsigned mask = 1 << (data->irq - ucb->irq_base); + + raw_spin_lock(&ucb->irq_lock); + ucb->irq_mask |= mask; + ucb1x00_irq_update(ucb, mask); + raw_spin_unlock(&ucb->irq_lock); +} + +static int ucb1x00_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + unsigned mask = 1 << (data->irq - ucb->irq_base); + + raw_spin_lock(&ucb->irq_lock); + if (type & IRQ_TYPE_EDGE_RISING) + ucb->irq_ris_enbl |= mask; + else + ucb->irq_ris_enbl &= ~mask; + + if (type & IRQ_TYPE_EDGE_FALLING) + ucb->irq_fal_enbl |= mask; + else + ucb->irq_fal_enbl &= ~mask; + if (ucb->irq_mask & mask) { + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_mask); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_mask); + } + raw_spin_unlock(&ucb->irq_lock); + + return 0; +} + +static int ucb1x00_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + struct ucb1x00_plat_data *pdata = ucb->mcp->attached_device.platform_data; + unsigned mask = 1 << (data->irq - ucb->irq_base); + + if (!pdata || !pdata->can_wakeup) + return -EINVAL; + + raw_spin_lock(&ucb->irq_lock); + if (on) + ucb->irq_wake |= mask; + else + ucb->irq_wake &= ~mask; + raw_spin_unlock(&ucb->irq_lock); + + return 0; +} + +static struct irq_chip ucb1x00_irqchip = { + .name = "ucb1x00", + .irq_ack = ucb1x00_irq_noop, + .irq_mask = ucb1x00_irq_mask, + .irq_unmask = ucb1x00_irq_unmask, + .irq_set_type = ucb1x00_irq_set_type, + .irq_set_wake = ucb1x00_irq_set_wake, +}; + +static int ucb1x00_add_dev(struct ucb1x00 *ucb, struct ucb1x00_driver *drv) +{ + struct ucb1x00_dev *dev; + int ret; + + dev = kmalloc(sizeof(struct ucb1x00_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->ucb = ucb; + dev->drv = drv; + + ret = drv->add(dev); + if (ret) { + kfree(dev); + return ret; + } + + list_add_tail(&dev->dev_node, &ucb->devs); + list_add_tail(&dev->drv_node, &drv->devs); + + return ret; +} + +static void ucb1x00_remove_dev(struct ucb1x00_dev *dev) +{ + dev->drv->remove(dev); + list_del(&dev->dev_node); + list_del(&dev->drv_node); + kfree(dev); +} + +/* + * Try to probe our interrupt, rather than relying on lots of + * hard-coded machine dependencies. For reference, the expected + * IRQ mappings are: + * + * Machine Default IRQ + * adsbitsy IRQ_GPCIN4 + * cerf IRQ_GPIO_UCB1200_IRQ + * flexanet IRQ_GPIO_GUI + * freebird IRQ_GPIO_FREEBIRD_UCB1300_IRQ + * graphicsclient ADS_EXT_IRQ(8) + * graphicsmaster ADS_EXT_IRQ(8) + * lart LART_IRQ_UCB1200 + * omnimeter IRQ_GPIO23 + * pfs168 IRQ_GPIO_UCB1300_IRQ + * simpad IRQ_GPIO_UCB1300_IRQ + * shannon SHANNON_IRQ_GPIO_IRQ_CODEC + * yopy IRQ_GPIO_UCB1200_IRQ + */ +static int ucb1x00_detect_irq(struct ucb1x00 *ucb) +{ + unsigned long mask; + + mask = probe_irq_on(); + if (!mask) { + probe_irq_off(mask); + return NO_IRQ; + } + + /* + * Enable the ADC interrupt. + */ + ucb1x00_reg_write(ucb, UCB_IE_RIS, UCB_IE_ADC); + ucb1x00_reg_write(ucb, UCB_IE_FAL, UCB_IE_ADC); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0xffff); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + /* + * Cause an ADC interrupt. + */ + ucb1x00_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA); + ucb1x00_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START); + + /* + * Wait for the conversion to complete. + */ + while ((ucb1x00_reg_read(ucb, UCB_ADC_DATA) & UCB_ADC_DAT_VAL) == 0); + ucb1x00_reg_write(ucb, UCB_ADC_CR, 0); + + /* + * Disable and clear interrupt. + */ + ucb1x00_reg_write(ucb, UCB_IE_RIS, 0); + ucb1x00_reg_write(ucb, UCB_IE_FAL, 0); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0xffff); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + /* + * Read triggered interrupt. + */ + return probe_irq_off(mask); +} + +static void ucb1x00_release(struct device *dev) +{ + struct ucb1x00 *ucb = classdev_to_ucb1x00(dev); + kfree(ucb); +} + +static struct class ucb1x00_class = { + .name = "ucb1x00", + .dev_release = ucb1x00_release, +}; + +static int ucb1x00_probe(struct mcp *mcp) +{ + struct ucb1x00_plat_data *pdata = mcp->attached_device.platform_data; + struct ucb1x00_driver *drv; + struct ucb1x00 *ucb; + unsigned id, i, irq_base; + int ret = -ENODEV; + + /* Tell the platform to deassert the UCB1x00 reset */ + if (pdata && pdata->reset) + pdata->reset(UCB_RST_PROBE); + + mcp_enable(mcp); + id = mcp_reg_read(mcp, UCB_ID); + mcp_disable(mcp); + + if (id != UCB_ID_1200 && id != UCB_ID_1300 && id != UCB_ID_TC35143) { + printk(KERN_WARNING "UCB1x00 ID not found: %04x\n", id); + goto out; + } + + ucb = kzalloc(sizeof(struct ucb1x00), GFP_KERNEL); + ret = -ENOMEM; + if (!ucb) + goto out; + + device_initialize(&ucb->dev); + ucb->dev.class = &ucb1x00_class; + ucb->dev.parent = &mcp->attached_device; + dev_set_name(&ucb->dev, "ucb1x00"); + + raw_spin_lock_init(&ucb->irq_lock); + spin_lock_init(&ucb->io_lock); + mutex_init(&ucb->adc_mutex); + + ucb->id = id; + ucb->mcp = mcp; + + ret = device_add(&ucb->dev); + if (ret) + goto err_dev_add; + + ucb1x00_enable(ucb); + ucb->irq = ucb1x00_detect_irq(ucb); + ucb1x00_disable(ucb); + if (ucb->irq == NO_IRQ) { + dev_err(&ucb->dev, "IRQ probe failed\n"); + ret = -ENODEV; + goto err_no_irq; + } + + ucb->gpio.base = -1; + irq_base = pdata ? pdata->irq_base : 0; + ucb->irq_base = irq_alloc_descs(-1, irq_base, 16, -1); + if (ucb->irq_base < 0) { + dev_err(&ucb->dev, "unable to allocate 16 irqs: %d\n", + ucb->irq_base); + ret = ucb->irq_base; + goto err_irq_alloc; + } + + for (i = 0; i < 16; i++) { + unsigned irq = ucb->irq_base + i; + + irq_set_chip_and_handler(irq, &ucb1x00_irqchip, handle_edge_irq); + irq_set_chip_data(irq, ucb); + set_irq_flags(irq, IRQF_VALID | IRQ_NOREQUEST); + } + + irq_set_irq_type(ucb->irq, IRQ_TYPE_EDGE_RISING); + irq_set_handler_data(ucb->irq, ucb); + irq_set_chained_handler(ucb->irq, ucb1x00_irq); + + if (pdata && pdata->gpio_base) { + ucb->gpio.label = dev_name(&ucb->dev); + ucb->gpio.dev = &ucb->dev; + ucb->gpio.owner = THIS_MODULE; + ucb->gpio.base = pdata->gpio_base; + ucb->gpio.ngpio = 10; + ucb->gpio.set = ucb1x00_gpio_set; + ucb->gpio.get = ucb1x00_gpio_get; + ucb->gpio.direction_input = ucb1x00_gpio_direction_input; + ucb->gpio.direction_output = ucb1x00_gpio_direction_output; + ucb->gpio.to_irq = ucb1x00_to_irq; + ret = gpiochip_add(&ucb->gpio); + if (ret) + goto err_gpio_add; + } else + dev_info(&ucb->dev, "gpio_base not set so no gpiolib support"); + + mcp_set_drvdata(mcp, ucb); + + if (pdata) + device_set_wakeup_capable(&ucb->dev, pdata->can_wakeup); + + INIT_LIST_HEAD(&ucb->devs); + mutex_lock(&ucb1x00_mutex); + list_add_tail(&ucb->node, &ucb1x00_devices); + list_for_each_entry(drv, &ucb1x00_drivers, node) { + ucb1x00_add_dev(ucb, drv); + } + mutex_unlock(&ucb1x00_mutex); + + return ret; + + err_gpio_add: + irq_set_chained_handler(ucb->irq, NULL); + err_irq_alloc: + if (ucb->irq_base > 0) + irq_free_descs(ucb->irq_base, 16); + err_no_irq: + device_del(&ucb->dev); + err_dev_add: + put_device(&ucb->dev); + out: + if (pdata && pdata->reset) + pdata->reset(UCB_RST_PROBE_FAIL); + return ret; +} + +static void ucb1x00_remove(struct mcp *mcp) +{ + struct ucb1x00_plat_data *pdata = mcp->attached_device.platform_data; + struct ucb1x00 *ucb = mcp_get_drvdata(mcp); + struct list_head *l, *n; + + mutex_lock(&ucb1x00_mutex); + list_del(&ucb->node); + list_for_each_safe(l, n, &ucb->devs) { + struct ucb1x00_dev *dev = list_entry(l, struct ucb1x00_dev, dev_node); + ucb1x00_remove_dev(dev); + } + mutex_unlock(&ucb1x00_mutex); + + if (ucb->gpio.base != -1) + gpiochip_remove(&ucb->gpio); + + irq_set_chained_handler(ucb->irq, NULL); + irq_free_descs(ucb->irq_base, 16); + device_unregister(&ucb->dev); + + if (pdata && pdata->reset) + pdata->reset(UCB_RST_REMOVE); +} + +int ucb1x00_register_driver(struct ucb1x00_driver *drv) +{ + struct ucb1x00 *ucb; + + INIT_LIST_HEAD(&drv->devs); + mutex_lock(&ucb1x00_mutex); + list_add_tail(&drv->node, &ucb1x00_drivers); + list_for_each_entry(ucb, &ucb1x00_devices, node) { + ucb1x00_add_dev(ucb, drv); + } + mutex_unlock(&ucb1x00_mutex); + return 0; +} + +void ucb1x00_unregister_driver(struct ucb1x00_driver *drv) +{ + struct list_head *n, *l; + + mutex_lock(&ucb1x00_mutex); + list_del(&drv->node); + list_for_each_safe(l, n, &drv->devs) { + struct ucb1x00_dev *dev = list_entry(l, struct ucb1x00_dev, drv_node); + ucb1x00_remove_dev(dev); + } + mutex_unlock(&ucb1x00_mutex); +} + +#ifdef CONFIG_PM_SLEEP +static int ucb1x00_suspend(struct device *dev) +{ + struct ucb1x00_plat_data *pdata = dev_get_platdata(dev); + struct ucb1x00 *ucb = dev_get_drvdata(dev); + struct ucb1x00_dev *udev; + + mutex_lock(&ucb1x00_mutex); + list_for_each_entry(udev, &ucb->devs, dev_node) { + if (udev->drv->suspend) + udev->drv->suspend(udev); + } + mutex_unlock(&ucb1x00_mutex); + + if (ucb->irq_wake) { + unsigned long flags; + + raw_spin_lock_irqsave(&ucb->irq_lock, flags); + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_wake); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_wake); + ucb1x00_disable(ucb); + raw_spin_unlock_irqrestore(&ucb->irq_lock, flags); + + enable_irq_wake(ucb->irq); + } else if (pdata && pdata->reset) + pdata->reset(UCB_RST_SUSPEND); + + return 0; +} + +static int ucb1x00_resume(struct device *dev) +{ + struct ucb1x00_plat_data *pdata = dev_get_platdata(dev); + struct ucb1x00 *ucb = dev_get_drvdata(dev); + struct ucb1x00_dev *udev; + + if (!ucb->irq_wake && pdata && pdata->reset) + pdata->reset(UCB_RST_RESUME); + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + + if (ucb->irq_wake) { + unsigned long flags; + + raw_spin_lock_irqsave(&ucb->irq_lock, flags); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_mask); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_mask); + raw_spin_unlock_irqrestore(&ucb->irq_lock, flags); + + disable_irq_wake(ucb->irq); + } + ucb1x00_disable(ucb); + + mutex_lock(&ucb1x00_mutex); + list_for_each_entry(udev, &ucb->devs, dev_node) { + if (udev->drv->resume) + udev->drv->resume(udev); + } + mutex_unlock(&ucb1x00_mutex); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ucb1x00_pm_ops, ucb1x00_suspend, ucb1x00_resume); + +static struct mcp_driver ucb1x00_driver = { + .drv = { + .name = "ucb1x00", + .owner = THIS_MODULE, + .pm = &ucb1x00_pm_ops, + }, + .probe = ucb1x00_probe, + .remove = ucb1x00_remove, +}; + +static int __init ucb1x00_init(void) +{ + int ret = class_register(&ucb1x00_class); + if (ret == 0) { + ret = mcp_driver_register(&ucb1x00_driver); + if (ret) + class_unregister(&ucb1x00_class); + } + return ret; +} + +static void __exit ucb1x00_exit(void) +{ + mcp_driver_unregister(&ucb1x00_driver); + class_unregister(&ucb1x00_class); +} + +module_init(ucb1x00_init); +module_exit(ucb1x00_exit); + +EXPORT_SYMBOL(ucb1x00_io_set_dir); +EXPORT_SYMBOL(ucb1x00_io_write); +EXPORT_SYMBOL(ucb1x00_io_read); + +EXPORT_SYMBOL(ucb1x00_adc_enable); +EXPORT_SYMBOL(ucb1x00_adc_read); +EXPORT_SYMBOL(ucb1x00_adc_disable); + +EXPORT_SYMBOL(ucb1x00_register_driver); +EXPORT_SYMBOL(ucb1x00_unregister_driver); + +MODULE_ALIAS("mcp:ucb1x00"); +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("UCB1x00 core driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/ucb1x00-ts.c b/kernel/drivers/mfd/ucb1x00-ts.c new file mode 100644 index 000000000..1e0e20c0e --- /dev/null +++ b/kernel/drivers/mfd/ucb1x00-ts.c @@ -0,0 +1,448 @@ +/* + * Touchscreen driver for UCB1x00-based touchscreens + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * Copyright (C) 2005 Pavel Machek + * + * 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. + * + * 21-Jan-2002 <jco@ict.es> : + * + * Added support for synchronous A/D mode. This mode is useful to + * avoid noise induced in the touchpanel by the LCD, provided that + * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin. + * It is important to note that the signal connected to the ADCSYNC + * pin should provide pulses even when the LCD is blanked, otherwise + * a pen touch needed to unblank the LCD will never be read. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/freezer.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/mfd/ucb1x00.h> + +#include <mach/collie.h> +#include <asm/mach-types.h> + + + +struct ucb1x00_ts { + struct input_dev *idev; + struct ucb1x00 *ucb; + + spinlock_t irq_lock; + unsigned irq_disabled; + wait_queue_head_t irq_wait; + struct task_struct *rtask; + u16 x_res; + u16 y_res; + + unsigned int adcsync:1; +}; + +static int adcsync; + +static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y) +{ + struct input_dev *idev = ts->idev; + + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + input_report_abs(idev, ABS_PRESSURE, pressure); + input_report_key(idev, BTN_TOUCH, 1); + input_sync(idev); +} + +static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts) +{ + struct input_dev *idev = ts->idev; + + input_report_abs(idev, ABS_PRESSURE, 0); + input_report_key(idev, BTN_TOUCH, 0); + input_sync(idev); +} + +/* + * Switch to interrupt mode. + */ +static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) { + ucb1x00_io_write(ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync); + } else { + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); + } +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + else { + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + } + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + else { + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + } + + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync); +} + +/* + * Switch to X plate resistance mode. Set MX to ground, PX to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +/* + * Switch to Y plate resistance mode. Set MY to ground, PY to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts) +{ + unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR); + + if (machine_is_collie()) + return (!(val & (UCB_TS_CR_TSPX_LOW))); + else + return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)); +} + +/* + * This is a RT kernel thread that handles the ADC accesses + * (mainly so we can use semaphores in the UCB1200 core code + * to serialise accesses to the ADC). + */ +static int ucb1x00_thread(void *_ts) +{ + struct ucb1x00_ts *ts = _ts; + DECLARE_WAITQUEUE(wait, current); + bool frozen, ignore = false; + int valid = 0; + + set_freezable(); + add_wait_queue(&ts->irq_wait, &wait); + while (!kthread_freezable_should_stop(&frozen)) { + unsigned int x, y, p; + signed long timeout; + + if (frozen) + ignore = true; + + ucb1x00_adc_enable(ts->ucb); + + x = ucb1x00_ts_read_xpos(ts); + y = ucb1x00_ts_read_ypos(ts); + p = ucb1x00_ts_read_pressure(ts); + + /* + * Switch back to interrupt mode. + */ + ucb1x00_ts_mode_int(ts); + ucb1x00_adc_disable(ts->ucb); + + msleep(10); + + ucb1x00_enable(ts->ucb); + + + if (ucb1x00_ts_pen_down(ts)) { + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irq(&ts->irq_lock); + if (ts->irq_disabled) { + ts->irq_disabled = 0; + enable_irq(ts->ucb->irq_base + UCB_IRQ_TSPX); + } + spin_unlock_irq(&ts->irq_lock); + ucb1x00_disable(ts->ucb); + + /* + * If we spat out a valid sample set last time, + * spit out a "pen off" sample here. + */ + if (valid) { + ucb1x00_ts_event_release(ts); + valid = 0; + } + + timeout = MAX_SCHEDULE_TIMEOUT; + } else { + ucb1x00_disable(ts->ucb); + + /* + * Filtering is policy. Policy belongs in user + * space. We therefore leave it to user space + * to do any filtering they please. + */ + if (!ignore) { + ucb1x00_ts_evt_add(ts, p, x, y); + valid = 1; + } + + set_current_state(TASK_INTERRUPTIBLE); + timeout = HZ / 100; + } + + schedule_timeout(timeout); + } + + remove_wait_queue(&ts->irq_wait, &wait); + + ts->rtask = NULL; + return 0; +} + +/* + * We only detect touch screen _touches_ with this interrupt + * handler, and even then we just schedule our task. + */ +static irqreturn_t ucb1x00_ts_irq(int irq, void *id) +{ + struct ucb1x00_ts *ts = id; + + spin_lock(&ts->irq_lock); + ts->irq_disabled = 1; + disable_irq_nosync(ts->ucb->irq_base + UCB_IRQ_TSPX); + spin_unlock(&ts->irq_lock); + wake_up(&ts->irq_wait); + + return IRQ_HANDLED; +} + +static int ucb1x00_ts_open(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = input_get_drvdata(idev); + unsigned long flags = 0; + int ret = 0; + + BUG_ON(ts->rtask); + + if (machine_is_collie()) + flags = IRQF_TRIGGER_RISING; + else + flags = IRQF_TRIGGER_FALLING; + + ts->irq_disabled = 0; + + init_waitqueue_head(&ts->irq_wait); + ret = request_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ucb1x00_ts_irq, + flags, "ucb1x00-ts", ts); + if (ret < 0) + goto out; + + /* + * If we do this at all, we should allow the user to + * measure and read the X and Y resistance at any time. + */ + ucb1x00_adc_enable(ts->ucb); + ts->x_res = ucb1x00_ts_read_xres(ts); + ts->y_res = ucb1x00_ts_read_yres(ts); + ucb1x00_adc_disable(ts->ucb); + + ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd"); + if (!IS_ERR(ts->rtask)) { + ret = 0; + } else { + free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts); + ts->rtask = NULL; + ret = -EFAULT; + } + + out: + return ret; +} + +/* + * Release touchscreen resources. Disable IRQs. + */ +static void ucb1x00_ts_close(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = input_get_drvdata(idev); + + if (ts->rtask) + kthread_stop(ts->rtask); + + ucb1x00_enable(ts->ucb); + free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0); + ucb1x00_disable(ts->ucb); +} + + +/* + * Initialisation. + */ +static int ucb1x00_ts_add(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts; + struct input_dev *idev; + int err; + + ts = kzalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL); + idev = input_allocate_device(); + if (!ts || !idev) { + err = -ENOMEM; + goto fail; + } + + ts->ucb = dev->ucb; + ts->idev = idev; + ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC; + spin_lock_init(&ts->irq_lock); + + idev->name = "Touchscreen panel"; + idev->id.product = ts->ucb->id; + idev->open = ucb1x00_ts_open; + idev->close = ucb1x00_ts_close; + idev->dev.parent = &ts->ucb->dev; + + idev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); + idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_drvdata(idev, ts); + + ucb1x00_adc_enable(ts->ucb); + ts->x_res = ucb1x00_ts_read_xres(ts); + ts->y_res = ucb1x00_ts_read_yres(ts); + ucb1x00_adc_disable(ts->ucb); + + input_set_abs_params(idev, ABS_X, 0, ts->x_res, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, ts->y_res, 0, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, 0, 0, 0); + + err = input_register_device(idev); + if (err) + goto fail; + + dev->priv = ts; + + return 0; + + fail: + input_free_device(idev); + kfree(ts); + return err; +} + +static void ucb1x00_ts_remove(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts = dev->priv; + + input_unregister_device(ts->idev); + kfree(ts); +} + +static struct ucb1x00_driver ucb1x00_ts_driver = { + .add = ucb1x00_ts_add, + .remove = ucb1x00_ts_remove, +}; + +static int __init ucb1x00_ts_init(void) +{ + return ucb1x00_register_driver(&ucb1x00_ts_driver); +} + +static void __exit ucb1x00_ts_exit(void) +{ + ucb1x00_unregister_driver(&ucb1x00_ts_driver); +} + +module_param(adcsync, int, 0444); +module_init(ucb1x00_ts_init); +module_exit(ucb1x00_ts_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("UCB1x00 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/vexpress-sysreg.c b/kernel/drivers/mfd/vexpress-sysreg.c new file mode 100644 index 000000000..3e628df92 --- /dev/null +++ b/kernel/drivers/mfd/vexpress-sysreg.c @@ -0,0 +1,235 @@ +/* + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#include <linux/basic_mmio_gpio.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mfd/core.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_data/syscon.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/vexpress.h> + +#define SYS_ID 0x000 +#define SYS_SW 0x004 +#define SYS_LED 0x008 +#define SYS_100HZ 0x024 +#define SYS_FLAGSSET 0x030 +#define SYS_FLAGSCLR 0x034 +#define SYS_NVFLAGS 0x038 +#define SYS_NVFLAGSSET 0x038 +#define SYS_NVFLAGSCLR 0x03c +#define SYS_MCI 0x048 +#define SYS_FLASH 0x04c +#define SYS_CFGSW 0x058 +#define SYS_24MHZ 0x05c +#define SYS_MISC 0x060 +#define SYS_DMA 0x064 +#define SYS_PROCID0 0x084 +#define SYS_PROCID1 0x088 +#define SYS_CFGDATA 0x0a0 +#define SYS_CFGCTRL 0x0a4 +#define SYS_CFGSTAT 0x0a8 + +#define SYS_HBI_MASK 0xfff +#define SYS_PROCIDx_HBI_SHIFT 0 + +#define SYS_MISC_MASTERSITE (1 << 14) + +void vexpress_flags_set(u32 data) +{ + static void __iomem *base; + + if (!base) { + struct device_node *node = of_find_compatible_node(NULL, NULL, + "arm,vexpress-sysreg"); + + base = of_iomap(node, 0); + } + + if (WARN_ON(!base)) + return; + + writel(~0, base + SYS_FLAGSCLR); + writel(data, base + SYS_FLAGSSET); +} + +/* The sysreg block is just a random collection of various functions... */ + +static struct syscon_platform_data vexpress_sysreg_sys_id_pdata = { + .label = "sys_id", +}; + +static struct bgpio_pdata vexpress_sysreg_sys_led_pdata = { + .label = "sys_led", + .base = -1, + .ngpio = 8, +}; + +static struct bgpio_pdata vexpress_sysreg_sys_mci_pdata = { + .label = "sys_mci", + .base = -1, + .ngpio = 2, +}; + +static struct bgpio_pdata vexpress_sysreg_sys_flash_pdata = { + .label = "sys_flash", + .base = -1, + .ngpio = 1, +}; + +static struct syscon_platform_data vexpress_sysreg_sys_misc_pdata = { + .label = "sys_misc", +}; + +static struct syscon_platform_data vexpress_sysreg_sys_procid_pdata = { + .label = "sys_procid", +}; + +static struct mfd_cell vexpress_sysreg_cells[] = { + { + .name = "syscon", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM(SYS_ID, 0x4), + }, + .platform_data = &vexpress_sysreg_sys_id_pdata, + .pdata_size = sizeof(vexpress_sysreg_sys_id_pdata), + }, { + .name = "basic-mmio-gpio", + .of_compatible = "arm,vexpress-sysreg,sys_led", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM_NAMED(SYS_LED, 0x4, "dat"), + }, + .platform_data = &vexpress_sysreg_sys_led_pdata, + .pdata_size = sizeof(vexpress_sysreg_sys_led_pdata), + }, { + .name = "basic-mmio-gpio", + .of_compatible = "arm,vexpress-sysreg,sys_mci", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM_NAMED(SYS_MCI, 0x4, "dat"), + }, + .platform_data = &vexpress_sysreg_sys_mci_pdata, + .pdata_size = sizeof(vexpress_sysreg_sys_mci_pdata), + }, { + .name = "basic-mmio-gpio", + .of_compatible = "arm,vexpress-sysreg,sys_flash", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM_NAMED(SYS_FLASH, 0x4, "dat"), + }, + .platform_data = &vexpress_sysreg_sys_flash_pdata, + .pdata_size = sizeof(vexpress_sysreg_sys_flash_pdata), + }, { + .name = "syscon", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM(SYS_MISC, 0x4), + }, + .platform_data = &vexpress_sysreg_sys_misc_pdata, + .pdata_size = sizeof(vexpress_sysreg_sys_misc_pdata), + }, { + .name = "syscon", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM(SYS_PROCID0, 0x8), + }, + .platform_data = &vexpress_sysreg_sys_procid_pdata, + .pdata_size = sizeof(vexpress_sysreg_sys_procid_pdata), + }, { + .name = "vexpress-syscfg", + .num_resources = 1, + .resources = (struct resource []) { + DEFINE_RES_MEM(SYS_CFGDATA, 0xc), + }, + } +}; + +static int vexpress_sysreg_probe(struct platform_device *pdev) +{ + struct resource *mem; + void __iomem *base; + struct bgpio_chip *mmc_gpio_chip; + int master; + u32 dt_hbi; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -EINVAL; + + base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!base) + return -ENOMEM; + + master = readl(base + SYS_MISC) & SYS_MISC_MASTERSITE ? + VEXPRESS_SITE_DB2 : VEXPRESS_SITE_DB1; + vexpress_config_set_master(master); + + /* Confirm board type against DT property, if available */ + if (of_property_read_u32(of_root, "arm,hbi", &dt_hbi) == 0) { + u32 id = readl(base + (master == VEXPRESS_SITE_DB1 ? + SYS_PROCID0 : SYS_PROCID1)); + u32 hbi = (id >> SYS_PROCIDx_HBI_SHIFT) & SYS_HBI_MASK; + + if (WARN_ON(dt_hbi != hbi)) + dev_warn(&pdev->dev, "DT HBI (%x) is not matching hardware (%x)!\n", + dt_hbi, hbi); + } + + /* + * Duplicated SYS_MCI pseudo-GPIO controller for compatibility with + * older trees using sysreg node for MMC control lines. + */ + mmc_gpio_chip = devm_kzalloc(&pdev->dev, sizeof(*mmc_gpio_chip), + GFP_KERNEL); + if (!mmc_gpio_chip) + return -ENOMEM; + bgpio_init(mmc_gpio_chip, &pdev->dev, 0x4, base + SYS_MCI, + NULL, NULL, NULL, NULL, 0); + mmc_gpio_chip->gc.ngpio = 2; + gpiochip_add(&mmc_gpio_chip->gc); + + return mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, + vexpress_sysreg_cells, + ARRAY_SIZE(vexpress_sysreg_cells), mem, 0, NULL); +} + +static const struct of_device_id vexpress_sysreg_match[] = { + { .compatible = "arm,vexpress-sysreg", }, + {}, +}; + +static struct platform_driver vexpress_sysreg_driver = { + .driver = { + .name = "vexpress-sysreg", + .of_match_table = vexpress_sysreg_match, + }, + .probe = vexpress_sysreg_probe, +}; + +static int __init vexpress_sysreg_init(void) +{ + struct device_node *node; + + /* Need the sysreg early, before any other device... */ + for_each_matching_node(node, vexpress_sysreg_match) + of_platform_device_create(node, NULL, NULL); + + return platform_driver_register(&vexpress_sysreg_driver); +} +core_initcall(vexpress_sysreg_init); diff --git a/kernel/drivers/mfd/viperboard.c b/kernel/drivers/mfd/viperboard.c new file mode 100644 index 000000000..e6b3c70ae --- /dev/null +++ b/kernel/drivers/mfd/viperboard.c @@ -0,0 +1,137 @@ +/* + * Nano River Technologies viperboard driver + * + * This is the core driver for the viperboard. There are cell drivers + * available for I2C, ADC and both GPIOs. SPI is not yet supported. + * The drivers do not support all features the board exposes. See user + * manual of the viperboard. + * + * (C) 2012 by Lemonage GmbH + * Author: Lars Poeschel <poeschel@lemonage.de> + * All rights reserved. + * + * 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/errno.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/mutex.h> + +#include <linux/mfd/core.h> +#include <linux/mfd/viperboard.h> + +#include <linux/usb.h> + + +static const struct usb_device_id vprbrd_table[] = { + { USB_DEVICE(0x2058, 0x1005) }, /* Nano River Technologies */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, vprbrd_table); + +static const struct mfd_cell vprbrd_devs[] = { + { + .name = "viperboard-gpio", + }, + { + .name = "viperboard-i2c", + }, + { + .name = "viperboard-adc", + }, +}; + +static int vprbrd_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct vprbrd *vb; + + u16 version = 0; + int pipe, ret; + + /* allocate memory for our device state and initialize it */ + vb = kzalloc(sizeof(*vb), GFP_KERNEL); + if (vb == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + return -ENOMEM; + } + + mutex_init(&vb->lock); + + vb->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, vb); + dev_set_drvdata(&vb->pdev.dev, vb); + + /* get version information, major first, minor then */ + pipe = usb_rcvctrlpipe(vb->usb_dev, 0); + ret = usb_control_msg(vb->usb_dev, pipe, VPRBRD_USB_REQUEST_MAJOR, + VPRBRD_USB_TYPE_IN, 0x0000, 0x0000, vb->buf, 1, + VPRBRD_USB_TIMEOUT_MS); + if (ret == 1) + version = vb->buf[0]; + + ret = usb_control_msg(vb->usb_dev, pipe, VPRBRD_USB_REQUEST_MINOR, + VPRBRD_USB_TYPE_IN, 0x0000, 0x0000, vb->buf, 1, + VPRBRD_USB_TIMEOUT_MS); + if (ret == 1) { + version <<= 8; + version = version | vb->buf[0]; + } + + dev_info(&interface->dev, + "version %x.%02x found at bus %03d address %03d\n", + version >> 8, version & 0xff, + vb->usb_dev->bus->busnum, vb->usb_dev->devnum); + + ret = mfd_add_hotplug_devices(&interface->dev, vprbrd_devs, + ARRAY_SIZE(vprbrd_devs)); + if (ret != 0) { + dev_err(&interface->dev, "Failed to add mfd devices to core."); + goto error; + } + + return 0; + +error: + if (vb) { + usb_put_dev(vb->usb_dev); + kfree(vb); + } + + return ret; +} + +static void vprbrd_disconnect(struct usb_interface *interface) +{ + struct vprbrd *vb = usb_get_intfdata(interface); + + mfd_remove_devices(&interface->dev); + usb_set_intfdata(interface, NULL); + usb_put_dev(vb->usb_dev); + kfree(vb); + + dev_dbg(&interface->dev, "disconnected\n"); +} + +static struct usb_driver vprbrd_driver = { + .name = "viperboard", + .probe = vprbrd_probe, + .disconnect = vprbrd_disconnect, + .id_table = vprbrd_table, +}; + +module_usb_driver(vprbrd_driver); + +MODULE_DESCRIPTION("Nano River Technologies viperboard mfd core driver"); +MODULE_AUTHOR("Lars Poeschel <poeschel@lemonage.de>"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/vx855.c b/kernel/drivers/mfd/vx855.c new file mode 100644 index 000000000..84f01da48 --- /dev/null +++ b/kernel/drivers/mfd/vx855.c @@ -0,0 +1,138 @@ +/* + * Linux multi-function-device driver (MFD) for the integrated peripherals + * of the VIA VX855 chipset + * + * Copyright (C) 2009 VIA Technologies, Inc. + * Copyright (C) 2010 One Laptop per Child + * Author: Harald Welte <HaraldWelte@viatech.com> + * All rights reserved. + * + * 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 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pci.h> +#include <linux/mfd/core.h> + +/* offset into pci config space indicating the 16bit register containing + * the power management IO space base */ +#define VX855_CFG_PMIO_OFFSET 0x88 + +/* ACPI I/O Space registers */ +#define VX855_PMIO_ACPI 0x00 +#define VX855_PMIO_ACPI_LEN 0x0b + +/* Processor Power Management */ +#define VX855_PMIO_PPM 0x10 +#define VX855_PMIO_PPM_LEN 0x08 + +/* General Purpose Power Management */ +#define VX855_PMIO_GPPM 0x20 +#define VX855_PMIO_R_GPI 0x48 +#define VX855_PMIO_R_GPO 0x4c +#define VX855_PMIO_GPPM_LEN 0x33 + +#define VSPIC_MMIO_SIZE 0x1000 + +static struct resource vx855_gpio_resources[] = { + { + .flags = IORESOURCE_IO, + }, + { + .flags = IORESOURCE_IO, + }, +}; + +static const struct mfd_cell vx855_cells[] = { + { + .name = "vx855_gpio", + .num_resources = ARRAY_SIZE(vx855_gpio_resources), + .resources = vx855_gpio_resources, + + /* we must ignore resource conflicts, for reasons outlined in + * the vx855_gpio driver */ + .ignore_resource_conflicts = true, + }, +}; + +static int vx855_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret; + u16 gpio_io_offset; + + ret = pci_enable_device(pdev); + if (ret) + return -ENODEV; + + pci_read_config_word(pdev, VX855_CFG_PMIO_OFFSET, &gpio_io_offset); + if (!gpio_io_offset) { + dev_warn(&pdev->dev, + "BIOS did not assign PMIO base offset?!?\n"); + ret = -ENODEV; + goto out; + } + + /* mask out the lowest seven bits, as they are always zero, but + * hardware returns them as 0x01 */ + gpio_io_offset &= 0xff80; + + /* As the region identified here includes many non-GPIO things, we + * only work with the specific registers that concern us. */ + vx855_gpio_resources[0].start = gpio_io_offset + VX855_PMIO_R_GPI; + vx855_gpio_resources[0].end = vx855_gpio_resources[0].start + 3; + vx855_gpio_resources[1].start = gpio_io_offset + VX855_PMIO_R_GPO; + vx855_gpio_resources[1].end = vx855_gpio_resources[1].start + 3; + + ret = mfd_add_devices(&pdev->dev, -1, vx855_cells, ARRAY_SIZE(vx855_cells), + NULL, 0, NULL); + + /* we always return -ENODEV here in order to enable other + * drivers like old, not-yet-platform_device ported i2c-viapro */ + return -ENODEV; +out: + pci_disable_device(pdev); + return ret; +} + +static void vx855_remove(struct pci_dev *pdev) +{ + mfd_remove_devices(&pdev->dev); + pci_disable_device(pdev); +} + +static const struct pci_device_id vx855_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX855) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, vx855_pci_tbl); + +static struct pci_driver vx855_pci_driver = { + .name = "vx855", + .id_table = vx855_pci_tbl, + .probe = vx855_probe, + .remove = vx855_remove, +}; + +module_pci_driver(vx855_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>"); +MODULE_DESCRIPTION("Driver for the VIA VX855 chipset"); diff --git a/kernel/drivers/mfd/wl1273-core.c b/kernel/drivers/mfd/wl1273-core.c new file mode 100644 index 000000000..f7c52d901 --- /dev/null +++ b/kernel/drivers/mfd/wl1273-core.c @@ -0,0 +1,287 @@ +/* + * MFD driver for wl1273 FM radio and audio codec submodules. + * + * Copyright (C) 2011 Nokia Corporation + * Author: Matti Aaltonen <matti.j.aaltonen@nokia.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/mfd/wl1273-core.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define DRIVER_DESC "WL1273 FM Radio Core" + +static const struct i2c_device_id wl1273_driver_id_table[] = { + { WL1273_FM_DRIVER_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wl1273_driver_id_table); + +static int wl1273_fm_read_reg(struct wl1273_core *core, u8 reg, u16 *value) +{ + struct i2c_client *client = core->client; + u8 b[2]; + int r; + + r = i2c_smbus_read_i2c_block_data(client, reg, sizeof(b), b); + if (r != 2) { + dev_err(&client->dev, "%s: Read: %d fails.\n", __func__, reg); + return -EREMOTEIO; + } + + *value = (u16)b[0] << 8 | b[1]; + + return 0; +} + +static int wl1273_fm_write_cmd(struct wl1273_core *core, u8 cmd, u16 param) +{ + struct i2c_client *client = core->client; + u8 buf[] = { (param >> 8) & 0xff, param & 0xff }; + int r; + + r = i2c_smbus_write_i2c_block_data(client, cmd, sizeof(buf), buf); + if (r) { + dev_err(&client->dev, "%s: Cmd: %d fails.\n", __func__, cmd); + return r; + } + + return 0; +} + +static int wl1273_fm_write_data(struct wl1273_core *core, u8 *data, u16 len) +{ + struct i2c_client *client = core->client; + struct i2c_msg msg; + int r; + + msg.addr = client->addr; + msg.flags = 0; + msg.buf = data; + msg.len = len; + + r = i2c_transfer(client->adapter, &msg, 1); + if (r != 1) { + dev_err(&client->dev, "%s: write error.\n", __func__); + return -EREMOTEIO; + } + + return 0; +} + +/** + * wl1273_fm_set_audio() - Set audio mode. + * @core: A pointer to the device struct. + * @new_mode: The new audio mode. + * + * Audio modes are WL1273_AUDIO_DIGITAL and WL1273_AUDIO_ANALOG. + */ +static int wl1273_fm_set_audio(struct wl1273_core *core, unsigned int new_mode) +{ + int r = 0; + + if (core->mode == WL1273_MODE_OFF || + core->mode == WL1273_MODE_SUSPENDED) + return -EPERM; + + if (core->mode == WL1273_MODE_RX && new_mode == WL1273_AUDIO_DIGITAL) { + r = wl1273_fm_write_cmd(core, WL1273_PCM_MODE_SET, + WL1273_PCM_DEF_MODE); + if (r) + goto out; + + r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, + core->i2s_mode); + if (r) + goto out; + + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE, + WL1273_AUDIO_ENABLE_I2S); + if (r) + goto out; + + } else if (core->mode == WL1273_MODE_RX && + new_mode == WL1273_AUDIO_ANALOG) { + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE, + WL1273_AUDIO_ENABLE_ANALOG); + if (r) + goto out; + + } else if (core->mode == WL1273_MODE_TX && + new_mode == WL1273_AUDIO_DIGITAL) { + r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, + core->i2s_mode); + if (r) + goto out; + + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET, + WL1273_AUDIO_IO_SET_I2S); + if (r) + goto out; + + } else if (core->mode == WL1273_MODE_TX && + new_mode == WL1273_AUDIO_ANALOG) { + r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET, + WL1273_AUDIO_IO_SET_ANALOG); + if (r) + goto out; + } + + core->audio_mode = new_mode; +out: + return r; +} + +/** + * wl1273_fm_set_volume() - Set volume. + * @core: A pointer to the device struct. + * @volume: The new volume value. + */ +static int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume) +{ + int r; + + if (volume > WL1273_MAX_VOLUME) + return -EINVAL; + + if (core->volume == volume) + return 0; + + r = wl1273_fm_write_cmd(core, WL1273_VOLUME_SET, volume); + if (r) + return r; + + core->volume = volume; + return 0; +} + +static int wl1273_core_remove(struct i2c_client *client) +{ + dev_dbg(&client->dev, "%s\n", __func__); + + mfd_remove_devices(&client->dev); + + return 0; +} + +static int wl1273_core_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wl1273_fm_platform_data *pdata = dev_get_platdata(&client->dev); + struct wl1273_core *core; + struct mfd_cell *cell; + int children = 0; + int r = 0; + + dev_dbg(&client->dev, "%s\n", __func__); + + if (!pdata) { + dev_err(&client->dev, "No platform data.\n"); + return -EINVAL; + } + + if (!(pdata->children & WL1273_RADIO_CHILD)) { + dev_err(&client->dev, "Cannot function without radio child.\n"); + return -EINVAL; + } + + core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->pdata = pdata; + core->client = client; + mutex_init(&core->lock); + + i2c_set_clientdata(client, core); + + dev_dbg(&client->dev, "%s: Have V4L2.\n", __func__); + + cell = &core->cells[children]; + cell->name = "wl1273_fm_radio"; + cell->platform_data = &core; + cell->pdata_size = sizeof(core); + children++; + + core->read = wl1273_fm_read_reg; + core->write = wl1273_fm_write_cmd; + core->write_data = wl1273_fm_write_data; + core->set_audio = wl1273_fm_set_audio; + core->set_volume = wl1273_fm_set_volume; + + if (pdata->children & WL1273_CODEC_CHILD) { + cell = &core->cells[children]; + + dev_dbg(&client->dev, "%s: Have codec.\n", __func__); + cell->name = "wl1273-codec"; + cell->platform_data = &core; + cell->pdata_size = sizeof(core); + children++; + } + + dev_dbg(&client->dev, "%s: number of children: %d.\n", + __func__, children); + + r = mfd_add_devices(&client->dev, -1, core->cells, + children, NULL, 0, NULL); + if (r) + goto err; + + return 0; + +err: + pdata->free_resources(); + + dev_dbg(&client->dev, "%s\n", __func__); + + return r; +} + +static struct i2c_driver wl1273_core_driver = { + .driver = { + .name = WL1273_FM_DRIVER_NAME, + }, + .probe = wl1273_core_probe, + .id_table = wl1273_driver_id_table, + .remove = wl1273_core_remove, +}; + +static int __init wl1273_core_init(void) +{ + int r; + + r = i2c_add_driver(&wl1273_core_driver); + if (r) { + pr_err(WL1273_FM_DRIVER_NAME + ": driver registration failed\n"); + return r; + } + + return r; +} + +static void __exit wl1273_core_exit(void) +{ + i2c_del_driver(&wl1273_core_driver); +} +late_initcall(wl1273_core_init); +module_exit(wl1273_core_exit); + +MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/wm5102-tables.c b/kernel/drivers/mfd/wm5102-tables.c new file mode 100644 index 000000000..aeae6ec12 --- /dev/null +++ b/kernel/drivers/mfd/wm5102-tables.c @@ -0,0 +1,1995 @@ +/* + * wm5102-tables.c -- WM5102 data tables + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/module.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/registers.h> + +#include "arizona.h" + +#define WM5102_NUM_AOD_ISR 2 +#define WM5102_NUM_ISR 5 + +static const struct reg_default wm5102_reva_patch[] = { + { 0x80, 0x0003 }, + { 0x221, 0x0090 }, + { 0x211, 0x0014 }, + { 0x212, 0x0000 }, + { 0x214, 0x000C }, + { 0x171, 0x0002 }, + { 0x171, 0x0000 }, + { 0x461, 0x8000 }, + { 0x463, 0x50F0 }, + { 0x465, 0x4820 }, + { 0x467, 0x4040 }, + { 0x469, 0x3940 }, + { 0x46B, 0x3310 }, + { 0x46D, 0x2D80 }, + { 0x46F, 0x2890 }, + { 0x471, 0x1990 }, + { 0x473, 0x1450 }, + { 0x475, 0x1020 }, + { 0x477, 0x0CD0 }, + { 0x479, 0x0A30 }, + { 0x47B, 0x0810 }, + { 0x47D, 0x0510 }, + { 0x4D1, 0x017F }, + { 0x500, 0x000D }, + { 0x507, 0x1820 }, + { 0x508, 0x1820 }, + { 0x540, 0x000D }, + { 0x547, 0x1820 }, + { 0x548, 0x1820 }, + { 0x580, 0x000D }, + { 0x587, 0x1820 }, + { 0x588, 0x1820 }, + { 0x80, 0x0000 }, +}; + +static const struct reg_default wm5102_revb_patch[] = { + { 0x19, 0x0001 }, + { 0x80, 0x0003 }, + { 0x081, 0xE022 }, + { 0x410, 0x6080 }, + { 0x418, 0xa080 }, + { 0x420, 0xa080 }, + { 0x428, 0xe000 }, + { 0x442, 0x3F0A }, + { 0x443, 0xDC1F }, + { 0x4B0, 0x0066 }, + { 0x458, 0x000b }, + { 0x212, 0x0000 }, + { 0x171, 0x0000 }, + { 0x35E, 0x000C }, + { 0x2D4, 0x0000 }, + { 0x4DC, 0x0900 }, + { 0x80, 0x0000 }, +}; + +/* We use a function so we can use ARRAY_SIZE() */ +int wm5102_patch(struct arizona *arizona) +{ + const struct reg_default *wm5102_patch; + int patch_size; + + switch (arizona->rev) { + case 0: + wm5102_patch = wm5102_reva_patch; + patch_size = ARRAY_SIZE(wm5102_reva_patch); + break; + default: + wm5102_patch = wm5102_revb_patch; + patch_size = ARRAY_SIZE(wm5102_revb_patch); + } + + return regmap_multi_reg_write_bypassed(arizona->regmap, + wm5102_patch, + patch_size); +} + +static const struct regmap_irq wm5102_aod_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_MICD_CLAMP_FALL] = { + .mask = ARIZONA_MICD_CLAMP_FALL_EINT1 + }, + [ARIZONA_IRQ_MICD_CLAMP_RISE] = { + .mask = ARIZONA_MICD_CLAMP_RISE_EINT1 + }, + [ARIZONA_IRQ_GP5_FALL] = { .mask = ARIZONA_GP5_FALL_EINT1 }, + [ARIZONA_IRQ_GP5_RISE] = { .mask = ARIZONA_GP5_RISE_EINT1 }, + [ARIZONA_IRQ_JD_FALL] = { .mask = ARIZONA_JD1_FALL_EINT1 }, + [ARIZONA_IRQ_JD_RISE] = { .mask = ARIZONA_JD1_RISE_EINT1 }, +}; + +const struct regmap_irq_chip wm5102_aod = { + .name = "wm5102 AOD", + .status_base = ARIZONA_AOD_IRQ1, + .mask_base = ARIZONA_AOD_IRQ_MASK_IRQ1, + .ack_base = ARIZONA_AOD_IRQ1, + .wake_base = ARIZONA_WAKE_CONTROL, + .wake_invert = 1, + .num_regs = 1, + .irqs = wm5102_aod_irqs, + .num_irqs = ARRAY_SIZE(wm5102_aod_irqs), +}; + +static const struct regmap_irq wm5102_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_GP4] = { .reg_offset = 0, .mask = ARIZONA_GP4_EINT1 }, + [ARIZONA_IRQ_GP3] = { .reg_offset = 0, .mask = ARIZONA_GP3_EINT1 }, + [ARIZONA_IRQ_GP2] = { .reg_offset = 0, .mask = ARIZONA_GP2_EINT1 }, + [ARIZONA_IRQ_GP1] = { .reg_offset = 0, .mask = ARIZONA_GP1_EINT1 }, + + [ARIZONA_IRQ_DSP1_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP1_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ2] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ2_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ1] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ1_EINT1 + }, + + [ARIZONA_IRQ_SPK_OVERHEAT_WARN] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_WARN_EINT1 + }, + [ARIZONA_IRQ_SPK_OVERHEAT] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_EINT1 + }, + [ARIZONA_IRQ_HPDET] = { + .reg_offset = 2, .mask = ARIZONA_HPDET_EINT1 + }, + [ARIZONA_IRQ_MICDET] = { + .reg_offset = 2, .mask = ARIZONA_MICDET_EINT1 + }, + [ARIZONA_IRQ_WSEQ_DONE] = { + .reg_offset = 2, .mask = ARIZONA_WSEQ_DONE_EINT1 + }, + [ARIZONA_IRQ_DRC2_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC2_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_DRC1_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC1_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_ASRC2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_ASRC2_LOCK_EINT1 + }, + [ARIZONA_IRQ_ASRC1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_ASRC1_LOCK_EINT1 + }, + [ARIZONA_IRQ_UNDERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_UNDERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_OVERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_OVERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_FLL2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL2_LOCK_EINT1 + }, + [ARIZONA_IRQ_FLL1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL1_LOCK_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR_ASYNC] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_ASYNC_EINT1 + }, + + [ARIZONA_IRQ_ASRC_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ASRC_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF3_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF3_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF2_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF2_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF1_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF1_ERR_EINT1 + }, + [ARIZONA_IRQ_CTRLIF_ERR] = { + .reg_offset = 3, .mask = ARIZONA_CTRLIF_ERR_EINT1 + }, + [ARIZONA_IRQ_MIXER_DROPPED_SAMPLES] = { + .reg_offset = 3, .mask = ARIZONA_MIXER_DROPPED_SAMPLE_EINT1 + }, + [ARIZONA_IRQ_ASYNC_CLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_ASYNC_CLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_SYSCLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_SYSCLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_ISRC1_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ISRC1_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_ISRC2_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ISRC2_CFG_ERR_EINT1 + }, + + [ARIZONA_IRQ_BOOT_DONE] = { + .reg_offset = 4, .mask = ARIZONA_BOOT_DONE_EINT1 + }, + [ARIZONA_IRQ_DCS_DAC_DONE] = { + .reg_offset = 4, .mask = ARIZONA_DCS_DAC_DONE_EINT1 + }, + [ARIZONA_IRQ_DCS_HP_DONE] = { + .reg_offset = 4, .mask = ARIZONA_DCS_HP_DONE_EINT1 + }, + [ARIZONA_IRQ_FLL2_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL2_CLOCK_OK_EINT1 + }, + [ARIZONA_IRQ_FLL1_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL1_CLOCK_OK_EINT1 + }, +}; + +const struct regmap_irq_chip wm5102_irq = { + .name = "wm5102 IRQ", + .status_base = ARIZONA_INTERRUPT_STATUS_1, + .mask_base = ARIZONA_INTERRUPT_STATUS_1_MASK, + .ack_base = ARIZONA_INTERRUPT_STATUS_1, + .num_regs = 5, + .irqs = wm5102_irqs, + .num_irqs = ARRAY_SIZE(wm5102_irqs), +}; + +static const struct reg_default wm5102_reg_default[] = { + { 0x00000008, 0x0019 }, /* R8 - Ctrl IF SPI CFG 1 */ + { 0x00000009, 0x0001 }, /* R9 - Ctrl IF I2C1 CFG 1 */ + { 0x00000020, 0x0000 }, /* R32 - Tone Generator 1 */ + { 0x00000021, 0x1000 }, /* R33 - Tone Generator 2 */ + { 0x00000022, 0x0000 }, /* R34 - Tone Generator 3 */ + { 0x00000023, 0x1000 }, /* R35 - Tone Generator 4 */ + { 0x00000024, 0x0000 }, /* R36 - Tone Generator 5 */ + { 0x00000030, 0x0000 }, /* R48 - PWM Drive 1 */ + { 0x00000031, 0x0100 }, /* R49 - PWM Drive 2 */ + { 0x00000032, 0x0100 }, /* R50 - PWM Drive 3 */ + { 0x00000040, 0x0000 }, /* R64 - Wake control */ + { 0x00000041, 0x0000 }, /* R65 - Sequence control */ + { 0x00000061, 0x01FF }, /* R97 - Sample Rate Sequence Select 1 */ + { 0x00000062, 0x01FF }, /* R98 - Sample Rate Sequence Select 2 */ + { 0x00000063, 0x01FF }, /* R99 - Sample Rate Sequence Select 3 */ + { 0x00000064, 0x01FF }, /* R100 - Sample Rate Sequence Select 4 */ + { 0x00000066, 0x01FF }, /* R102 - Always On Triggers Sequence Select 1 */ + { 0x00000067, 0x01FF }, /* R103 - Always On Triggers Sequence Select 2 */ + { 0x00000068, 0x01FF }, /* R104 - Always On Triggers Sequence Select 3 */ + { 0x00000069, 0x01FF }, /* R105 - Always On Triggers Sequence Select 4 */ + { 0x0000006A, 0x01FF }, /* R106 - Always On Triggers Sequence Select 5 */ + { 0x0000006B, 0x01FF }, /* R107 - Always On Triggers Sequence Select 6 */ + { 0x0000006E, 0x01FF }, /* R110 - Trigger Sequence Select 32 */ + { 0x0000006F, 0x01FF }, /* R111 - Trigger Sequence Select 33 */ + { 0x00000070, 0x0000 }, /* R112 - Comfort Noise Generator */ + { 0x00000090, 0x0000 }, /* R144 - Haptics Control 1 */ + { 0x00000091, 0x7FFF }, /* R145 - Haptics Control 2 */ + { 0x00000092, 0x0000 }, /* R146 - Haptics phase 1 intensity */ + { 0x00000093, 0x0000 }, /* R147 - Haptics phase 1 duration */ + { 0x00000094, 0x0000 }, /* R148 - Haptics phase 2 intensity */ + { 0x00000095, 0x0000 }, /* R149 - Haptics phase 2 duration */ + { 0x00000096, 0x0000 }, /* R150 - Haptics phase 3 intensity */ + { 0x00000097, 0x0000 }, /* R151 - Haptics phase 3 duration */ + { 0x00000100, 0x0002 }, /* R256 - Clock 32k 1 */ + { 0x00000101, 0x0304 }, /* R257 - System Clock 1 */ + { 0x00000102, 0x0011 }, /* R258 - Sample rate 1 */ + { 0x00000103, 0x0011 }, /* R259 - Sample rate 2 */ + { 0x00000104, 0x0011 }, /* R260 - Sample rate 3 */ + { 0x00000112, 0x0305 }, /* R274 - Async clock 1 */ + { 0x00000113, 0x0011 }, /* R275 - Async sample rate 1 */ + { 0x00000114, 0x0011 }, /* R276 - Async sample rate 2 */ + { 0x00000149, 0x0000 }, /* R329 - Output system clock */ + { 0x0000014A, 0x0000 }, /* R330 - Output async clock */ + { 0x00000152, 0x0000 }, /* R338 - Rate Estimator 1 */ + { 0x00000153, 0x0000 }, /* R339 - Rate Estimator 2 */ + { 0x00000154, 0x0000 }, /* R340 - Rate Estimator 3 */ + { 0x00000155, 0x0000 }, /* R341 - Rate Estimator 4 */ + { 0x00000156, 0x0000 }, /* R342 - Rate Estimator 5 */ + { 0x00000161, 0x0000 }, /* R353 - Dynamic Frequency Scaling 1 */ + { 0x00000171, 0x0000 }, /* R369 - FLL1 Control 1 */ + { 0x00000172, 0x0008 }, /* R370 - FLL1 Control 2 */ + { 0x00000173, 0x0018 }, /* R371 - FLL1 Control 3 */ + { 0x00000174, 0x007D }, /* R372 - FLL1 Control 4 */ + { 0x00000175, 0x0004 }, /* R373 - FLL1 Control 5 */ + { 0x00000176, 0x0000 }, /* R374 - FLL1 Control 6 */ + { 0x00000177, 0x0181 }, /* R375 - FLL1 Loop Filter Test 1 */ + { 0x00000178, 0x0000 }, /* R376 - FLL1 NCO Test 0 */ + { 0x00000179, 0x0000 }, /* R377 - FLL1 Control 7 */ + { 0x00000181, 0x0000 }, /* R385 - FLL1 Synchroniser 1 */ + { 0x00000182, 0x0000 }, /* R386 - FLL1 Synchroniser 2 */ + { 0x00000183, 0x0000 }, /* R387 - FLL1 Synchroniser 3 */ + { 0x00000184, 0x0000 }, /* R388 - FLL1 Synchroniser 4 */ + { 0x00000185, 0x0000 }, /* R389 - FLL1 Synchroniser 5 */ + { 0x00000186, 0x0000 }, /* R390 - FLL1 Synchroniser 6 */ + { 0x00000187, 0x0001 }, /* R391 - FLL1 Synchroniser 7 */ + { 0x00000189, 0x0000 }, /* R393 - FLL1 Spread Spectrum */ + { 0x0000018A, 0x0004 }, /* R394 - FLL1 GPIO Clock */ + { 0x00000191, 0x0000 }, /* R401 - FLL2 Control 1 */ + { 0x00000192, 0x0008 }, /* R402 - FLL2 Control 2 */ + { 0x00000193, 0x0018 }, /* R403 - FLL2 Control 3 */ + { 0x00000194, 0x007D }, /* R404 - FLL2 Control 4 */ + { 0x00000195, 0x0004 }, /* R405 - FLL2 Control 5 */ + { 0x00000196, 0x0000 }, /* R406 - FLL2 Control 6 */ + { 0x00000197, 0x0000 }, /* R407 - FLL2 Loop Filter Test 1 */ + { 0x00000198, 0x0000 }, /* R408 - FLL2 NCO Test 0 */ + { 0x00000199, 0x0000 }, /* R409 - FLL2 Control 7 */ + { 0x000001A1, 0x0000 }, /* R417 - FLL2 Synchroniser 1 */ + { 0x000001A2, 0x0000 }, /* R418 - FLL2 Synchroniser 2 */ + { 0x000001A3, 0x0000 }, /* R419 - FLL2 Synchroniser 3 */ + { 0x000001A4, 0x0000 }, /* R420 - FLL2 Synchroniser 4 */ + { 0x000001A5, 0x0000 }, /* R421 - FLL2 Synchroniser 5 */ + { 0x000001A6, 0x0000 }, /* R422 - FLL2 Synchroniser 6 */ + { 0x000001A7, 0x0001 }, /* R423 - FLL2 Synchroniser 7 */ + { 0x000001A9, 0x0000 }, /* R425 - FLL2 Spread Spectrum */ + { 0x000001AA, 0x0004 }, /* R426 - FLL2 GPIO Clock */ + { 0x00000200, 0x0006 }, /* R512 - Mic Charge Pump 1 */ + { 0x00000210, 0x00D4 }, /* R528 - LDO1 Control 1 */ + { 0x00000212, 0x0000 }, /* R530 - LDO1 Control 2 */ + { 0x00000213, 0x0344 }, /* R531 - LDO2 Control 1 */ + { 0x00000218, 0x01A6 }, /* R536 - Mic Bias Ctrl 1 */ + { 0x00000219, 0x01A6 }, /* R537 - Mic Bias Ctrl 2 */ + { 0x0000021A, 0x01A6 }, /* R538 - Mic Bias Ctrl 3 */ + { 0x00000293, 0x0000 }, /* R659 - Accessory Detect Mode 1 */ + { 0x0000029B, 0x0020 }, /* R667 - Headphone Detect 1 */ + { 0x0000029C, 0x0000 }, /* R668 - Headphone Detect 2 */ + { 0x0000029F, 0x0000 }, /* R671 - Headphone Detect Test */ + { 0x000002A2, 0x0000 }, /* R674 - Micd clamp control */ + { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ + { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ + { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ + { 0x000002A6, 0x3737 }, /* R678 - Mic Detect Level 1 */ + { 0x000002A7, 0x372C }, /* R679 - Mic Detect Level 2 */ + { 0x000002A8, 0x1422 }, /* R680 - Mic Detect Level 3 */ + { 0x000002A9, 0x030A }, /* R681 - Mic Detect Level 4 */ + { 0x000002C3, 0x0000 }, /* R707 - Mic noise mix control 1 */ + { 0x000002CB, 0x0000 }, /* R715 - Isolation control */ + { 0x000002D3, 0x0000 }, /* R723 - Jack detect analogue */ + { 0x00000300, 0x0000 }, /* R768 - Input Enables */ + { 0x00000308, 0x0000 }, /* R776 - Input Rate */ + { 0x00000309, 0x0022 }, /* R777 - Input Volume Ramp */ + { 0x00000310, 0x2080 }, /* R784 - IN1L Control */ + { 0x00000311, 0x0180 }, /* R785 - ADC Digital Volume 1L */ + { 0x00000312, 0x0000 }, /* R786 - DMIC1L Control */ + { 0x00000314, 0x0080 }, /* R788 - IN1R Control */ + { 0x00000315, 0x0180 }, /* R789 - ADC Digital Volume 1R */ + { 0x00000316, 0x0000 }, /* R790 - DMIC1R Control */ + { 0x00000318, 0x2080 }, /* R792 - IN2L Control */ + { 0x00000319, 0x0180 }, /* R793 - ADC Digital Volume 2L */ + { 0x0000031A, 0x0000 }, /* R794 - DMIC2L Control */ + { 0x0000031C, 0x0080 }, /* R796 - IN2R Control */ + { 0x0000031D, 0x0180 }, /* R797 - ADC Digital Volume 2R */ + { 0x0000031E, 0x0000 }, /* R798 - DMIC2R Control */ + { 0x00000320, 0x2080 }, /* R800 - IN3L Control */ + { 0x00000321, 0x0180 }, /* R801 - ADC Digital Volume 3L */ + { 0x00000322, 0x0000 }, /* R802 - DMIC3L Control */ + { 0x00000324, 0x0080 }, /* R804 - IN3R Control */ + { 0x00000325, 0x0180 }, /* R805 - ADC Digital Volume 3R */ + { 0x00000326, 0x0000 }, /* R806 - DMIC3R Control */ + { 0x00000400, 0x0000 }, /* R1024 - Output Enables 1 */ + { 0x00000408, 0x0000 }, /* R1032 - Output Rate 1 */ + { 0x00000409, 0x0022 }, /* R1033 - Output Volume Ramp */ + { 0x00000410, 0x6080 }, /* R1040 - Output Path Config 1L */ + { 0x00000411, 0x0180 }, /* R1041 - DAC Digital Volume 1L */ + { 0x00000412, 0x0081 }, /* R1042 - DAC Volume Limit 1L */ + { 0x00000413, 0x0001 }, /* R1043 - Noise Gate Select 1L */ + { 0x00000414, 0x0080 }, /* R1044 - Output Path Config 1R */ + { 0x00000415, 0x0180 }, /* R1045 - DAC Digital Volume 1R */ + { 0x00000416, 0x0081 }, /* R1046 - DAC Volume Limit 1R */ + { 0x00000417, 0x0002 }, /* R1047 - Noise Gate Select 1R */ + { 0x00000418, 0xA080 }, /* R1048 - Output Path Config 2L */ + { 0x00000419, 0x0180 }, /* R1049 - DAC Digital Volume 2L */ + { 0x0000041A, 0x0081 }, /* R1050 - DAC Volume Limit 2L */ + { 0x0000041B, 0x0004 }, /* R1051 - Noise Gate Select 2L */ + { 0x0000041C, 0x0080 }, /* R1052 - Output Path Config 2R */ + { 0x0000041D, 0x0180 }, /* R1053 - DAC Digital Volume 2R */ + { 0x0000041E, 0x0081 }, /* R1054 - DAC Volume Limit 2R */ + { 0x0000041F, 0x0008 }, /* R1055 - Noise Gate Select 2R */ + { 0x00000420, 0xA080 }, /* R1056 - Output Path Config 3L */ + { 0x00000421, 0x0180 }, /* R1057 - DAC Digital Volume 3L */ + { 0x00000422, 0x0081 }, /* R1058 - DAC Volume Limit 3L */ + { 0x00000423, 0x0010 }, /* R1059 - Noise Gate Select 3L */ + { 0x00000428, 0xE000 }, /* R1064 - Output Path Config 4L */ + { 0x00000429, 0x0180 }, /* R1065 - DAC Digital Volume 4L */ + { 0x0000042A, 0x0081 }, /* R1066 - Out Volume 4L */ + { 0x0000042B, 0x0040 }, /* R1067 - Noise Gate Select 4L */ + { 0x0000042D, 0x0180 }, /* R1069 - DAC Digital Volume 4R */ + { 0x0000042E, 0x0081 }, /* R1070 - Out Volume 4R */ + { 0x0000042F, 0x0080 }, /* R1071 - Noise Gate Select 4R */ + { 0x00000430, 0x0000 }, /* R1072 - Output Path Config 5L */ + { 0x00000431, 0x0180 }, /* R1073 - DAC Digital Volume 5L */ + { 0x00000432, 0x0081 }, /* R1074 - DAC Volume Limit 5L */ + { 0x00000433, 0x0100 }, /* R1075 - Noise Gate Select 5L */ + { 0x00000435, 0x0180 }, /* R1077 - DAC Digital Volume 5R */ + { 0x00000436, 0x0081 }, /* R1078 - DAC Volume Limit 5R */ + { 0x00000437, 0x0200 }, /* R1079 - Noise Gate Select 5R */ + { 0x00000440, 0x8FFF }, /* R1088 - DRE Enable */ + { 0x00000442, 0x3F0A }, /* R1090 - DRE Control 2 */ + { 0x00000443, 0xDC1F }, /* R1090 - DRE Control 3 */ + { 0x00000450, 0x0000 }, /* R1104 - DAC AEC Control 1 */ + { 0x00000458, 0x000B }, /* R1112 - Noise Gate Control */ + { 0x00000490, 0x0069 }, /* R1168 - PDM SPK1 CTRL 1 */ + { 0x00000491, 0x0000 }, /* R1169 - PDM SPK1 CTRL 2 */ + { 0x00000500, 0x000C }, /* R1280 - AIF1 BCLK Ctrl */ + { 0x00000501, 0x0008 }, /* R1281 - AIF1 Tx Pin Ctrl */ + { 0x00000502, 0x0000 }, /* R1282 - AIF1 Rx Pin Ctrl */ + { 0x00000503, 0x0000 }, /* R1283 - AIF1 Rate Ctrl */ + { 0x00000504, 0x0000 }, /* R1284 - AIF1 Format */ + { 0x00000505, 0x0040 }, /* R1285 - AIF1 Tx BCLK Rate */ + { 0x00000506, 0x0040 }, /* R1286 - AIF1 Rx BCLK Rate */ + { 0x00000507, 0x1818 }, /* R1287 - AIF1 Frame Ctrl 1 */ + { 0x00000508, 0x1818 }, /* R1288 - AIF1 Frame Ctrl 2 */ + { 0x00000509, 0x0000 }, /* R1289 - AIF1 Frame Ctrl 3 */ + { 0x0000050A, 0x0001 }, /* R1290 - AIF1 Frame Ctrl 4 */ + { 0x0000050B, 0x0002 }, /* R1291 - AIF1 Frame Ctrl 5 */ + { 0x0000050C, 0x0003 }, /* R1292 - AIF1 Frame Ctrl 6 */ + { 0x0000050D, 0x0004 }, /* R1293 - AIF1 Frame Ctrl 7 */ + { 0x0000050E, 0x0005 }, /* R1294 - AIF1 Frame Ctrl 8 */ + { 0x0000050F, 0x0006 }, /* R1295 - AIF1 Frame Ctrl 9 */ + { 0x00000510, 0x0007 }, /* R1296 - AIF1 Frame Ctrl 10 */ + { 0x00000511, 0x0000 }, /* R1297 - AIF1 Frame Ctrl 11 */ + { 0x00000512, 0x0001 }, /* R1298 - AIF1 Frame Ctrl 12 */ + { 0x00000513, 0x0002 }, /* R1299 - AIF1 Frame Ctrl 13 */ + { 0x00000514, 0x0003 }, /* R1300 - AIF1 Frame Ctrl 14 */ + { 0x00000515, 0x0004 }, /* R1301 - AIF1 Frame Ctrl 15 */ + { 0x00000516, 0x0005 }, /* R1302 - AIF1 Frame Ctrl 16 */ + { 0x00000517, 0x0006 }, /* R1303 - AIF1 Frame Ctrl 17 */ + { 0x00000518, 0x0007 }, /* R1304 - AIF1 Frame Ctrl 18 */ + { 0x00000519, 0x0000 }, /* R1305 - AIF1 Tx Enables */ + { 0x0000051A, 0x0000 }, /* R1306 - AIF1 Rx Enables */ + { 0x00000540, 0x000C }, /* R1344 - AIF2 BCLK Ctrl */ + { 0x00000541, 0x0008 }, /* R1345 - AIF2 Tx Pin Ctrl */ + { 0x00000542, 0x0000 }, /* R1346 - AIF2 Rx Pin Ctrl */ + { 0x00000543, 0x0000 }, /* R1347 - AIF2 Rate Ctrl */ + { 0x00000544, 0x0000 }, /* R1348 - AIF2 Format */ + { 0x00000545, 0x0040 }, /* R1349 - AIF2 Tx BCLK Rate */ + { 0x00000546, 0x0040 }, /* R1350 - AIF2 Rx BCLK Rate */ + { 0x00000547, 0x1818 }, /* R1351 - AIF2 Frame Ctrl 1 */ + { 0x00000548, 0x1818 }, /* R1352 - AIF2 Frame Ctrl 2 */ + { 0x00000549, 0x0000 }, /* R1353 - AIF2 Frame Ctrl 3 */ + { 0x0000054A, 0x0001 }, /* R1354 - AIF2 Frame Ctrl 4 */ + { 0x00000551, 0x0000 }, /* R1361 - AIF2 Frame Ctrl 11 */ + { 0x00000552, 0x0001 }, /* R1362 - AIF2 Frame Ctrl 12 */ + { 0x00000559, 0x0000 }, /* R1369 - AIF2 Tx Enables */ + { 0x0000055A, 0x0000 }, /* R1370 - AIF2 Rx Enables */ + { 0x00000580, 0x000C }, /* R1408 - AIF3 BCLK Ctrl */ + { 0x00000581, 0x0008 }, /* R1409 - AIF3 Tx Pin Ctrl */ + { 0x00000582, 0x0000 }, /* R1410 - AIF3 Rx Pin Ctrl */ + { 0x00000583, 0x0000 }, /* R1411 - AIF3 Rate Ctrl */ + { 0x00000584, 0x0000 }, /* R1412 - AIF3 Format */ + { 0x00000585, 0x0040 }, /* R1413 - AIF3 Tx BCLK Rate */ + { 0x00000586, 0x0040 }, /* R1414 - AIF3 Rx BCLK Rate */ + { 0x00000587, 0x1818 }, /* R1415 - AIF3 Frame Ctrl 1 */ + { 0x00000588, 0x1818 }, /* R1416 - AIF3 Frame Ctrl 2 */ + { 0x00000589, 0x0000 }, /* R1417 - AIF3 Frame Ctrl 3 */ + { 0x0000058A, 0x0001 }, /* R1418 - AIF3 Frame Ctrl 4 */ + { 0x00000591, 0x0000 }, /* R1425 - AIF3 Frame Ctrl 11 */ + { 0x00000592, 0x0001 }, /* R1426 - AIF3 Frame Ctrl 12 */ + { 0x00000599, 0x0000 }, /* R1433 - AIF3 Tx Enables */ + { 0x0000059A, 0x0000 }, /* R1434 - AIF3 Rx Enables */ + { 0x000005E3, 0x0004 }, /* R1507 - SLIMbus Framer Ref Gear */ + { 0x000005E5, 0x0000 }, /* R1509 - SLIMbus Rates 1 */ + { 0x000005E6, 0x0000 }, /* R1510 - SLIMbus Rates 2 */ + { 0x000005E7, 0x0000 }, /* R1511 - SLIMbus Rates 3 */ + { 0x000005E8, 0x0000 }, /* R1512 - SLIMbus Rates 4 */ + { 0x000005E9, 0x0000 }, /* R1513 - SLIMbus Rates 5 */ + { 0x000005EA, 0x0000 }, /* R1514 - SLIMbus Rates 6 */ + { 0x000005EB, 0x0000 }, /* R1515 - SLIMbus Rates 7 */ + { 0x000005EC, 0x0000 }, /* R1516 - SLIMbus Rates 8 */ + { 0x000005F5, 0x0000 }, /* R1525 - SLIMbus RX Channel Enable */ + { 0x000005F6, 0x0000 }, /* R1526 - SLIMbus TX Channel Enable */ + { 0x00000640, 0x0000 }, /* R1600 - PWM1MIX Input 1 Source */ + { 0x00000641, 0x0080 }, /* R1601 - PWM1MIX Input 1 Volume */ + { 0x00000642, 0x0000 }, /* R1602 - PWM1MIX Input 2 Source */ + { 0x00000643, 0x0080 }, /* R1603 - PWM1MIX Input 2 Volume */ + { 0x00000644, 0x0000 }, /* R1604 - PWM1MIX Input 3 Source */ + { 0x00000645, 0x0080 }, /* R1605 - PWM1MIX Input 3 Volume */ + { 0x00000646, 0x0000 }, /* R1606 - PWM1MIX Input 4 Source */ + { 0x00000647, 0x0080 }, /* R1607 - PWM1MIX Input 4 Volume */ + { 0x00000648, 0x0000 }, /* R1608 - PWM2MIX Input 1 Source */ + { 0x00000649, 0x0080 }, /* R1609 - PWM2MIX Input 1 Volume */ + { 0x0000064A, 0x0000 }, /* R1610 - PWM2MIX Input 2 Source */ + { 0x0000064B, 0x0080 }, /* R1611 - PWM2MIX Input 2 Volume */ + { 0x0000064C, 0x0000 }, /* R1612 - PWM2MIX Input 3 Source */ + { 0x0000064D, 0x0080 }, /* R1613 - PWM2MIX Input 3 Volume */ + { 0x0000064E, 0x0000 }, /* R1614 - PWM2MIX Input 4 Source */ + { 0x0000064F, 0x0080 }, /* R1615 - PWM2MIX Input 4 Volume */ + { 0x00000660, 0x0000 }, /* R1632 - MICMIX Input 1 Source */ + { 0x00000661, 0x0080 }, /* R1633 - MICMIX Input 1 Volume */ + { 0x00000662, 0x0000 }, /* R1634 - MICMIX Input 2 Source */ + { 0x00000663, 0x0080 }, /* R1635 - MICMIX Input 2 Volume */ + { 0x00000664, 0x0000 }, /* R1636 - MICMIX Input 3 Source */ + { 0x00000665, 0x0080 }, /* R1637 - MICMIX Input 3 Volume */ + { 0x00000666, 0x0000 }, /* R1638 - MICMIX Input 4 Source */ + { 0x00000667, 0x0080 }, /* R1639 - MICMIX Input 4 Volume */ + { 0x00000668, 0x0000 }, /* R1640 - NOISEMIX Input 1 Source */ + { 0x00000669, 0x0080 }, /* R1641 - NOISEMIX Input 1 Volume */ + { 0x0000066A, 0x0000 }, /* R1642 - NOISEMIX Input 2 Source */ + { 0x0000066B, 0x0080 }, /* R1643 - NOISEMIX Input 2 Volume */ + { 0x0000066C, 0x0000 }, /* R1644 - NOISEMIX Input 3 Source */ + { 0x0000066D, 0x0080 }, /* R1645 - NOISEMIX Input 3 Volume */ + { 0x0000066E, 0x0000 }, /* R1646 - NOISEMIX Input 4 Source */ + { 0x0000066F, 0x0080 }, /* R1647 - NOISEMIX Input 4 Volume */ + { 0x00000680, 0x0000 }, /* R1664 - OUT1LMIX Input 1 Source */ + { 0x00000681, 0x0080 }, /* R1665 - OUT1LMIX Input 1 Volume */ + { 0x00000682, 0x0000 }, /* R1666 - OUT1LMIX Input 2 Source */ + { 0x00000683, 0x0080 }, /* R1667 - OUT1LMIX Input 2 Volume */ + { 0x00000684, 0x0000 }, /* R1668 - OUT1LMIX Input 3 Source */ + { 0x00000685, 0x0080 }, /* R1669 - OUT1LMIX Input 3 Volume */ + { 0x00000686, 0x0000 }, /* R1670 - OUT1LMIX Input 4 Source */ + { 0x00000687, 0x0080 }, /* R1671 - OUT1LMIX Input 4 Volume */ + { 0x00000688, 0x0000 }, /* R1672 - OUT1RMIX Input 1 Source */ + { 0x00000689, 0x0080 }, /* R1673 - OUT1RMIX Input 1 Volume */ + { 0x0000068A, 0x0000 }, /* R1674 - OUT1RMIX Input 2 Source */ + { 0x0000068B, 0x0080 }, /* R1675 - OUT1RMIX Input 2 Volume */ + { 0x0000068C, 0x0000 }, /* R1676 - OUT1RMIX Input 3 Source */ + { 0x0000068D, 0x0080 }, /* R1677 - OUT1RMIX Input 3 Volume */ + { 0x0000068E, 0x0000 }, /* R1678 - OUT1RMIX Input 4 Source */ + { 0x0000068F, 0x0080 }, /* R1679 - OUT1RMIX Input 4 Volume */ + { 0x00000690, 0x0000 }, /* R1680 - OUT2LMIX Input 1 Source */ + { 0x00000691, 0x0080 }, /* R1681 - OUT2LMIX Input 1 Volume */ + { 0x00000692, 0x0000 }, /* R1682 - OUT2LMIX Input 2 Source */ + { 0x00000693, 0x0080 }, /* R1683 - OUT2LMIX Input 2 Volume */ + { 0x00000694, 0x0000 }, /* R1684 - OUT2LMIX Input 3 Source */ + { 0x00000695, 0x0080 }, /* R1685 - OUT2LMIX Input 3 Volume */ + { 0x00000696, 0x0000 }, /* R1686 - OUT2LMIX Input 4 Source */ + { 0x00000697, 0x0080 }, /* R1687 - OUT2LMIX Input 4 Volume */ + { 0x00000698, 0x0000 }, /* R1688 - OUT2RMIX Input 1 Source */ + { 0x00000699, 0x0080 }, /* R1689 - OUT2RMIX Input 1 Volume */ + { 0x0000069A, 0x0000 }, /* R1690 - OUT2RMIX Input 2 Source */ + { 0x0000069B, 0x0080 }, /* R1691 - OUT2RMIX Input 2 Volume */ + { 0x0000069C, 0x0000 }, /* R1692 - OUT2RMIX Input 3 Source */ + { 0x0000069D, 0x0080 }, /* R1693 - OUT2RMIX Input 3 Volume */ + { 0x0000069E, 0x0000 }, /* R1694 - OUT2RMIX Input 4 Source */ + { 0x0000069F, 0x0080 }, /* R1695 - OUT2RMIX Input 4 Volume */ + { 0x000006A0, 0x0000 }, /* R1696 - OUT3LMIX Input 1 Source */ + { 0x000006A1, 0x0080 }, /* R1697 - OUT3LMIX Input 1 Volume */ + { 0x000006A2, 0x0000 }, /* R1698 - OUT3LMIX Input 2 Source */ + { 0x000006A3, 0x0080 }, /* R1699 - OUT3LMIX Input 2 Volume */ + { 0x000006A4, 0x0000 }, /* R1700 - OUT3LMIX Input 3 Source */ + { 0x000006A5, 0x0080 }, /* R1701 - OUT3LMIX Input 3 Volume */ + { 0x000006A6, 0x0000 }, /* R1702 - OUT3LMIX Input 4 Source */ + { 0x000006A7, 0x0080 }, /* R1703 - OUT3LMIX Input 4 Volume */ + { 0x000006B0, 0x0000 }, /* R1712 - OUT4LMIX Input 1 Source */ + { 0x000006B1, 0x0080 }, /* R1713 - OUT4LMIX Input 1 Volume */ + { 0x000006B2, 0x0000 }, /* R1714 - OUT4LMIX Input 2 Source */ + { 0x000006B3, 0x0080 }, /* R1715 - OUT4LMIX Input 2 Volume */ + { 0x000006B4, 0x0000 }, /* R1716 - OUT4LMIX Input 3 Source */ + { 0x000006B5, 0x0080 }, /* R1717 - OUT4LMIX Input 3 Volume */ + { 0x000006B6, 0x0000 }, /* R1718 - OUT4LMIX Input 4 Source */ + { 0x000006B7, 0x0080 }, /* R1719 - OUT4LMIX Input 4 Volume */ + { 0x000006B8, 0x0000 }, /* R1720 - OUT4RMIX Input 1 Source */ + { 0x000006B9, 0x0080 }, /* R1721 - OUT4RMIX Input 1 Volume */ + { 0x000006BA, 0x0000 }, /* R1722 - OUT4RMIX Input 2 Source */ + { 0x000006BB, 0x0080 }, /* R1723 - OUT4RMIX Input 2 Volume */ + { 0x000006BC, 0x0000 }, /* R1724 - OUT4RMIX Input 3 Source */ + { 0x000006BD, 0x0080 }, /* R1725 - OUT4RMIX Input 3 Volume */ + { 0x000006BE, 0x0000 }, /* R1726 - OUT4RMIX Input 4 Source */ + { 0x000006BF, 0x0080 }, /* R1727 - OUT4RMIX Input 4 Volume */ + { 0x000006C0, 0x0000 }, /* R1728 - OUT5LMIX Input 1 Source */ + { 0x000006C1, 0x0080 }, /* R1729 - OUT5LMIX Input 1 Volume */ + { 0x000006C2, 0x0000 }, /* R1730 - OUT5LMIX Input 2 Source */ + { 0x000006C3, 0x0080 }, /* R1731 - OUT5LMIX Input 2 Volume */ + { 0x000006C4, 0x0000 }, /* R1732 - OUT5LMIX Input 3 Source */ + { 0x000006C5, 0x0080 }, /* R1733 - OUT5LMIX Input 3 Volume */ + { 0x000006C6, 0x0000 }, /* R1734 - OUT5LMIX Input 4 Source */ + { 0x000006C7, 0x0080 }, /* R1735 - OUT5LMIX Input 4 Volume */ + { 0x000006C8, 0x0000 }, /* R1736 - OUT5RMIX Input 1 Source */ + { 0x000006C9, 0x0080 }, /* R1737 - OUT5RMIX Input 1 Volume */ + { 0x000006CA, 0x0000 }, /* R1738 - OUT5RMIX Input 2 Source */ + { 0x000006CB, 0x0080 }, /* R1739 - OUT5RMIX Input 2 Volume */ + { 0x000006CC, 0x0000 }, /* R1740 - OUT5RMIX Input 3 Source */ + { 0x000006CD, 0x0080 }, /* R1741 - OUT5RMIX Input 3 Volume */ + { 0x000006CE, 0x0000 }, /* R1742 - OUT5RMIX Input 4 Source */ + { 0x000006CF, 0x0080 }, /* R1743 - OUT5RMIX Input 4 Volume */ + { 0x00000700, 0x0000 }, /* R1792 - AIF1TX1MIX Input 1 Source */ + { 0x00000701, 0x0080 }, /* R1793 - AIF1TX1MIX Input 1 Volume */ + { 0x00000702, 0x0000 }, /* R1794 - AIF1TX1MIX Input 2 Source */ + { 0x00000703, 0x0080 }, /* R1795 - AIF1TX1MIX Input 2 Volume */ + { 0x00000704, 0x0000 }, /* R1796 - AIF1TX1MIX Input 3 Source */ + { 0x00000705, 0x0080 }, /* R1797 - AIF1TX1MIX Input 3 Volume */ + { 0x00000706, 0x0000 }, /* R1798 - AIF1TX1MIX Input 4 Source */ + { 0x00000707, 0x0080 }, /* R1799 - AIF1TX1MIX Input 4 Volume */ + { 0x00000708, 0x0000 }, /* R1800 - AIF1TX2MIX Input 1 Source */ + { 0x00000709, 0x0080 }, /* R1801 - AIF1TX2MIX Input 1 Volume */ + { 0x0000070A, 0x0000 }, /* R1802 - AIF1TX2MIX Input 2 Source */ + { 0x0000070B, 0x0080 }, /* R1803 - AIF1TX2MIX Input 2 Volume */ + { 0x0000070C, 0x0000 }, /* R1804 - AIF1TX2MIX Input 3 Source */ + { 0x0000070D, 0x0080 }, /* R1805 - AIF1TX2MIX Input 3 Volume */ + { 0x0000070E, 0x0000 }, /* R1806 - AIF1TX2MIX Input 4 Source */ + { 0x0000070F, 0x0080 }, /* R1807 - AIF1TX2MIX Input 4 Volume */ + { 0x00000710, 0x0000 }, /* R1808 - AIF1TX3MIX Input 1 Source */ + { 0x00000711, 0x0080 }, /* R1809 - AIF1TX3MIX Input 1 Volume */ + { 0x00000712, 0x0000 }, /* R1810 - AIF1TX3MIX Input 2 Source */ + { 0x00000713, 0x0080 }, /* R1811 - AIF1TX3MIX Input 2 Volume */ + { 0x00000714, 0x0000 }, /* R1812 - AIF1TX3MIX Input 3 Source */ + { 0x00000715, 0x0080 }, /* R1813 - AIF1TX3MIX Input 3 Volume */ + { 0x00000716, 0x0000 }, /* R1814 - AIF1TX3MIX Input 4 Source */ + { 0x00000717, 0x0080 }, /* R1815 - AIF1TX3MIX Input 4 Volume */ + { 0x00000718, 0x0000 }, /* R1816 - AIF1TX4MIX Input 1 Source */ + { 0x00000719, 0x0080 }, /* R1817 - AIF1TX4MIX Input 1 Volume */ + { 0x0000071A, 0x0000 }, /* R1818 - AIF1TX4MIX Input 2 Source */ + { 0x0000071B, 0x0080 }, /* R1819 - AIF1TX4MIX Input 2 Volume */ + { 0x0000071C, 0x0000 }, /* R1820 - AIF1TX4MIX Input 3 Source */ + { 0x0000071D, 0x0080 }, /* R1821 - AIF1TX4MIX Input 3 Volume */ + { 0x0000071E, 0x0000 }, /* R1822 - AIF1TX4MIX Input 4 Source */ + { 0x0000071F, 0x0080 }, /* R1823 - AIF1TX4MIX Input 4 Volume */ + { 0x00000720, 0x0000 }, /* R1824 - AIF1TX5MIX Input 1 Source */ + { 0x00000721, 0x0080 }, /* R1825 - AIF1TX5MIX Input 1 Volume */ + { 0x00000722, 0x0000 }, /* R1826 - AIF1TX5MIX Input 2 Source */ + { 0x00000723, 0x0080 }, /* R1827 - AIF1TX5MIX Input 2 Volume */ + { 0x00000724, 0x0000 }, /* R1828 - AIF1TX5MIX Input 3 Source */ + { 0x00000725, 0x0080 }, /* R1829 - AIF1TX5MIX Input 3 Volume */ + { 0x00000726, 0x0000 }, /* R1830 - AIF1TX5MIX Input 4 Source */ + { 0x00000727, 0x0080 }, /* R1831 - AIF1TX5MIX Input 4 Volume */ + { 0x00000728, 0x0000 }, /* R1832 - AIF1TX6MIX Input 1 Source */ + { 0x00000729, 0x0080 }, /* R1833 - AIF1TX6MIX Input 1 Volume */ + { 0x0000072A, 0x0000 }, /* R1834 - AIF1TX6MIX Input 2 Source */ + { 0x0000072B, 0x0080 }, /* R1835 - AIF1TX6MIX Input 2 Volume */ + { 0x0000072C, 0x0000 }, /* R1836 - AIF1TX6MIX Input 3 Source */ + { 0x0000072D, 0x0080 }, /* R1837 - AIF1TX6MIX Input 3 Volume */ + { 0x0000072E, 0x0000 }, /* R1838 - AIF1TX6MIX Input 4 Source */ + { 0x0000072F, 0x0080 }, /* R1839 - AIF1TX6MIX Input 4 Volume */ + { 0x00000730, 0x0000 }, /* R1840 - AIF1TX7MIX Input 1 Source */ + { 0x00000731, 0x0080 }, /* R1841 - AIF1TX7MIX Input 1 Volume */ + { 0x00000732, 0x0000 }, /* R1842 - AIF1TX7MIX Input 2 Source */ + { 0x00000733, 0x0080 }, /* R1843 - AIF1TX7MIX Input 2 Volume */ + { 0x00000734, 0x0000 }, /* R1844 - AIF1TX7MIX Input 3 Source */ + { 0x00000735, 0x0080 }, /* R1845 - AIF1TX7MIX Input 3 Volume */ + { 0x00000736, 0x0000 }, /* R1846 - AIF1TX7MIX Input 4 Source */ + { 0x00000737, 0x0080 }, /* R1847 - AIF1TX7MIX Input 4 Volume */ + { 0x00000738, 0x0000 }, /* R1848 - AIF1TX8MIX Input 1 Source */ + { 0x00000739, 0x0080 }, /* R1849 - AIF1TX8MIX Input 1 Volume */ + { 0x0000073A, 0x0000 }, /* R1850 - AIF1TX8MIX Input 2 Source */ + { 0x0000073B, 0x0080 }, /* R1851 - AIF1TX8MIX Input 2 Volume */ + { 0x0000073C, 0x0000 }, /* R1852 - AIF1TX8MIX Input 3 Source */ + { 0x0000073D, 0x0080 }, /* R1853 - AIF1TX8MIX Input 3 Volume */ + { 0x0000073E, 0x0000 }, /* R1854 - AIF1TX8MIX Input 4 Source */ + { 0x0000073F, 0x0080 }, /* R1855 - AIF1TX8MIX Input 4 Volume */ + { 0x00000740, 0x0000 }, /* R1856 - AIF2TX1MIX Input 1 Source */ + { 0x00000741, 0x0080 }, /* R1857 - AIF2TX1MIX Input 1 Volume */ + { 0x00000742, 0x0000 }, /* R1858 - AIF2TX1MIX Input 2 Source */ + { 0x00000743, 0x0080 }, /* R1859 - AIF2TX1MIX Input 2 Volume */ + { 0x00000744, 0x0000 }, /* R1860 - AIF2TX1MIX Input 3 Source */ + { 0x00000745, 0x0080 }, /* R1861 - AIF2TX1MIX Input 3 Volume */ + { 0x00000746, 0x0000 }, /* R1862 - AIF2TX1MIX Input 4 Source */ + { 0x00000747, 0x0080 }, /* R1863 - AIF2TX1MIX Input 4 Volume */ + { 0x00000748, 0x0000 }, /* R1864 - AIF2TX2MIX Input 1 Source */ + { 0x00000749, 0x0080 }, /* R1865 - AIF2TX2MIX Input 1 Volume */ + { 0x0000074A, 0x0000 }, /* R1866 - AIF2TX2MIX Input 2 Source */ + { 0x0000074B, 0x0080 }, /* R1867 - AIF2TX2MIX Input 2 Volume */ + { 0x0000074C, 0x0000 }, /* R1868 - AIF2TX2MIX Input 3 Source */ + { 0x0000074D, 0x0080 }, /* R1869 - AIF2TX2MIX Input 3 Volume */ + { 0x0000074E, 0x0000 }, /* R1870 - AIF2TX2MIX Input 4 Source */ + { 0x0000074F, 0x0080 }, /* R1871 - AIF2TX2MIX Input 4 Volume */ + { 0x00000780, 0x0000 }, /* R1920 - AIF3TX1MIX Input 1 Source */ + { 0x00000781, 0x0080 }, /* R1921 - AIF3TX1MIX Input 1 Volume */ + { 0x00000782, 0x0000 }, /* R1922 - AIF3TX1MIX Input 2 Source */ + { 0x00000783, 0x0080 }, /* R1923 - AIF3TX1MIX Input 2 Volume */ + { 0x00000784, 0x0000 }, /* R1924 - AIF3TX1MIX Input 3 Source */ + { 0x00000785, 0x0080 }, /* R1925 - AIF3TX1MIX Input 3 Volume */ + { 0x00000786, 0x0000 }, /* R1926 - AIF3TX1MIX Input 4 Source */ + { 0x00000787, 0x0080 }, /* R1927 - AIF3TX1MIX Input 4 Volume */ + { 0x00000788, 0x0000 }, /* R1928 - AIF3TX2MIX Input 1 Source */ + { 0x00000789, 0x0080 }, /* R1929 - AIF3TX2MIX Input 1 Volume */ + { 0x0000078A, 0x0000 }, /* R1930 - AIF3TX2MIX Input 2 Source */ + { 0x0000078B, 0x0080 }, /* R1931 - AIF3TX2MIX Input 2 Volume */ + { 0x0000078C, 0x0000 }, /* R1932 - AIF3TX2MIX Input 3 Source */ + { 0x0000078D, 0x0080 }, /* R1933 - AIF3TX2MIX Input 3 Volume */ + { 0x0000078E, 0x0000 }, /* R1934 - AIF3TX2MIX Input 4 Source */ + { 0x0000078F, 0x0080 }, /* R1935 - AIF3TX2MIX Input 4 Volume */ + { 0x000007C0, 0x0000 }, /* R1984 - SLIMTX1MIX Input 1 Source */ + { 0x000007C1, 0x0080 }, /* R1985 - SLIMTX1MIX Input 1 Volume */ + { 0x000007C2, 0x0000 }, /* R1986 - SLIMTX1MIX Input 2 Source */ + { 0x000007C3, 0x0080 }, /* R1987 - SLIMTX1MIX Input 2 Volume */ + { 0x000007C4, 0x0000 }, /* R1988 - SLIMTX1MIX Input 3 Source */ + { 0x000007C5, 0x0080 }, /* R1989 - SLIMTX1MIX Input 3 Volume */ + { 0x000007C6, 0x0000 }, /* R1990 - SLIMTX1MIX Input 4 Source */ + { 0x000007C7, 0x0080 }, /* R1991 - SLIMTX1MIX Input 4 Volume */ + { 0x000007C8, 0x0000 }, /* R1992 - SLIMTX2MIX Input 1 Source */ + { 0x000007C9, 0x0080 }, /* R1993 - SLIMTX2MIX Input 1 Volume */ + { 0x000007CA, 0x0000 }, /* R1994 - SLIMTX2MIX Input 2 Source */ + { 0x000007CB, 0x0080 }, /* R1995 - SLIMTX2MIX Input 2 Volume */ + { 0x000007CC, 0x0000 }, /* R1996 - SLIMTX2MIX Input 3 Source */ + { 0x000007CD, 0x0080 }, /* R1997 - SLIMTX2MIX Input 3 Volume */ + { 0x000007CE, 0x0000 }, /* R1998 - SLIMTX2MIX Input 4 Source */ + { 0x000007CF, 0x0080 }, /* R1999 - SLIMTX2MIX Input 4 Volume */ + { 0x000007D0, 0x0000 }, /* R2000 - SLIMTX3MIX Input 1 Source */ + { 0x000007D1, 0x0080 }, /* R2001 - SLIMTX3MIX Input 1 Volume */ + { 0x000007D2, 0x0000 }, /* R2002 - SLIMTX3MIX Input 2 Source */ + { 0x000007D3, 0x0080 }, /* R2003 - SLIMTX3MIX Input 2 Volume */ + { 0x000007D4, 0x0000 }, /* R2004 - SLIMTX3MIX Input 3 Source */ + { 0x000007D5, 0x0080 }, /* R2005 - SLIMTX3MIX Input 3 Volume */ + { 0x000007D6, 0x0000 }, /* R2006 - SLIMTX3MIX Input 4 Source */ + { 0x000007D7, 0x0080 }, /* R2007 - SLIMTX3MIX Input 4 Volume */ + { 0x000007D8, 0x0000 }, /* R2008 - SLIMTX4MIX Input 1 Source */ + { 0x000007D9, 0x0080 }, /* R2009 - SLIMTX4MIX Input 1 Volume */ + { 0x000007DA, 0x0000 }, /* R2010 - SLIMTX4MIX Input 2 Source */ + { 0x000007DB, 0x0080 }, /* R2011 - SLIMTX4MIX Input 2 Volume */ + { 0x000007DC, 0x0000 }, /* R2012 - SLIMTX4MIX Input 3 Source */ + { 0x000007DD, 0x0080 }, /* R2013 - SLIMTX4MIX Input 3 Volume */ + { 0x000007DE, 0x0000 }, /* R2014 - SLIMTX4MIX Input 4 Source */ + { 0x000007DF, 0x0080 }, /* R2015 - SLIMTX4MIX Input 4 Volume */ + { 0x000007E0, 0x0000 }, /* R2016 - SLIMTX5MIX Input 1 Source */ + { 0x000007E1, 0x0080 }, /* R2017 - SLIMTX5MIX Input 1 Volume */ + { 0x000007E2, 0x0000 }, /* R2018 - SLIMTX5MIX Input 2 Source */ + { 0x000007E3, 0x0080 }, /* R2019 - SLIMTX5MIX Input 2 Volume */ + { 0x000007E4, 0x0000 }, /* R2020 - SLIMTX5MIX Input 3 Source */ + { 0x000007E5, 0x0080 }, /* R2021 - SLIMTX5MIX Input 3 Volume */ + { 0x000007E6, 0x0000 }, /* R2022 - SLIMTX5MIX Input 4 Source */ + { 0x000007E7, 0x0080 }, /* R2023 - SLIMTX5MIX Input 4 Volume */ + { 0x000007E8, 0x0000 }, /* R2024 - SLIMTX6MIX Input 1 Source */ + { 0x000007E9, 0x0080 }, /* R2025 - SLIMTX6MIX Input 1 Volume */ + { 0x000007EA, 0x0000 }, /* R2026 - SLIMTX6MIX Input 2 Source */ + { 0x000007EB, 0x0080 }, /* R2027 - SLIMTX6MIX Input 2 Volume */ + { 0x000007EC, 0x0000 }, /* R2028 - SLIMTX6MIX Input 3 Source */ + { 0x000007ED, 0x0080 }, /* R2029 - SLIMTX6MIX Input 3 Volume */ + { 0x000007EE, 0x0000 }, /* R2030 - SLIMTX6MIX Input 4 Source */ + { 0x000007EF, 0x0080 }, /* R2031 - SLIMTX6MIX Input 4 Volume */ + { 0x000007F0, 0x0000 }, /* R2032 - SLIMTX7MIX Input 1 Source */ + { 0x000007F1, 0x0080 }, /* R2033 - SLIMTX7MIX Input 1 Volume */ + { 0x000007F2, 0x0000 }, /* R2034 - SLIMTX7MIX Input 2 Source */ + { 0x000007F3, 0x0080 }, /* R2035 - SLIMTX7MIX Input 2 Volume */ + { 0x000007F4, 0x0000 }, /* R2036 - SLIMTX7MIX Input 3 Source */ + { 0x000007F5, 0x0080 }, /* R2037 - SLIMTX7MIX Input 3 Volume */ + { 0x000007F6, 0x0000 }, /* R2038 - SLIMTX7MIX Input 4 Source */ + { 0x000007F7, 0x0080 }, /* R2039 - SLIMTX7MIX Input 4 Volume */ + { 0x000007F8, 0x0000 }, /* R2040 - SLIMTX8MIX Input 1 Source */ + { 0x000007F9, 0x0080 }, /* R2041 - SLIMTX8MIX Input 1 Volume */ + { 0x000007FA, 0x0000 }, /* R2042 - SLIMTX8MIX Input 2 Source */ + { 0x000007FB, 0x0080 }, /* R2043 - SLIMTX8MIX Input 2 Volume */ + { 0x000007FC, 0x0000 }, /* R2044 - SLIMTX8MIX Input 3 Source */ + { 0x000007FD, 0x0080 }, /* R2045 - SLIMTX8MIX Input 3 Volume */ + { 0x000007FE, 0x0000 }, /* R2046 - SLIMTX8MIX Input 4 Source */ + { 0x000007FF, 0x0080 }, /* R2047 - SLIMTX8MIX Input 4 Volume */ + { 0x00000880, 0x0000 }, /* R2176 - EQ1MIX Input 1 Source */ + { 0x00000881, 0x0080 }, /* R2177 - EQ1MIX Input 1 Volume */ + { 0x00000882, 0x0000 }, /* R2178 - EQ1MIX Input 2 Source */ + { 0x00000883, 0x0080 }, /* R2179 - EQ1MIX Input 2 Volume */ + { 0x00000884, 0x0000 }, /* R2180 - EQ1MIX Input 3 Source */ + { 0x00000885, 0x0080 }, /* R2181 - EQ1MIX Input 3 Volume */ + { 0x00000886, 0x0000 }, /* R2182 - EQ1MIX Input 4 Source */ + { 0x00000887, 0x0080 }, /* R2183 - EQ1MIX Input 4 Volume */ + { 0x00000888, 0x0000 }, /* R2184 - EQ2MIX Input 1 Source */ + { 0x00000889, 0x0080 }, /* R2185 - EQ2MIX Input 1 Volume */ + { 0x0000088A, 0x0000 }, /* R2186 - EQ2MIX Input 2 Source */ + { 0x0000088B, 0x0080 }, /* R2187 - EQ2MIX Input 2 Volume */ + { 0x0000088C, 0x0000 }, /* R2188 - EQ2MIX Input 3 Source */ + { 0x0000088D, 0x0080 }, /* R2189 - EQ2MIX Input 3 Volume */ + { 0x0000088E, 0x0000 }, /* R2190 - EQ2MIX Input 4 Source */ + { 0x0000088F, 0x0080 }, /* R2191 - EQ2MIX Input 4 Volume */ + { 0x00000890, 0x0000 }, /* R2192 - EQ3MIX Input 1 Source */ + { 0x00000891, 0x0080 }, /* R2193 - EQ3MIX Input 1 Volume */ + { 0x00000892, 0x0000 }, /* R2194 - EQ3MIX Input 2 Source */ + { 0x00000893, 0x0080 }, /* R2195 - EQ3MIX Input 2 Volume */ + { 0x00000894, 0x0000 }, /* R2196 - EQ3MIX Input 3 Source */ + { 0x00000895, 0x0080 }, /* R2197 - EQ3MIX Input 3 Volume */ + { 0x00000896, 0x0000 }, /* R2198 - EQ3MIX Input 4 Source */ + { 0x00000897, 0x0080 }, /* R2199 - EQ3MIX Input 4 Volume */ + { 0x00000898, 0x0000 }, /* R2200 - EQ4MIX Input 1 Source */ + { 0x00000899, 0x0080 }, /* R2201 - EQ4MIX Input 1 Volume */ + { 0x0000089A, 0x0000 }, /* R2202 - EQ4MIX Input 2 Source */ + { 0x0000089B, 0x0080 }, /* R2203 - EQ4MIX Input 2 Volume */ + { 0x0000089C, 0x0000 }, /* R2204 - EQ4MIX Input 3 Source */ + { 0x0000089D, 0x0080 }, /* R2205 - EQ4MIX Input 3 Volume */ + { 0x0000089E, 0x0000 }, /* R2206 - EQ4MIX Input 4 Source */ + { 0x0000089F, 0x0080 }, /* R2207 - EQ4MIX Input 4 Volume */ + { 0x000008C0, 0x0000 }, /* R2240 - DRC1LMIX Input 1 Source */ + { 0x000008C1, 0x0080 }, /* R2241 - DRC1LMIX Input 1 Volume */ + { 0x000008C2, 0x0000 }, /* R2242 - DRC1LMIX Input 2 Source */ + { 0x000008C3, 0x0080 }, /* R2243 - DRC1LMIX Input 2 Volume */ + { 0x000008C4, 0x0000 }, /* R2244 - DRC1LMIX Input 3 Source */ + { 0x000008C5, 0x0080 }, /* R2245 - DRC1LMIX Input 3 Volume */ + { 0x000008C6, 0x0000 }, /* R2246 - DRC1LMIX Input 4 Source */ + { 0x000008C7, 0x0080 }, /* R2247 - DRC1LMIX Input 4 Volume */ + { 0x000008C8, 0x0000 }, /* R2248 - DRC1RMIX Input 1 Source */ + { 0x000008C9, 0x0080 }, /* R2249 - DRC1RMIX Input 1 Volume */ + { 0x000008CA, 0x0000 }, /* R2250 - DRC1RMIX Input 2 Source */ + { 0x000008CB, 0x0080 }, /* R2251 - DRC1RMIX Input 2 Volume */ + { 0x000008CC, 0x0000 }, /* R2252 - DRC1RMIX Input 3 Source */ + { 0x000008CD, 0x0080 }, /* R2253 - DRC1RMIX Input 3 Volume */ + { 0x000008CE, 0x0000 }, /* R2254 - DRC1RMIX Input 4 Source */ + { 0x000008CF, 0x0080 }, /* R2255 - DRC1RMIX Input 4 Volume */ + { 0x00000900, 0x0000 }, /* R2304 - HPLP1MIX Input 1 Source */ + { 0x00000901, 0x0080 }, /* R2305 - HPLP1MIX Input 1 Volume */ + { 0x00000902, 0x0000 }, /* R2306 - HPLP1MIX Input 2 Source */ + { 0x00000903, 0x0080 }, /* R2307 - HPLP1MIX Input 2 Volume */ + { 0x00000904, 0x0000 }, /* R2308 - HPLP1MIX Input 3 Source */ + { 0x00000905, 0x0080 }, /* R2309 - HPLP1MIX Input 3 Volume */ + { 0x00000906, 0x0000 }, /* R2310 - HPLP1MIX Input 4 Source */ + { 0x00000907, 0x0080 }, /* R2311 - HPLP1MIX Input 4 Volume */ + { 0x00000908, 0x0000 }, /* R2312 - HPLP2MIX Input 1 Source */ + { 0x00000909, 0x0080 }, /* R2313 - HPLP2MIX Input 1 Volume */ + { 0x0000090A, 0x0000 }, /* R2314 - HPLP2MIX Input 2 Source */ + { 0x0000090B, 0x0080 }, /* R2315 - HPLP2MIX Input 2 Volume */ + { 0x0000090C, 0x0000 }, /* R2316 - HPLP2MIX Input 3 Source */ + { 0x0000090D, 0x0080 }, /* R2317 - HPLP2MIX Input 3 Volume */ + { 0x0000090E, 0x0000 }, /* R2318 - HPLP2MIX Input 4 Source */ + { 0x0000090F, 0x0080 }, /* R2319 - HPLP2MIX Input 4 Volume */ + { 0x00000910, 0x0000 }, /* R2320 - HPLP3MIX Input 1 Source */ + { 0x00000911, 0x0080 }, /* R2321 - HPLP3MIX Input 1 Volume */ + { 0x00000912, 0x0000 }, /* R2322 - HPLP3MIX Input 2 Source */ + { 0x00000913, 0x0080 }, /* R2323 - HPLP3MIX Input 2 Volume */ + { 0x00000914, 0x0000 }, /* R2324 - HPLP3MIX Input 3 Source */ + { 0x00000915, 0x0080 }, /* R2325 - HPLP3MIX Input 3 Volume */ + { 0x00000916, 0x0000 }, /* R2326 - HPLP3MIX Input 4 Source */ + { 0x00000917, 0x0080 }, /* R2327 - HPLP3MIX Input 4 Volume */ + { 0x00000918, 0x0000 }, /* R2328 - HPLP4MIX Input 1 Source */ + { 0x00000919, 0x0080 }, /* R2329 - HPLP4MIX Input 1 Volume */ + { 0x0000091A, 0x0000 }, /* R2330 - HPLP4MIX Input 2 Source */ + { 0x0000091B, 0x0080 }, /* R2331 - HPLP4MIX Input 2 Volume */ + { 0x0000091C, 0x0000 }, /* R2332 - HPLP4MIX Input 3 Source */ + { 0x0000091D, 0x0080 }, /* R2333 - HPLP4MIX Input 3 Volume */ + { 0x0000091E, 0x0000 }, /* R2334 - HPLP4MIX Input 4 Source */ + { 0x0000091F, 0x0080 }, /* R2335 - HPLP4MIX Input 4 Volume */ + { 0x00000940, 0x0000 }, /* R2368 - DSP1LMIX Input 1 Source */ + { 0x00000941, 0x0080 }, /* R2369 - DSP1LMIX Input 1 Volume */ + { 0x00000942, 0x0000 }, /* R2370 - DSP1LMIX Input 2 Source */ + { 0x00000943, 0x0080 }, /* R2371 - DSP1LMIX Input 2 Volume */ + { 0x00000944, 0x0000 }, /* R2372 - DSP1LMIX Input 3 Source */ + { 0x00000945, 0x0080 }, /* R2373 - DSP1LMIX Input 3 Volume */ + { 0x00000946, 0x0000 }, /* R2374 - DSP1LMIX Input 4 Source */ + { 0x00000947, 0x0080 }, /* R2375 - DSP1LMIX Input 4 Volume */ + { 0x00000948, 0x0000 }, /* R2376 - DSP1RMIX Input 1 Source */ + { 0x00000949, 0x0080 }, /* R2377 - DSP1RMIX Input 1 Volume */ + { 0x0000094A, 0x0000 }, /* R2378 - DSP1RMIX Input 2 Source */ + { 0x0000094B, 0x0080 }, /* R2379 - DSP1RMIX Input 2 Volume */ + { 0x0000094C, 0x0000 }, /* R2380 - DSP1RMIX Input 3 Source */ + { 0x0000094D, 0x0080 }, /* R2381 - DSP1RMIX Input 3 Volume */ + { 0x0000094E, 0x0000 }, /* R2382 - DSP1RMIX Input 4 Source */ + { 0x0000094F, 0x0080 }, /* R2383 - DSP1RMIX Input 4 Volume */ + { 0x00000950, 0x0000 }, /* R2384 - DSP1AUX1MIX Input 1 Source */ + { 0x00000958, 0x0000 }, /* R2392 - DSP1AUX2MIX Input 1 Source */ + { 0x00000960, 0x0000 }, /* R2400 - DSP1AUX3MIX Input 1 Source */ + { 0x00000968, 0x0000 }, /* R2408 - DSP1AUX4MIX Input 1 Source */ + { 0x00000970, 0x0000 }, /* R2416 - DSP1AUX5MIX Input 1 Source */ + { 0x00000978, 0x0000 }, /* R2424 - DSP1AUX6MIX Input 1 Source */ + { 0x00000A80, 0x0000 }, /* R2688 - ASRC1LMIX Input 1 Source */ + { 0x00000A88, 0x0000 }, /* R2696 - ASRC1RMIX Input 1 Source */ + { 0x00000A90, 0x0000 }, /* R2704 - ASRC2LMIX Input 1 Source */ + { 0x00000A98, 0x0000 }, /* R2712 - ASRC2RMIX Input 1 Source */ + { 0x00000B00, 0x0000 }, /* R2816 - ISRC1DEC1MIX Input 1 Source */ + { 0x00000B08, 0x0000 }, /* R2824 - ISRC1DEC2MIX Input 1 Source */ + { 0x00000B20, 0x0000 }, /* R2848 - ISRC1INT1MIX Input 1 Source */ + { 0x00000B28, 0x0000 }, /* R2856 - ISRC1INT2MIX Input 1 Source */ + { 0x00000B40, 0x0000 }, /* R2880 - ISRC2DEC1MIX Input 1 Source */ + { 0x00000B48, 0x0000 }, /* R2888 - ISRC2DEC2MIX Input 1 Source */ + { 0x00000B60, 0x0000 }, /* R2912 - ISRC2INT1MIX Input 1 Source */ + { 0x00000B68, 0x0000 }, /* R2920 - ISRC2INT2MIX Input 1 Source */ + { 0x00000C00, 0xA101 }, /* R3072 - GPIO1 CTRL */ + { 0x00000C01, 0xA101 }, /* R3073 - GPIO2 CTRL */ + { 0x00000C02, 0xA101 }, /* R3074 - GPIO3 CTRL */ + { 0x00000C03, 0xA101 }, /* R3075 - GPIO4 CTRL */ + { 0x00000C04, 0xA101 }, /* R3076 - GPIO5 CTRL */ + { 0x00000C0F, 0x0400 }, /* R3087 - IRQ CTRL 1 */ + { 0x00000C10, 0x1000 }, /* R3088 - GPIO Debounce Config */ + { 0x00000C20, 0x8002 }, /* R3104 - Misc Pad Ctrl 1 */ + { 0x00000C21, 0x8001 }, /* R3105 - Misc Pad Ctrl 2 */ + { 0x00000C22, 0x0000 }, /* R3106 - Misc Pad Ctrl 3 */ + { 0x00000C23, 0x0000 }, /* R3107 - Misc Pad Ctrl 4 */ + { 0x00000C24, 0x0000 }, /* R3108 - Misc Pad Ctrl 5 */ + { 0x00000C25, 0x0000 }, /* R3109 - Misc Pad Ctrl 6 */ + { 0x00000D08, 0xFFFF }, /* R3336 - Interrupt Status 1 Mask */ + { 0x00000D09, 0xFFFF }, /* R3337 - Interrupt Status 2 Mask */ + { 0x00000D0A, 0xFFFF }, /* R3338 - Interrupt Status 3 Mask */ + { 0x00000D0B, 0xFFFF }, /* R3339 - Interrupt Status 4 Mask */ + { 0x00000D0C, 0xFEFF }, /* R3340 - Interrupt Status 5 Mask */ + { 0x00000D0F, 0x0000 }, /* R3343 - Interrupt Control */ + { 0x00000D18, 0xFFFF }, /* R3352 - IRQ2 Status 1 Mask */ + { 0x00000D19, 0xFFFF }, /* R3353 - IRQ2 Status 2 Mask */ + { 0x00000D1A, 0xFFFF }, /* R3354 - IRQ2 Status 3 Mask */ + { 0x00000D1B, 0xFFFF }, /* R3355 - IRQ2 Status 4 Mask */ + { 0x00000D1C, 0xFFFF }, /* R3356 - IRQ2 Status 5 Mask */ + { 0x00000D1F, 0x0000 }, /* R3359 - IRQ2 Control */ + { 0x00000D53, 0xFFFF }, /* R3411 - AOD IRQ Mask IRQ1 */ + { 0x00000D54, 0xFFFF }, /* R3412 - AOD IRQ Mask IRQ2 */ + { 0x00000D56, 0x0000 }, /* R3414 - Jack detect debounce */ + { 0x00000E00, 0x0000 }, /* R3584 - FX_Ctrl1 */ + { 0x00000E01, 0x0000 }, /* R3585 - FX_Ctrl2 */ + { 0x00000E10, 0x6318 }, /* R3600 - EQ1_1 */ + { 0x00000E11, 0x6300 }, /* R3601 - EQ1_2 */ + { 0x00000E12, 0x0FC8 }, /* R3602 - EQ1_3 */ + { 0x00000E13, 0x03FE }, /* R3603 - EQ1_4 */ + { 0x00000E14, 0x00E0 }, /* R3604 - EQ1_5 */ + { 0x00000E15, 0x1EC4 }, /* R3605 - EQ1_6 */ + { 0x00000E16, 0xF136 }, /* R3606 - EQ1_7 */ + { 0x00000E17, 0x0409 }, /* R3607 - EQ1_8 */ + { 0x00000E18, 0x04CC }, /* R3608 - EQ1_9 */ + { 0x00000E19, 0x1C9B }, /* R3609 - EQ1_10 */ + { 0x00000E1A, 0xF337 }, /* R3610 - EQ1_11 */ + { 0x00000E1B, 0x040B }, /* R3611 - EQ1_12 */ + { 0x00000E1C, 0x0CBB }, /* R3612 - EQ1_13 */ + { 0x00000E1D, 0x16F8 }, /* R3613 - EQ1_14 */ + { 0x00000E1E, 0xF7D9 }, /* R3614 - EQ1_15 */ + { 0x00000E1F, 0x040A }, /* R3615 - EQ1_16 */ + { 0x00000E20, 0x1F14 }, /* R3616 - EQ1_17 */ + { 0x00000E21, 0x058C }, /* R3617 - EQ1_18 */ + { 0x00000E22, 0x0563 }, /* R3618 - EQ1_19 */ + { 0x00000E23, 0x4000 }, /* R3619 - EQ1_20 */ + { 0x00000E24, 0x0B75 }, /* R3620 - EQ1_21 */ + { 0x00000E26, 0x6318 }, /* R3622 - EQ2_1 */ + { 0x00000E27, 0x6300 }, /* R3623 - EQ2_2 */ + { 0x00000E28, 0x0FC8 }, /* R3624 - EQ2_3 */ + { 0x00000E29, 0x03FE }, /* R3625 - EQ2_4 */ + { 0x00000E2A, 0x00E0 }, /* R3626 - EQ2_5 */ + { 0x00000E2B, 0x1EC4 }, /* R3627 - EQ2_6 */ + { 0x00000E2C, 0xF136 }, /* R3628 - EQ2_7 */ + { 0x00000E2D, 0x0409 }, /* R3629 - EQ2_8 */ + { 0x00000E2E, 0x04CC }, /* R3630 - EQ2_9 */ + { 0x00000E2F, 0x1C9B }, /* R3631 - EQ2_10 */ + { 0x00000E30, 0xF337 }, /* R3632 - EQ2_11 */ + { 0x00000E31, 0x040B }, /* R3633 - EQ2_12 */ + { 0x00000E32, 0x0CBB }, /* R3634 - EQ2_13 */ + { 0x00000E33, 0x16F8 }, /* R3635 - EQ2_14 */ + { 0x00000E34, 0xF7D9 }, /* R3636 - EQ2_15 */ + { 0x00000E35, 0x040A }, /* R3637 - EQ2_16 */ + { 0x00000E36, 0x1F14 }, /* R3638 - EQ2_17 */ + { 0x00000E37, 0x058C }, /* R3639 - EQ2_18 */ + { 0x00000E38, 0x0563 }, /* R3640 - EQ2_19 */ + { 0x00000E39, 0x4000 }, /* R3641 - EQ2_20 */ + { 0x00000E3A, 0x0B75 }, /* R3642 - EQ2_21 */ + { 0x00000E3C, 0x6318 }, /* R3644 - EQ3_1 */ + { 0x00000E3D, 0x6300 }, /* R3645 - EQ3_2 */ + { 0x00000E3E, 0x0FC8 }, /* R3646 - EQ3_3 */ + { 0x00000E3F, 0x03FE }, /* R3647 - EQ3_4 */ + { 0x00000E40, 0x00E0 }, /* R3648 - EQ3_5 */ + { 0x00000E41, 0x1EC4 }, /* R3649 - EQ3_6 */ + { 0x00000E42, 0xF136 }, /* R3650 - EQ3_7 */ + { 0x00000E43, 0x0409 }, /* R3651 - EQ3_8 */ + { 0x00000E44, 0x04CC }, /* R3652 - EQ3_9 */ + { 0x00000E45, 0x1C9B }, /* R3653 - EQ3_10 */ + { 0x00000E46, 0xF337 }, /* R3654 - EQ3_11 */ + { 0x00000E47, 0x040B }, /* R3655 - EQ3_12 */ + { 0x00000E48, 0x0CBB }, /* R3656 - EQ3_13 */ + { 0x00000E49, 0x16F8 }, /* R3657 - EQ3_14 */ + { 0x00000E4A, 0xF7D9 }, /* R3658 - EQ3_15 */ + { 0x00000E4B, 0x040A }, /* R3659 - EQ3_16 */ + { 0x00000E4C, 0x1F14 }, /* R3660 - EQ3_17 */ + { 0x00000E4D, 0x058C }, /* R3661 - EQ3_18 */ + { 0x00000E4E, 0x0563 }, /* R3662 - EQ3_19 */ + { 0x00000E4F, 0x4000 }, /* R3663 - EQ3_20 */ + { 0x00000E50, 0x0B75 }, /* R3664 - EQ3_21 */ + { 0x00000E52, 0x6318 }, /* R3666 - EQ4_1 */ + { 0x00000E53, 0x6300 }, /* R3667 - EQ4_2 */ + { 0x00000E54, 0x0FC8 }, /* R3668 - EQ4_3 */ + { 0x00000E55, 0x03FE }, /* R3669 - EQ4_4 */ + { 0x00000E56, 0x00E0 }, /* R3670 - EQ4_5 */ + { 0x00000E57, 0x1EC4 }, /* R3671 - EQ4_6 */ + { 0x00000E58, 0xF136 }, /* R3672 - EQ4_7 */ + { 0x00000E59, 0x0409 }, /* R3673 - EQ4_8 */ + { 0x00000E5A, 0x04CC }, /* R3674 - EQ4_9 */ + { 0x00000E5B, 0x1C9B }, /* R3675 - EQ4_10 */ + { 0x00000E5C, 0xF337 }, /* R3676 - EQ4_11 */ + { 0x00000E5D, 0x040B }, /* R3677 - EQ4_12 */ + { 0x00000E5E, 0x0CBB }, /* R3678 - EQ4_13 */ + { 0x00000E5F, 0x16F8 }, /* R3679 - EQ4_14 */ + { 0x00000E60, 0xF7D9 }, /* R3680 - EQ4_15 */ + { 0x00000E61, 0x040A }, /* R3681 - EQ4_16 */ + { 0x00000E62, 0x1F14 }, /* R3682 - EQ4_17 */ + { 0x00000E63, 0x058C }, /* R3683 - EQ4_18 */ + { 0x00000E64, 0x0563 }, /* R3684 - EQ4_19 */ + { 0x00000E65, 0x4000 }, /* R3685 - EQ4_20 */ + { 0x00000E66, 0x0B75 }, /* R3686 - EQ4_21 */ + { 0x00000E80, 0x0018 }, /* R3712 - DRC1 ctrl1 */ + { 0x00000E81, 0x0933 }, /* R3713 - DRC1 ctrl2 */ + { 0x00000E82, 0x0018 }, /* R3714 - DRC1 ctrl3 */ + { 0x00000E83, 0x0000 }, /* R3715 - DRC1 ctrl4 */ + { 0x00000E84, 0x0000 }, /* R3716 - DRC1 ctrl5 */ + { 0x00000EC0, 0x0000 }, /* R3776 - HPLPF1_1 */ + { 0x00000EC1, 0x0000 }, /* R3777 - HPLPF1_2 */ + { 0x00000EC4, 0x0000 }, /* R3780 - HPLPF2_1 */ + { 0x00000EC5, 0x0000 }, /* R3781 - HPLPF2_2 */ + { 0x00000EC8, 0x0000 }, /* R3784 - HPLPF3_1 */ + { 0x00000EC9, 0x0000 }, /* R3785 - HPLPF3_2 */ + { 0x00000ECC, 0x0000 }, /* R3788 - HPLPF4_1 */ + { 0x00000ECD, 0x0000 }, /* R3789 - HPLPF4_2 */ + { 0x00000EE0, 0x0000 }, /* R3808 - ASRC_ENABLE */ + { 0x00000EE2, 0x0000 }, /* R3810 - ASRC_RATE1 */ + { 0x00000EF0, 0x0000 }, /* R3824 - ISRC 1 CTRL 1 */ + { 0x00000EF1, 0x0000 }, /* R3825 - ISRC 1 CTRL 2 */ + { 0x00000EF2, 0x0000 }, /* R3826 - ISRC 1 CTRL 3 */ + { 0x00000EF3, 0x0000 }, /* R3827 - ISRC 2 CTRL 1 */ + { 0x00000EF4, 0x0000 }, /* R3828 - ISRC 2 CTRL 2 */ + { 0x00000EF5, 0x0000 }, /* R3829 - ISRC 2 CTRL 3 */ + { 0x00001100, 0x0010 }, /* R4352 - DSP1 Control 1 */ + { 0x00001101, 0x0000 }, /* R4353 - DSP1 Clocking 1 */ +}; + +static bool wm5102_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ARIZONA_SOFTWARE_RESET: + case ARIZONA_DEVICE_REVISION: + case ARIZONA_CTRL_IF_SPI_CFG_1: + case ARIZONA_CTRL_IF_I2C1_CFG_1: + case ARIZONA_CTRL_IF_STATUS_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_WRITE_SEQUENCER_CTRL_3: + case ARIZONA_WRITE_SEQUENCER_PROM: + case ARIZONA_TONE_GENERATOR_1: + case ARIZONA_TONE_GENERATOR_2: + case ARIZONA_TONE_GENERATOR_3: + case ARIZONA_TONE_GENERATOR_4: + case ARIZONA_TONE_GENERATOR_5: + case ARIZONA_PWM_DRIVE_1: + case ARIZONA_PWM_DRIVE_2: + case ARIZONA_PWM_DRIVE_3: + case ARIZONA_WAKE_CONTROL: + case ARIZONA_SEQUENCE_CONTROL: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_1: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_2: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_3: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_4: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_1: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_2: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_3: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_4: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_5: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_6: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_7: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_8: + case ARIZONA_COMFORT_NOISE_GENERATOR: + case ARIZONA_HAPTICS_CONTROL_1: + case ARIZONA_HAPTICS_CONTROL_2: + case ARIZONA_HAPTICS_PHASE_1_INTENSITY: + case ARIZONA_HAPTICS_PHASE_1_DURATION: + case ARIZONA_HAPTICS_PHASE_2_INTENSITY: + case ARIZONA_HAPTICS_PHASE_2_DURATION: + case ARIZONA_HAPTICS_PHASE_3_INTENSITY: + case ARIZONA_HAPTICS_PHASE_3_DURATION: + case ARIZONA_HAPTICS_STATUS: + case ARIZONA_CLOCK_32K_1: + case ARIZONA_SYSTEM_CLOCK_1: + case ARIZONA_SAMPLE_RATE_1: + case ARIZONA_SAMPLE_RATE_2: + case ARIZONA_SAMPLE_RATE_3: + case ARIZONA_SAMPLE_RATE_1_STATUS: + case ARIZONA_SAMPLE_RATE_2_STATUS: + case ARIZONA_SAMPLE_RATE_3_STATUS: + case ARIZONA_ASYNC_CLOCK_1: + case ARIZONA_ASYNC_SAMPLE_RATE_1: + case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: + case ARIZONA_OUTPUT_SYSTEM_CLOCK: + case ARIZONA_OUTPUT_ASYNC_CLOCK: + case ARIZONA_RATE_ESTIMATOR_1: + case ARIZONA_RATE_ESTIMATOR_2: + case ARIZONA_RATE_ESTIMATOR_3: + case ARIZONA_RATE_ESTIMATOR_4: + case ARIZONA_RATE_ESTIMATOR_5: + case ARIZONA_DYNAMIC_FREQUENCY_SCALING_1: + case ARIZONA_FLL1_CONTROL_1: + case ARIZONA_FLL1_CONTROL_2: + case ARIZONA_FLL1_CONTROL_3: + case ARIZONA_FLL1_CONTROL_4: + case ARIZONA_FLL1_CONTROL_5: + case ARIZONA_FLL1_CONTROL_6: + case ARIZONA_FLL1_LOOP_FILTER_TEST_1: + case ARIZONA_FLL1_NCO_TEST_0: + case ARIZONA_FLL1_CONTROL_7: + case ARIZONA_FLL1_SYNCHRONISER_1: + case ARIZONA_FLL1_SYNCHRONISER_2: + case ARIZONA_FLL1_SYNCHRONISER_3: + case ARIZONA_FLL1_SYNCHRONISER_4: + case ARIZONA_FLL1_SYNCHRONISER_5: + case ARIZONA_FLL1_SYNCHRONISER_6: + case ARIZONA_FLL1_SYNCHRONISER_7: + case ARIZONA_FLL1_SPREAD_SPECTRUM: + case ARIZONA_FLL1_GPIO_CLOCK: + case ARIZONA_FLL2_CONTROL_1: + case ARIZONA_FLL2_CONTROL_2: + case ARIZONA_FLL2_CONTROL_3: + case ARIZONA_FLL2_CONTROL_4: + case ARIZONA_FLL2_CONTROL_5: + case ARIZONA_FLL2_CONTROL_6: + case ARIZONA_FLL2_LOOP_FILTER_TEST_1: + case ARIZONA_FLL2_NCO_TEST_0: + case ARIZONA_FLL2_CONTROL_7: + case ARIZONA_FLL2_SYNCHRONISER_1: + case ARIZONA_FLL2_SYNCHRONISER_2: + case ARIZONA_FLL2_SYNCHRONISER_3: + case ARIZONA_FLL2_SYNCHRONISER_4: + case ARIZONA_FLL2_SYNCHRONISER_5: + case ARIZONA_FLL2_SYNCHRONISER_6: + case ARIZONA_FLL2_SYNCHRONISER_7: + case ARIZONA_FLL2_SPREAD_SPECTRUM: + case ARIZONA_FLL2_GPIO_CLOCK: + case ARIZONA_MIC_CHARGE_PUMP_1: + case ARIZONA_LDO1_CONTROL_1: + case ARIZONA_LDO1_CONTROL_2: + case ARIZONA_LDO2_CONTROL_1: + case ARIZONA_MIC_BIAS_CTRL_1: + case ARIZONA_MIC_BIAS_CTRL_2: + case ARIZONA_MIC_BIAS_CTRL_3: + case ARIZONA_HP_CTRL_1L: + case ARIZONA_HP_CTRL_1R: + case ARIZONA_ACCESSORY_DETECT_MODE_1: + case ARIZONA_HEADPHONE_DETECT_1: + case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_HP_DACVAL: + case ARIZONA_MICD_CLAMP_CONTROL: + case ARIZONA_MIC_DETECT_1: + case ARIZONA_MIC_DETECT_2: + case ARIZONA_MIC_DETECT_3: + case ARIZONA_MIC_DETECT_LEVEL_1: + case ARIZONA_MIC_DETECT_LEVEL_2: + case ARIZONA_MIC_DETECT_LEVEL_3: + case ARIZONA_MIC_DETECT_LEVEL_4: + case ARIZONA_MIC_NOISE_MIX_CONTROL_1: + case ARIZONA_ISOLATION_CONTROL: + case ARIZONA_JACK_DETECT_ANALOGUE: + case ARIZONA_INPUT_ENABLES: + case ARIZONA_INPUT_RATE: + case ARIZONA_INPUT_VOLUME_RAMP: + case ARIZONA_IN1L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_1L: + case ARIZONA_DMIC1L_CONTROL: + case ARIZONA_IN1R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_1R: + case ARIZONA_DMIC1R_CONTROL: + case ARIZONA_IN2L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_2L: + case ARIZONA_DMIC2L_CONTROL: + case ARIZONA_IN2R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_2R: + case ARIZONA_DMIC2R_CONTROL: + case ARIZONA_IN3L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_3L: + case ARIZONA_DMIC3L_CONTROL: + case ARIZONA_IN3R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_3R: + case ARIZONA_DMIC3R_CONTROL: + case ARIZONA_OUTPUT_ENABLES_1: + case ARIZONA_OUTPUT_STATUS_1: + case ARIZONA_OUTPUT_RATE_1: + case ARIZONA_OUTPUT_VOLUME_RAMP: + case ARIZONA_OUTPUT_PATH_CONFIG_1L: + case ARIZONA_DAC_DIGITAL_VOLUME_1L: + case ARIZONA_DAC_VOLUME_LIMIT_1L: + case ARIZONA_NOISE_GATE_SELECT_1L: + case ARIZONA_OUTPUT_PATH_CONFIG_1R: + case ARIZONA_DAC_DIGITAL_VOLUME_1R: + case ARIZONA_DAC_VOLUME_LIMIT_1R: + case ARIZONA_NOISE_GATE_SELECT_1R: + case ARIZONA_OUTPUT_PATH_CONFIG_2L: + case ARIZONA_DAC_DIGITAL_VOLUME_2L: + case ARIZONA_DAC_VOLUME_LIMIT_2L: + case ARIZONA_NOISE_GATE_SELECT_2L: + case ARIZONA_OUTPUT_PATH_CONFIG_2R: + case ARIZONA_DAC_DIGITAL_VOLUME_2R: + case ARIZONA_DAC_VOLUME_LIMIT_2R: + case ARIZONA_NOISE_GATE_SELECT_2R: + case ARIZONA_OUTPUT_PATH_CONFIG_3L: + case ARIZONA_DAC_DIGITAL_VOLUME_3L: + case ARIZONA_DAC_VOLUME_LIMIT_3L: + case ARIZONA_NOISE_GATE_SELECT_3L: + case ARIZONA_OUTPUT_PATH_CONFIG_4L: + case ARIZONA_DAC_DIGITAL_VOLUME_4L: + case ARIZONA_OUT_VOLUME_4L: + case ARIZONA_NOISE_GATE_SELECT_4L: + case ARIZONA_OUTPUT_PATH_CONFIG_4R: + case ARIZONA_DAC_DIGITAL_VOLUME_4R: + case ARIZONA_OUT_VOLUME_4R: + case ARIZONA_NOISE_GATE_SELECT_4R: + case ARIZONA_OUTPUT_PATH_CONFIG_5L: + case ARIZONA_DAC_DIGITAL_VOLUME_5L: + case ARIZONA_DAC_VOLUME_LIMIT_5L: + case ARIZONA_NOISE_GATE_SELECT_5L: + case ARIZONA_OUTPUT_PATH_CONFIG_5R: + case ARIZONA_DAC_DIGITAL_VOLUME_5R: + case ARIZONA_DAC_VOLUME_LIMIT_5R: + case ARIZONA_NOISE_GATE_SELECT_5R: + case ARIZONA_DRE_ENABLE: + case ARIZONA_DRE_CONTROL_2: + case ARIZONA_DRE_CONTROL_3: + case ARIZONA_DAC_AEC_CONTROL_1: + case ARIZONA_NOISE_GATE_CONTROL: + case ARIZONA_PDM_SPK1_CTRL_1: + case ARIZONA_PDM_SPK1_CTRL_2: + case ARIZONA_SPK_CTRL_2: + case ARIZONA_SPK_CTRL_3: + case ARIZONA_DAC_COMP_1: + case ARIZONA_DAC_COMP_2: + case ARIZONA_DAC_COMP_3: + case ARIZONA_DAC_COMP_4: + case ARIZONA_AIF1_BCLK_CTRL: + case ARIZONA_AIF1_TX_PIN_CTRL: + case ARIZONA_AIF1_RX_PIN_CTRL: + case ARIZONA_AIF1_RATE_CTRL: + case ARIZONA_AIF1_FORMAT: + case ARIZONA_AIF1_TX_BCLK_RATE: + case ARIZONA_AIF1_RX_BCLK_RATE: + case ARIZONA_AIF1_FRAME_CTRL_1: + case ARIZONA_AIF1_FRAME_CTRL_2: + case ARIZONA_AIF1_FRAME_CTRL_3: + case ARIZONA_AIF1_FRAME_CTRL_4: + case ARIZONA_AIF1_FRAME_CTRL_5: + case ARIZONA_AIF1_FRAME_CTRL_6: + case ARIZONA_AIF1_FRAME_CTRL_7: + case ARIZONA_AIF1_FRAME_CTRL_8: + case ARIZONA_AIF1_FRAME_CTRL_9: + case ARIZONA_AIF1_FRAME_CTRL_10: + case ARIZONA_AIF1_FRAME_CTRL_11: + case ARIZONA_AIF1_FRAME_CTRL_12: + case ARIZONA_AIF1_FRAME_CTRL_13: + case ARIZONA_AIF1_FRAME_CTRL_14: + case ARIZONA_AIF1_FRAME_CTRL_15: + case ARIZONA_AIF1_FRAME_CTRL_16: + case ARIZONA_AIF1_FRAME_CTRL_17: + case ARIZONA_AIF1_FRAME_CTRL_18: + case ARIZONA_AIF1_TX_ENABLES: + case ARIZONA_AIF1_RX_ENABLES: + case ARIZONA_AIF1_FORCE_WRITE: + case ARIZONA_AIF2_BCLK_CTRL: + case ARIZONA_AIF2_TX_PIN_CTRL: + case ARIZONA_AIF2_RX_PIN_CTRL: + case ARIZONA_AIF2_RATE_CTRL: + case ARIZONA_AIF2_FORMAT: + case ARIZONA_AIF2_TX_BCLK_RATE: + case ARIZONA_AIF2_RX_BCLK_RATE: + case ARIZONA_AIF2_FRAME_CTRL_1: + case ARIZONA_AIF2_FRAME_CTRL_2: + case ARIZONA_AIF2_FRAME_CTRL_3: + case ARIZONA_AIF2_FRAME_CTRL_4: + case ARIZONA_AIF2_FRAME_CTRL_11: + case ARIZONA_AIF2_FRAME_CTRL_12: + case ARIZONA_AIF2_TX_ENABLES: + case ARIZONA_AIF2_RX_ENABLES: + case ARIZONA_AIF2_FORCE_WRITE: + case ARIZONA_AIF3_BCLK_CTRL: + case ARIZONA_AIF3_TX_PIN_CTRL: + case ARIZONA_AIF3_RX_PIN_CTRL: + case ARIZONA_AIF3_RATE_CTRL: + case ARIZONA_AIF3_FORMAT: + case ARIZONA_AIF3_TX_BCLK_RATE: + case ARIZONA_AIF3_RX_BCLK_RATE: + case ARIZONA_AIF3_FRAME_CTRL_1: + case ARIZONA_AIF3_FRAME_CTRL_2: + case ARIZONA_AIF3_FRAME_CTRL_3: + case ARIZONA_AIF3_FRAME_CTRL_4: + case ARIZONA_AIF3_FRAME_CTRL_11: + case ARIZONA_AIF3_FRAME_CTRL_12: + case ARIZONA_AIF3_TX_ENABLES: + case ARIZONA_AIF3_RX_ENABLES: + case ARIZONA_AIF3_FORCE_WRITE: + case ARIZONA_SLIMBUS_FRAMER_REF_GEAR: + case ARIZONA_SLIMBUS_RATES_1: + case ARIZONA_SLIMBUS_RATES_2: + case ARIZONA_SLIMBUS_RATES_3: + case ARIZONA_SLIMBUS_RATES_4: + case ARIZONA_SLIMBUS_RATES_5: + case ARIZONA_SLIMBUS_RATES_6: + case ARIZONA_SLIMBUS_RATES_7: + case ARIZONA_SLIMBUS_RATES_8: + case ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE: + case ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE: + case ARIZONA_SLIMBUS_RX_PORT_STATUS: + case ARIZONA_SLIMBUS_TX_PORT_STATUS: + case ARIZONA_PWM1MIX_INPUT_1_SOURCE: + case ARIZONA_PWM1MIX_INPUT_1_VOLUME: + case ARIZONA_PWM1MIX_INPUT_2_SOURCE: + case ARIZONA_PWM1MIX_INPUT_2_VOLUME: + case ARIZONA_PWM1MIX_INPUT_3_SOURCE: + case ARIZONA_PWM1MIX_INPUT_3_VOLUME: + case ARIZONA_PWM1MIX_INPUT_4_SOURCE: + case ARIZONA_PWM1MIX_INPUT_4_VOLUME: + case ARIZONA_PWM2MIX_INPUT_1_SOURCE: + case ARIZONA_PWM2MIX_INPUT_1_VOLUME: + case ARIZONA_PWM2MIX_INPUT_2_SOURCE: + case ARIZONA_PWM2MIX_INPUT_2_VOLUME: + case ARIZONA_PWM2MIX_INPUT_3_SOURCE: + case ARIZONA_PWM2MIX_INPUT_3_VOLUME: + case ARIZONA_PWM2MIX_INPUT_4_SOURCE: + case ARIZONA_PWM2MIX_INPUT_4_VOLUME: + case ARIZONA_MICMIX_INPUT_1_SOURCE: + case ARIZONA_MICMIX_INPUT_1_VOLUME: + case ARIZONA_MICMIX_INPUT_2_SOURCE: + case ARIZONA_MICMIX_INPUT_2_VOLUME: + case ARIZONA_MICMIX_INPUT_3_SOURCE: + case ARIZONA_MICMIX_INPUT_3_VOLUME: + case ARIZONA_MICMIX_INPUT_4_SOURCE: + case ARIZONA_MICMIX_INPUT_4_VOLUME: + case ARIZONA_NOISEMIX_INPUT_1_SOURCE: + case ARIZONA_NOISEMIX_INPUT_1_VOLUME: + case ARIZONA_NOISEMIX_INPUT_2_SOURCE: + case ARIZONA_NOISEMIX_INPUT_2_VOLUME: + case ARIZONA_NOISEMIX_INPUT_3_SOURCE: + case ARIZONA_NOISEMIX_INPUT_3_VOLUME: + case ARIZONA_NOISEMIX_INPUT_4_SOURCE: + case ARIZONA_NOISEMIX_INPUT_4_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_4_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_4_VOLUME: + case ARIZONA_EQ1MIX_INPUT_1_SOURCE: + case ARIZONA_EQ1MIX_INPUT_1_VOLUME: + case ARIZONA_EQ1MIX_INPUT_2_SOURCE: + case ARIZONA_EQ1MIX_INPUT_2_VOLUME: + case ARIZONA_EQ1MIX_INPUT_3_SOURCE: + case ARIZONA_EQ1MIX_INPUT_3_VOLUME: + case ARIZONA_EQ1MIX_INPUT_4_SOURCE: + case ARIZONA_EQ1MIX_INPUT_4_VOLUME: + case ARIZONA_EQ2MIX_INPUT_1_SOURCE: + case ARIZONA_EQ2MIX_INPUT_1_VOLUME: + case ARIZONA_EQ2MIX_INPUT_2_SOURCE: + case ARIZONA_EQ2MIX_INPUT_2_VOLUME: + case ARIZONA_EQ2MIX_INPUT_3_SOURCE: + case ARIZONA_EQ2MIX_INPUT_3_VOLUME: + case ARIZONA_EQ2MIX_INPUT_4_SOURCE: + case ARIZONA_EQ2MIX_INPUT_4_VOLUME: + case ARIZONA_EQ3MIX_INPUT_1_SOURCE: + case ARIZONA_EQ3MIX_INPUT_1_VOLUME: + case ARIZONA_EQ3MIX_INPUT_2_SOURCE: + case ARIZONA_EQ3MIX_INPUT_2_VOLUME: + case ARIZONA_EQ3MIX_INPUT_3_SOURCE: + case ARIZONA_EQ3MIX_INPUT_3_VOLUME: + case ARIZONA_EQ3MIX_INPUT_4_SOURCE: + case ARIZONA_EQ3MIX_INPUT_4_VOLUME: + case ARIZONA_EQ4MIX_INPUT_1_SOURCE: + case ARIZONA_EQ4MIX_INPUT_1_VOLUME: + case ARIZONA_EQ4MIX_INPUT_2_SOURCE: + case ARIZONA_EQ4MIX_INPUT_2_VOLUME: + case ARIZONA_EQ4MIX_INPUT_3_SOURCE: + case ARIZONA_EQ4MIX_INPUT_3_VOLUME: + case ARIZONA_EQ4MIX_INPUT_4_SOURCE: + case ARIZONA_EQ4MIX_INPUT_4_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_1_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_1_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_2_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_2_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_3_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_3_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_4_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_4_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_1_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_1_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_2_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_2_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_3_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_3_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_4_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_4_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_1_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_1_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_2_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_2_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_3_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_3_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_4_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_4_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_1_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_1_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_2_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_2_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_3_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_3_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_4_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_4_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_4_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_1_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_1_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_2_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_2_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_3_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_3_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_4_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_4_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_1_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_1_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_2_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_2_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_3_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_3_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_4_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_4_VOLUME: + case ARIZONA_DSP1AUX1MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX2MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX3MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX4MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX5MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX6MIX_INPUT_1_SOURCE: + case ARIZONA_ASRC1LMIX_INPUT_1_SOURCE: + case ARIZONA_ASRC1RMIX_INPUT_1_SOURCE: + case ARIZONA_ASRC2LMIX_INPUT_1_SOURCE: + case ARIZONA_ASRC2RMIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE: + case ARIZONA_GPIO1_CTRL: + case ARIZONA_GPIO2_CTRL: + case ARIZONA_GPIO3_CTRL: + case ARIZONA_GPIO4_CTRL: + case ARIZONA_GPIO5_CTRL: + case ARIZONA_IRQ_CTRL_1: + case ARIZONA_GPIO_DEBOUNCE_CONFIG: + case ARIZONA_MISC_PAD_CTRL_1: + case ARIZONA_MISC_PAD_CTRL_2: + case ARIZONA_MISC_PAD_CTRL_3: + case ARIZONA_MISC_PAD_CTRL_4: + case ARIZONA_MISC_PAD_CTRL_5: + case ARIZONA_MISC_PAD_CTRL_6: + case ARIZONA_INTERRUPT_STATUS_1: + case ARIZONA_INTERRUPT_STATUS_2: + case ARIZONA_INTERRUPT_STATUS_3: + case ARIZONA_INTERRUPT_STATUS_4: + case ARIZONA_INTERRUPT_STATUS_5: + case ARIZONA_INTERRUPT_STATUS_1_MASK: + case ARIZONA_INTERRUPT_STATUS_2_MASK: + case ARIZONA_INTERRUPT_STATUS_3_MASK: + case ARIZONA_INTERRUPT_STATUS_4_MASK: + case ARIZONA_INTERRUPT_STATUS_5_MASK: + case ARIZONA_INTERRUPT_CONTROL: + case ARIZONA_IRQ2_STATUS_1: + case ARIZONA_IRQ2_STATUS_2: + case ARIZONA_IRQ2_STATUS_3: + case ARIZONA_IRQ2_STATUS_4: + case ARIZONA_IRQ2_STATUS_5: + case ARIZONA_IRQ2_STATUS_1_MASK: + case ARIZONA_IRQ2_STATUS_2_MASK: + case ARIZONA_IRQ2_STATUS_3_MASK: + case ARIZONA_IRQ2_STATUS_4_MASK: + case ARIZONA_IRQ2_STATUS_5_MASK: + case ARIZONA_IRQ2_CONTROL: + case ARIZONA_INTERRUPT_RAW_STATUS_2: + case ARIZONA_INTERRUPT_RAW_STATUS_3: + case ARIZONA_INTERRUPT_RAW_STATUS_4: + case ARIZONA_INTERRUPT_RAW_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_7: + case ARIZONA_INTERRUPT_RAW_STATUS_8: + case ARIZONA_IRQ_PIN_STATUS: + case ARIZONA_ADSP2_IRQ0: + case ARIZONA_AOD_WKUP_AND_TRIG: + case ARIZONA_AOD_IRQ1: + case ARIZONA_AOD_IRQ2: + case ARIZONA_AOD_IRQ_MASK_IRQ1: + case ARIZONA_AOD_IRQ_MASK_IRQ2: + case ARIZONA_AOD_IRQ_RAW_STATUS: + case ARIZONA_JACK_DETECT_DEBOUNCE: + case ARIZONA_FX_CTRL1: + case ARIZONA_FX_CTRL2: + case ARIZONA_EQ1_1: + case ARIZONA_EQ1_2: + case ARIZONA_EQ1_3: + case ARIZONA_EQ1_4: + case ARIZONA_EQ1_5: + case ARIZONA_EQ1_6: + case ARIZONA_EQ1_7: + case ARIZONA_EQ1_8: + case ARIZONA_EQ1_9: + case ARIZONA_EQ1_10: + case ARIZONA_EQ1_11: + case ARIZONA_EQ1_12: + case ARIZONA_EQ1_13: + case ARIZONA_EQ1_14: + case ARIZONA_EQ1_15: + case ARIZONA_EQ1_16: + case ARIZONA_EQ1_17: + case ARIZONA_EQ1_18: + case ARIZONA_EQ1_19: + case ARIZONA_EQ1_20: + case ARIZONA_EQ1_21: + case ARIZONA_EQ2_1: + case ARIZONA_EQ2_2: + case ARIZONA_EQ2_3: + case ARIZONA_EQ2_4: + case ARIZONA_EQ2_5: + case ARIZONA_EQ2_6: + case ARIZONA_EQ2_7: + case ARIZONA_EQ2_8: + case ARIZONA_EQ2_9: + case ARIZONA_EQ2_10: + case ARIZONA_EQ2_11: + case ARIZONA_EQ2_12: + case ARIZONA_EQ2_13: + case ARIZONA_EQ2_14: + case ARIZONA_EQ2_15: + case ARIZONA_EQ2_16: + case ARIZONA_EQ2_17: + case ARIZONA_EQ2_18: + case ARIZONA_EQ2_19: + case ARIZONA_EQ2_20: + case ARIZONA_EQ2_21: + case ARIZONA_EQ3_1: + case ARIZONA_EQ3_2: + case ARIZONA_EQ3_3: + case ARIZONA_EQ3_4: + case ARIZONA_EQ3_5: + case ARIZONA_EQ3_6: + case ARIZONA_EQ3_7: + case ARIZONA_EQ3_8: + case ARIZONA_EQ3_9: + case ARIZONA_EQ3_10: + case ARIZONA_EQ3_11: + case ARIZONA_EQ3_12: + case ARIZONA_EQ3_13: + case ARIZONA_EQ3_14: + case ARIZONA_EQ3_15: + case ARIZONA_EQ3_16: + case ARIZONA_EQ3_17: + case ARIZONA_EQ3_18: + case ARIZONA_EQ3_19: + case ARIZONA_EQ3_20: + case ARIZONA_EQ3_21: + case ARIZONA_EQ4_1: + case ARIZONA_EQ4_2: + case ARIZONA_EQ4_3: + case ARIZONA_EQ4_4: + case ARIZONA_EQ4_5: + case ARIZONA_EQ4_6: + case ARIZONA_EQ4_7: + case ARIZONA_EQ4_8: + case ARIZONA_EQ4_9: + case ARIZONA_EQ4_10: + case ARIZONA_EQ4_11: + case ARIZONA_EQ4_12: + case ARIZONA_EQ4_13: + case ARIZONA_EQ4_14: + case ARIZONA_EQ4_15: + case ARIZONA_EQ4_16: + case ARIZONA_EQ4_17: + case ARIZONA_EQ4_18: + case ARIZONA_EQ4_19: + case ARIZONA_EQ4_20: + case ARIZONA_EQ4_21: + case ARIZONA_DRC1_CTRL1: + case ARIZONA_DRC1_CTRL2: + case ARIZONA_DRC1_CTRL3: + case ARIZONA_DRC1_CTRL4: + case ARIZONA_DRC1_CTRL5: + case ARIZONA_DRC2_CTRL1: + case ARIZONA_DRC2_CTRL2: + case ARIZONA_DRC2_CTRL3: + case ARIZONA_DRC2_CTRL4: + case ARIZONA_DRC2_CTRL5: + case ARIZONA_HPLPF1_1: + case ARIZONA_HPLPF1_2: + case ARIZONA_HPLPF2_1: + case ARIZONA_HPLPF2_2: + case ARIZONA_HPLPF3_1: + case ARIZONA_HPLPF3_2: + case ARIZONA_HPLPF4_1: + case ARIZONA_HPLPF4_2: + case ARIZONA_ASRC_ENABLE: + case ARIZONA_ASRC_RATE1: + case ARIZONA_ASRC_RATE2: + case ARIZONA_ISRC_1_CTRL_1: + case ARIZONA_ISRC_1_CTRL_2: + case ARIZONA_ISRC_1_CTRL_3: + case ARIZONA_ISRC_2_CTRL_1: + case ARIZONA_ISRC_2_CTRL_2: + case ARIZONA_ISRC_2_CTRL_3: + case ARIZONA_ISRC_3_CTRL_1: + case ARIZONA_ISRC_3_CTRL_2: + case ARIZONA_ISRC_3_CTRL_3: + case ARIZONA_DSP1_CONTROL_1: + case ARIZONA_DSP1_CLOCKING_1: + case ARIZONA_DSP1_STATUS_1: + case ARIZONA_DSP1_STATUS_2: + case ARIZONA_DSP1_STATUS_3: + case ARIZONA_DSP1_WDMA_BUFFER_1: + case ARIZONA_DSP1_WDMA_BUFFER_2: + case ARIZONA_DSP1_WDMA_BUFFER_3: + case ARIZONA_DSP1_WDMA_BUFFER_4: + case ARIZONA_DSP1_WDMA_BUFFER_5: + case ARIZONA_DSP1_WDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_BUFFER_7: + case ARIZONA_DSP1_WDMA_BUFFER_8: + case ARIZONA_DSP1_RDMA_BUFFER_1: + case ARIZONA_DSP1_RDMA_BUFFER_2: + case ARIZONA_DSP1_RDMA_BUFFER_3: + case ARIZONA_DSP1_RDMA_BUFFER_4: + case ARIZONA_DSP1_RDMA_BUFFER_5: + case ARIZONA_DSP1_RDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_CONFIG_1: + case ARIZONA_DSP1_WDMA_CONFIG_2: + case ARIZONA_DSP1_RDMA_CONFIG_1: + case ARIZONA_DSP1_SCRATCH_0: + case ARIZONA_DSP1_SCRATCH_1: + case ARIZONA_DSP1_SCRATCH_2: + case ARIZONA_DSP1_SCRATCH_3: + return true; + default: + if ((reg >= 0x100000 && reg < 0x106000) || + (reg >= 0x180000 && reg < 0x180800) || + (reg >= 0x190000 && reg < 0x194800) || + (reg >= 0x1a8000 && reg < 0x1a9800)) + return true; + else + return false; + } +} + +static bool wm5102_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ARIZONA_SOFTWARE_RESET: + case ARIZONA_DEVICE_REVISION: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_WRITE_SEQUENCER_CTRL_3: + case ARIZONA_OUTPUT_STATUS_1: + case ARIZONA_RAW_OUTPUT_STATUS_1: + case ARIZONA_SLIMBUS_RX_PORT_STATUS: + case ARIZONA_SLIMBUS_TX_PORT_STATUS: + case ARIZONA_SAMPLE_RATE_1_STATUS: + case ARIZONA_SAMPLE_RATE_2_STATUS: + case ARIZONA_SAMPLE_RATE_3_STATUS: + case ARIZONA_HAPTICS_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: + case ARIZONA_FLL1_NCO_TEST_0: + case ARIZONA_FLL2_NCO_TEST_0: + case ARIZONA_DAC_COMP_1: + case ARIZONA_DAC_COMP_2: + case ARIZONA_DAC_COMP_3: + case ARIZONA_DAC_COMP_4: + case ARIZONA_FX_CTRL2: + case ARIZONA_INTERRUPT_STATUS_1: + case ARIZONA_INTERRUPT_STATUS_2: + case ARIZONA_INTERRUPT_STATUS_3: + case ARIZONA_INTERRUPT_STATUS_4: + case ARIZONA_INTERRUPT_STATUS_5: + case ARIZONA_IRQ2_STATUS_1: + case ARIZONA_IRQ2_STATUS_2: + case ARIZONA_IRQ2_STATUS_3: + case ARIZONA_IRQ2_STATUS_4: + case ARIZONA_IRQ2_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_2: + case ARIZONA_INTERRUPT_RAW_STATUS_3: + case ARIZONA_INTERRUPT_RAW_STATUS_4: + case ARIZONA_INTERRUPT_RAW_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_7: + case ARIZONA_INTERRUPT_RAW_STATUS_8: + case ARIZONA_IRQ_PIN_STATUS: + case ARIZONA_AOD_WKUP_AND_TRIG: + case ARIZONA_AOD_IRQ1: + case ARIZONA_AOD_IRQ2: + case ARIZONA_AOD_IRQ_RAW_STATUS: + case ARIZONA_DSP1_CLOCKING_1: + case ARIZONA_DSP1_STATUS_1: + case ARIZONA_DSP1_STATUS_2: + case ARIZONA_DSP1_STATUS_3: + case ARIZONA_DSP1_WDMA_BUFFER_1: + case ARIZONA_DSP1_WDMA_BUFFER_2: + case ARIZONA_DSP1_WDMA_BUFFER_3: + case ARIZONA_DSP1_WDMA_BUFFER_4: + case ARIZONA_DSP1_WDMA_BUFFER_5: + case ARIZONA_DSP1_WDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_BUFFER_7: + case ARIZONA_DSP1_WDMA_BUFFER_8: + case ARIZONA_DSP1_RDMA_BUFFER_1: + case ARIZONA_DSP1_RDMA_BUFFER_2: + case ARIZONA_DSP1_RDMA_BUFFER_3: + case ARIZONA_DSP1_RDMA_BUFFER_4: + case ARIZONA_DSP1_RDMA_BUFFER_5: + case ARIZONA_DSP1_RDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_CONFIG_1: + case ARIZONA_DSP1_WDMA_CONFIG_2: + case ARIZONA_DSP1_RDMA_CONFIG_1: + case ARIZONA_DSP1_SCRATCH_0: + case ARIZONA_DSP1_SCRATCH_1: + case ARIZONA_DSP1_SCRATCH_2: + case ARIZONA_DSP1_SCRATCH_3: + case ARIZONA_HP_CTRL_1L: + case ARIZONA_HP_CTRL_1R: + case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_HP_DACVAL: + case ARIZONA_MIC_DETECT_3: + return true; + default: + if ((reg >= 0x100000 && reg < 0x106000) || + (reg >= 0x180000 && reg < 0x180800) || + (reg >= 0x190000 && reg < 0x194800) || + (reg >= 0x1a8000 && reg < 0x1a9800)) + return true; + else + return false; + } +} + +#define WM5102_MAX_REGISTER 0x1a9800 + +const struct regmap_config wm5102_spi_regmap = { + .reg_bits = 32, + .pad_bits = 16, + .val_bits = 16, + + .max_register = WM5102_MAX_REGISTER, + .readable_reg = wm5102_readable_register, + .volatile_reg = wm5102_volatile_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm5102_reg_default, + .num_reg_defaults = ARRAY_SIZE(wm5102_reg_default), +}; +EXPORT_SYMBOL_GPL(wm5102_spi_regmap); + +const struct regmap_config wm5102_i2c_regmap = { + .reg_bits = 32, + .val_bits = 16, + + .max_register = WM5102_MAX_REGISTER, + .readable_reg = wm5102_readable_register, + .volatile_reg = wm5102_volatile_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm5102_reg_default, + .num_reg_defaults = ARRAY_SIZE(wm5102_reg_default), +}; +EXPORT_SYMBOL_GPL(wm5102_i2c_regmap); diff --git a/kernel/drivers/mfd/wm5110-tables.c b/kernel/drivers/mfd/wm5110-tables.c new file mode 100644 index 000000000..12cad94b4 --- /dev/null +++ b/kernel/drivers/mfd/wm5110-tables.c @@ -0,0 +1,3033 @@ +/* + * wm5110-tables.c -- WM5110 data tables + * + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/registers.h> +#include <linux/device.h> + +#include "arizona.h" + +#define WM5110_NUM_AOD_ISR 2 +#define WM5110_NUM_ISR 5 + +static const struct reg_default wm5110_reva_patch[] = { + { 0x80, 0x3 }, + { 0x44, 0x20 }, + { 0x45, 0x40 }, + { 0x46, 0x60 }, + { 0x47, 0x80 }, + { 0x48, 0xa0 }, + { 0x51, 0x13 }, + { 0x52, 0x33 }, + { 0x53, 0x53 }, + { 0x54, 0x73 }, + { 0x55, 0x75 }, + { 0x56, 0xb3 }, + { 0x2ef, 0x124 }, + { 0x2ef, 0x124 }, + { 0x2f0, 0x124 }, + { 0x2f0, 0x124 }, + { 0x2f1, 0x124 }, + { 0x2f1, 0x124 }, + { 0x2f2, 0x124 }, + { 0x2f2, 0x124 }, + { 0x2f3, 0x124 }, + { 0x2f3, 0x124 }, + { 0x2f4, 0x124 }, + { 0x2f4, 0x124 }, + { 0x2eb, 0x60 }, + { 0x2ec, 0x60 }, + { 0x2ed, 0x60 }, + { 0xc30, 0x3e3e }, + { 0xc30, 0x3e3e }, + { 0xc31, 0x3e }, + { 0xc32, 0x3e3e }, + { 0xc32, 0x3e3e }, + { 0xc33, 0x3e3e }, + { 0xc33, 0x3e3e }, + { 0xc34, 0x3e3e }, + { 0xc34, 0x3e3e }, + { 0xc35, 0x3e3e }, + { 0xc35, 0x3e3e }, + { 0xc36, 0x3e3e }, + { 0xc36, 0x3e3e }, + { 0xc37, 0x3e3e }, + { 0xc37, 0x3e3e }, + { 0xc38, 0x3e3e }, + { 0xc38, 0x3e3e }, + { 0xc30, 0x3e3e }, + { 0xc30, 0x3e3e }, + { 0xc39, 0x3e3e }, + { 0xc39, 0x3e3e }, + { 0xc3a, 0x3e3e }, + { 0xc3a, 0x3e3e }, + { 0xc3b, 0x3e3e }, + { 0xc3b, 0x3e3e }, + { 0xc3c, 0x3e }, + { 0x201, 0x18a5 }, + { 0x201, 0x18a5 }, + { 0x201, 0x18a5 }, + { 0x202, 0x4100 }, + { 0x460, 0xc00 }, + { 0x461, 0x8000 }, + { 0x462, 0xc01 }, + { 0x463, 0x50f0 }, + { 0x464, 0xc01 }, + { 0x465, 0x4820 }, + { 0x466, 0xc01 }, + { 0x466, 0xc01 }, + { 0x467, 0x4040 }, + { 0x468, 0xc01 }, + { 0x468, 0xc01 }, + { 0x469, 0x3940 }, + { 0x46a, 0xc01 }, + { 0x46a, 0xc01 }, + { 0x46a, 0xc01 }, + { 0x46b, 0x3310 }, + { 0x46c, 0x801 }, + { 0x46c, 0x801 }, + { 0x46d, 0x2d80 }, + { 0x46e, 0x801 }, + { 0x46e, 0x801 }, + { 0x46f, 0x2890 }, + { 0x470, 0x801 }, + { 0x470, 0x801 }, + { 0x471, 0x1990 }, + { 0x472, 0x801 }, + { 0x472, 0x801 }, + { 0x473, 0x1450 }, + { 0x474, 0x801 }, + { 0x474, 0x801 }, + { 0x474, 0x801 }, + { 0x475, 0x1020 }, + { 0x476, 0x801 }, + { 0x476, 0x801 }, + { 0x476, 0x801 }, + { 0x477, 0xcd0 }, + { 0x478, 0x806 }, + { 0x478, 0x806 }, + { 0x479, 0xa30 }, + { 0x47a, 0x806 }, + { 0x47a, 0x806 }, + { 0x47b, 0x810 }, + { 0x47c, 0x80e }, + { 0x47c, 0x80e }, + { 0x47d, 0x510 }, + { 0x47e, 0x81f }, + { 0x47e, 0x81f }, + { 0x2DB, 0x0A00 }, + { 0x2DD, 0x0023 }, + { 0x2DF, 0x0102 }, + { 0x80, 0x0 }, + { 0xC20, 0x0002 }, + { 0x209, 0x002A }, +}; + +static const struct reg_default wm5110_revb_patch[] = { + { 0x80, 0x3 }, + { 0x36e, 0x0210 }, + { 0x370, 0x0210 }, + { 0x372, 0x0210 }, + { 0x374, 0x0210 }, + { 0x376, 0x0210 }, + { 0x378, 0x0210 }, + { 0x36d, 0x0028 }, + { 0x36f, 0x0028 }, + { 0x371, 0x0028 }, + { 0x373, 0x0028 }, + { 0x375, 0x0028 }, + { 0x377, 0x0028 }, + { 0x280, 0x2002 }, + { 0x44, 0x20 }, + { 0x45, 0x40 }, + { 0x46, 0x60 }, + { 0x47, 0x80 }, + { 0x48, 0xa0 }, + { 0x51, 0x13 }, + { 0x52, 0x33 }, + { 0x53, 0x53 }, + { 0x54, 0x73 }, + { 0x55, 0x93 }, + { 0x56, 0xb3 }, + { 0xc30, 0x3e3e }, + { 0xc31, 0x3e }, + { 0xc32, 0x3e3e }, + { 0xc33, 0x3e3e }, + { 0xc34, 0x3e3e }, + { 0xc35, 0x3e3e }, + { 0xc36, 0x3e3e }, + { 0xc37, 0x3e3e }, + { 0xc38, 0x3e3e }, + { 0xc39, 0x3e3e }, + { 0xc3a, 0x3e3e }, + { 0xc3b, 0x3e3e }, + { 0xc3c, 0x3e }, + { 0x201, 0x18a5 }, + { 0x202, 0x4100 }, + { 0x460, 0x0c40 }, + { 0x461, 0x8000 }, + { 0x462, 0x0c41 }, + { 0x463, 0x4820 }, + { 0x464, 0x0c41 }, + { 0x465, 0x4040 }, + { 0x466, 0x0841 }, + { 0x467, 0x3940 }, + { 0x468, 0x0841 }, + { 0x469, 0x2030 }, + { 0x46a, 0x0842 }, + { 0x46b, 0x1990 }, + { 0x46c, 0x08c2 }, + { 0x46d, 0x1450 }, + { 0x46e, 0x08c6 }, + { 0x46f, 0x1020 }, + { 0x470, 0x08c6 }, + { 0x471, 0x0cd0 }, + { 0x472, 0x08c6 }, + { 0x473, 0x0a30 }, + { 0x474, 0x0442 }, + { 0x475, 0x0660 }, + { 0x476, 0x0446 }, + { 0x477, 0x0510 }, + { 0x478, 0x04c6 }, + { 0x479, 0x0400 }, + { 0x47a, 0x04ce }, + { 0x47b, 0x0330 }, + { 0x47c, 0x05df }, + { 0x47d, 0x0001 }, + { 0x47e, 0x07ff }, + { 0x2db, 0x0a00 }, + { 0x2dd, 0x0023 }, + { 0x2df, 0x0102 }, + { 0x2ef, 0x924 }, + { 0x2f0, 0x924 }, + { 0x2f1, 0x924 }, + { 0x2f2, 0x924 }, + { 0x2f3, 0x924 }, + { 0x2f4, 0x924 }, + { 0x2eb, 0x60 }, + { 0x2ec, 0x60 }, + { 0x2ed, 0x60 }, + { 0x4f2, 0x33e }, + { 0x458, 0x0000 }, + { 0x15a, 0x0003 }, + { 0x80, 0x0 }, +}; + +static const struct reg_default wm5110_revd_patch[] = { + { 0x80, 0x3 }, + { 0x80, 0x3 }, + { 0x393, 0x27 }, + { 0x394, 0x27 }, + { 0x395, 0x27 }, + { 0x396, 0x27 }, + { 0x397, 0x27 }, + { 0x398, 0x26 }, + { 0x221, 0x90 }, + { 0x211, 0x8 }, + { 0x36c, 0x1fb }, + { 0x26e, 0x64 }, + { 0x26f, 0xea }, + { 0x270, 0x1f16 }, + { 0x51b, 0x1 }, + { 0x55b, 0x1 }, + { 0x59b, 0x1 }, + { 0x4f0, 0x633 }, + { 0x441, 0xc059 }, + { 0x209, 0x27 }, + { 0x80, 0x0 }, + { 0x80, 0x0 }, +}; + +/* We use a function so we can use ARRAY_SIZE() */ +int wm5110_patch(struct arizona *arizona) +{ + switch (arizona->rev) { + case 0: + return regmap_register_patch(arizona->regmap, + wm5110_reva_patch, + ARRAY_SIZE(wm5110_reva_patch)); + case 1: + return regmap_register_patch(arizona->regmap, + wm5110_revb_patch, + ARRAY_SIZE(wm5110_revb_patch)); + case 3: + return regmap_register_patch(arizona->regmap, + wm5110_revd_patch, + ARRAY_SIZE(wm5110_revd_patch)); + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(wm5110_patch); + +static const struct regmap_irq wm5110_aod_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_MICD_CLAMP_FALL] = { + .mask = ARIZONA_MICD_CLAMP_FALL_EINT1 + }, + [ARIZONA_IRQ_MICD_CLAMP_RISE] = { + .mask = ARIZONA_MICD_CLAMP_RISE_EINT1 + }, + [ARIZONA_IRQ_GP5_FALL] = { .mask = ARIZONA_GP5_FALL_EINT1 }, + [ARIZONA_IRQ_GP5_RISE] = { .mask = ARIZONA_GP5_RISE_EINT1 }, + [ARIZONA_IRQ_JD_FALL] = { .mask = ARIZONA_JD1_FALL_EINT1 }, + [ARIZONA_IRQ_JD_RISE] = { .mask = ARIZONA_JD1_RISE_EINT1 }, +}; + +const struct regmap_irq_chip wm5110_aod = { + .name = "wm5110 AOD", + .status_base = ARIZONA_AOD_IRQ1, + .mask_base = ARIZONA_AOD_IRQ_MASK_IRQ1, + .ack_base = ARIZONA_AOD_IRQ1, + .wake_base = ARIZONA_WAKE_CONTROL, + .wake_invert = 1, + .num_regs = 1, + .irqs = wm5110_aod_irqs, + .num_irqs = ARRAY_SIZE(wm5110_aod_irqs), +}; +EXPORT_SYMBOL_GPL(wm5110_aod); + +static const struct regmap_irq wm5110_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_GP4] = { .reg_offset = 0, .mask = ARIZONA_GP4_EINT1 }, + [ARIZONA_IRQ_GP3] = { .reg_offset = 0, .mask = ARIZONA_GP3_EINT1 }, + [ARIZONA_IRQ_GP2] = { .reg_offset = 0, .mask = ARIZONA_GP2_EINT1 }, + [ARIZONA_IRQ_GP1] = { .reg_offset = 0, .mask = ARIZONA_GP1_EINT1 }, + + [ARIZONA_IRQ_DSP4_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP4_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP3_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP3_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP2_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP2_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP1_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP1_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ8] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ8_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ7] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ7_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ6] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ6_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ5] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ5_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ4] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ4_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ3] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ3_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ2] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ2_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ1] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ1_EINT1 + }, + + [ARIZONA_IRQ_SPK_OVERHEAT_WARN] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_WARN_EINT1 + }, + [ARIZONA_IRQ_SPK_OVERHEAT] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_EINT1 + }, + [ARIZONA_IRQ_HPDET] = { + .reg_offset = 2, .mask = ARIZONA_HPDET_EINT1 + }, + [ARIZONA_IRQ_MICDET] = { + .reg_offset = 2, .mask = ARIZONA_MICDET_EINT1 + }, + [ARIZONA_IRQ_WSEQ_DONE] = { + .reg_offset = 2, .mask = ARIZONA_WSEQ_DONE_EINT1 + }, + [ARIZONA_IRQ_DRC2_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC2_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_DRC1_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC1_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_ASRC2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_ASRC2_LOCK_EINT1 + }, + [ARIZONA_IRQ_ASRC1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_ASRC1_LOCK_EINT1 + }, + [ARIZONA_IRQ_UNDERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_UNDERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_OVERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_OVERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_FLL2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL2_LOCK_EINT1 + }, + [ARIZONA_IRQ_FLL1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL1_LOCK_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR_ASYNC] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_ASYNC_EINT1 + }, + + [ARIZONA_IRQ_ASRC_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ASRC_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF3_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF3_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF2_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF2_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF1_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF1_ERR_EINT1 + }, + [ARIZONA_IRQ_CTRLIF_ERR] = { + .reg_offset = 3, .mask = ARIZONA_CTRLIF_ERR_EINT1 + }, + [ARIZONA_IRQ_MIXER_DROPPED_SAMPLES] = { + .reg_offset = 3, .mask = ARIZONA_MIXER_DROPPED_SAMPLE_EINT1 + }, + [ARIZONA_IRQ_ASYNC_CLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_ASYNC_CLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_SYSCLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_SYSCLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_ISRC1_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ISRC1_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_ISRC2_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ISRC2_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_HP3R_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP3R_DONE_EINT1 + }, + [ARIZONA_IRQ_HP3L_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP3L_DONE_EINT1 + }, + [ARIZONA_IRQ_HP2R_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP2R_DONE_EINT1 + }, + [ARIZONA_IRQ_HP2L_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP2L_DONE_EINT1 + }, + [ARIZONA_IRQ_HP1R_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP1R_DONE_EINT1 + }, + [ARIZONA_IRQ_HP1L_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP1L_DONE_EINT1 + }, + + [ARIZONA_IRQ_BOOT_DONE] = { + .reg_offset = 4, .mask = ARIZONA_BOOT_DONE_EINT1 + }, + [ARIZONA_IRQ_FLL2_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL2_CLOCK_OK_EINT1 + }, + [ARIZONA_IRQ_FLL1_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL1_CLOCK_OK_EINT1 + }, +}; + +const struct regmap_irq_chip wm5110_irq = { + .name = "wm5110 IRQ", + .status_base = ARIZONA_INTERRUPT_STATUS_1, + .mask_base = ARIZONA_INTERRUPT_STATUS_1_MASK, + .ack_base = ARIZONA_INTERRUPT_STATUS_1, + .num_regs = 5, + .irqs = wm5110_irqs, + .num_irqs = ARRAY_SIZE(wm5110_irqs), +}; +EXPORT_SYMBOL_GPL(wm5110_irq); + +static const struct regmap_irq wm5110_revd_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_GP4] = { .reg_offset = 0, .mask = ARIZONA_GP4_EINT1 }, + [ARIZONA_IRQ_GP3] = { .reg_offset = 0, .mask = ARIZONA_GP3_EINT1 }, + [ARIZONA_IRQ_GP2] = { .reg_offset = 0, .mask = ARIZONA_GP2_EINT1 }, + [ARIZONA_IRQ_GP1] = { .reg_offset = 0, .mask = ARIZONA_GP1_EINT1 }, + + [ARIZONA_IRQ_DSP4_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP4_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP3_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP3_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP2_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP2_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP1_RAM_RDY] = { + .reg_offset = 1, .mask = ARIZONA_DSP1_RAM_RDY_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ8] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ8_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ7] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ7_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ6] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ6_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ5] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ5_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ4] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ4_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ3] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ3_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ2] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ2_EINT1 + }, + [ARIZONA_IRQ_DSP_IRQ1] = { + .reg_offset = 1, .mask = ARIZONA_DSP_IRQ1_EINT1 + }, + + [ARIZONA_IRQ_SPK_OVERHEAT_WARN] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_WARN_EINT1 + }, + [ARIZONA_IRQ_SPK_OVERHEAT] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_EINT1 + }, + [ARIZONA_IRQ_HPDET] = { + .reg_offset = 2, .mask = ARIZONA_HPDET_EINT1 + }, + [ARIZONA_IRQ_MICDET] = { + .reg_offset = 2, .mask = ARIZONA_MICDET_EINT1 + }, + [ARIZONA_IRQ_WSEQ_DONE] = { + .reg_offset = 2, .mask = ARIZONA_WSEQ_DONE_EINT1 + }, + [ARIZONA_IRQ_DRC2_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC2_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_DRC1_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC1_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_ASRC2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_ASRC2_LOCK_EINT1 + }, + [ARIZONA_IRQ_ASRC1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_ASRC1_LOCK_EINT1 + }, + [ARIZONA_IRQ_UNDERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_UNDERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_OVERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_OVERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_FLL2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL2_LOCK_EINT1 + }, + [ARIZONA_IRQ_FLL1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL1_LOCK_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR_ASYNC] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_ASYNC_EINT1 + }, + + [ARIZONA_IRQ_CTRLIF_ERR] = { + .reg_offset = 3, .mask = ARIZONA_V2_CTRLIF_ERR_EINT1 + }, + [ARIZONA_IRQ_MIXER_DROPPED_SAMPLES] = { + .reg_offset = 3, .mask = ARIZONA_V2_MIXER_DROPPED_SAMPLE_EINT1 + }, + [ARIZONA_IRQ_ASYNC_CLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_V2_ASYNC_CLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_SYSCLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_V2_SYSCLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_ISRC1_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_V2_ISRC1_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_ISRC2_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_V2_ISRC2_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_ISRC3_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_V2_ISRC3_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_HP3R_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP3R_DONE_EINT1 + }, + [ARIZONA_IRQ_HP3L_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP3L_DONE_EINT1 + }, + [ARIZONA_IRQ_HP2R_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP2R_DONE_EINT1 + }, + [ARIZONA_IRQ_HP2L_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP2L_DONE_EINT1 + }, + [ARIZONA_IRQ_HP1R_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP1R_DONE_EINT1 + }, + [ARIZONA_IRQ_HP1L_DONE] = { + .reg_offset = 3, .mask = ARIZONA_HP1L_DONE_EINT1 + }, + + [ARIZONA_IRQ_BOOT_DONE] = { + .reg_offset = 4, .mask = ARIZONA_BOOT_DONE_EINT1 + }, + [ARIZONA_IRQ_ASRC_CFG_ERR] = { + .reg_offset = 4, .mask = ARIZONA_V2_ASRC_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_FLL2_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL2_CLOCK_OK_EINT1 + }, + [ARIZONA_IRQ_FLL1_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL1_CLOCK_OK_EINT1 + }, + + [ARIZONA_IRQ_DSP_SHARED_WR_COLL] = { + .reg_offset = 5, .mask = ARIZONA_DSP_SHARED_WR_COLL_EINT1 + }, + [ARIZONA_IRQ_SPK_SHUTDOWN] = { + .reg_offset = 5, .mask = ARIZONA_SPK_SHUTDOWN_EINT1 + }, + [ARIZONA_IRQ_SPK1R_SHORT] = { + .reg_offset = 5, .mask = ARIZONA_SPK1R_SHORT_EINT1 + }, + [ARIZONA_IRQ_SPK1L_SHORT] = { + .reg_offset = 5, .mask = ARIZONA_SPK1L_SHORT_EINT1 + }, + [ARIZONA_IRQ_HP3R_SC_NEG] = { + .reg_offset = 5, .mask = ARIZONA_HP3R_SC_NEG_EINT1 + }, + [ARIZONA_IRQ_HP3R_SC_POS] = { + .reg_offset = 5, .mask = ARIZONA_HP3R_SC_POS_EINT1 + }, + [ARIZONA_IRQ_HP3L_SC_NEG] = { + .reg_offset = 5, .mask = ARIZONA_HP3L_SC_NEG_EINT1 + }, + [ARIZONA_IRQ_HP3L_SC_POS] = { + .reg_offset = 5, .mask = ARIZONA_HP3L_SC_POS_EINT1 + }, + [ARIZONA_IRQ_HP2R_SC_NEG] = { + .reg_offset = 5, .mask = ARIZONA_HP2R_SC_NEG_EINT1 + }, + [ARIZONA_IRQ_HP2R_SC_POS] = { + .reg_offset = 5, .mask = ARIZONA_HP2R_SC_POS_EINT1 + }, + [ARIZONA_IRQ_HP2L_SC_NEG] = { + .reg_offset = 5, .mask = ARIZONA_HP2L_SC_NEG_EINT1 + }, + [ARIZONA_IRQ_HP2L_SC_POS] = { + .reg_offset = 5, .mask = ARIZONA_HP2L_SC_POS_EINT1 + }, + [ARIZONA_IRQ_HP1R_SC_NEG] = { + .reg_offset = 5, .mask = ARIZONA_HP1R_SC_NEG_EINT1 + }, + [ARIZONA_IRQ_HP1R_SC_POS] = { + .reg_offset = 5, .mask = ARIZONA_HP1R_SC_POS_EINT1 + }, + [ARIZONA_IRQ_HP1L_SC_NEG] = { + .reg_offset = 5, .mask = ARIZONA_HP1L_SC_NEG_EINT1 + }, + [ARIZONA_IRQ_HP1L_SC_POS] = { + .reg_offset = 5, .mask = ARIZONA_HP1L_SC_POS_EINT1 + }, +}; + +const struct regmap_irq_chip wm5110_revd_irq = { + .name = "wm5110 IRQ", + .status_base = ARIZONA_INTERRUPT_STATUS_1, + .mask_base = ARIZONA_INTERRUPT_STATUS_1_MASK, + .ack_base = ARIZONA_INTERRUPT_STATUS_1, + .num_regs = 6, + .irqs = wm5110_revd_irqs, + .num_irqs = ARRAY_SIZE(wm5110_revd_irqs), +}; +EXPORT_SYMBOL_GPL(wm5110_revd_irq); + +static const struct reg_default wm5110_reg_default[] = { + { 0x00000008, 0x0019 }, /* R8 - Ctrl IF SPI CFG 1 */ + { 0x00000009, 0x0001 }, /* R9 - Ctrl IF I2C1 CFG 1 */ + { 0x0000000A, 0x0001 }, /* R10 - Ctrl IF I2C2 CFG 1 */ + { 0x0000000B, 0x0036 }, /* R11 - Ctrl IF I2C1 CFG 2 */ + { 0x0000000C, 0x0036 }, /* R12 - Ctrl IF I2C2 CFG 2 */ + { 0x00000020, 0x0000 }, /* R32 - Tone Generator 1 */ + { 0x00000021, 0x1000 }, /* R33 - Tone Generator 2 */ + { 0x00000022, 0x0000 }, /* R34 - Tone Generator 3 */ + { 0x00000023, 0x1000 }, /* R35 - Tone Generator 4 */ + { 0x00000024, 0x0000 }, /* R36 - Tone Generator 5 */ + { 0x00000030, 0x0000 }, /* R48 - PWM Drive 1 */ + { 0x00000031, 0x0100 }, /* R49 - PWM Drive 2 */ + { 0x00000032, 0x0100 }, /* R50 - PWM Drive 3 */ + { 0x00000040, 0x0000 }, /* R64 - Wake control */ + { 0x00000041, 0x0000 }, /* R65 - Sequence control */ + { 0x00000061, 0x01FF }, /* R97 - Sample Rate Sequence Select 1 */ + { 0x00000062, 0x01FF }, /* R98 - Sample Rate Sequence Select 2 */ + { 0x00000063, 0x01FF }, /* R99 - Sample Rate Sequence Select 3 */ + { 0x00000064, 0x01FF }, /* R100 - Sample Rate Sequence Select 4 */ + { 0x00000066, 0x01FF }, /* R102 - Always On Triggers Sequence Select 1 */ + { 0x00000067, 0x01FF }, /* R103 - Always On Triggers Sequence Select 2 */ + { 0x00000068, 0x01FF }, /* R104 - Always On Triggers Sequence Select 3 */ + { 0x00000069, 0x01FF }, /* R105 - Always On Triggers Sequence Select 4 */ + { 0x0000006A, 0x01FF }, /* R106 - Always On Triggers Sequence Select 5 */ + { 0x0000006B, 0x01FF }, /* R107 - Always On Triggers Sequence Select 6 */ + { 0x00000070, 0x0000 }, /* R112 - Comfort Noise Generator */ + { 0x00000090, 0x0000 }, /* R144 - Haptics Control 1 */ + { 0x00000091, 0x7FFF }, /* R145 - Haptics Control 2 */ + { 0x00000092, 0x0000 }, /* R146 - Haptics phase 1 intensity */ + { 0x00000093, 0x0000 }, /* R147 - Haptics phase 1 duration */ + { 0x00000094, 0x0000 }, /* R148 - Haptics phase 2 intensity */ + { 0x00000095, 0x0000 }, /* R149 - Haptics phase 2 duration */ + { 0x00000096, 0x0000 }, /* R150 - Haptics phase 3 intensity */ + { 0x00000097, 0x0000 }, /* R151 - Haptics phase 3 duration */ + { 0x00000100, 0x0001 }, /* R256 - Clock 32k 1 */ + { 0x00000101, 0x0504 }, /* R257 - System Clock 1 */ + { 0x00000102, 0x0011 }, /* R258 - Sample rate 1 */ + { 0x00000103, 0x0011 }, /* R259 - Sample rate 2 */ + { 0x00000104, 0x0011 }, /* R260 - Sample rate 3 */ + { 0x00000112, 0x0305 }, /* R274 - Async clock 1 */ + { 0x00000113, 0x0011 }, /* R275 - Async sample rate 1 */ + { 0x00000114, 0x0011 }, /* R276 - Async sample rate 2 */ + { 0x00000149, 0x0000 }, /* R329 - Output system clock */ + { 0x0000014A, 0x0000 }, /* R330 - Output async clock */ + { 0x00000152, 0x0000 }, /* R338 - Rate Estimator 1 */ + { 0x00000153, 0x0000 }, /* R339 - Rate Estimator 2 */ + { 0x00000154, 0x0000 }, /* R340 - Rate Estimator 3 */ + { 0x00000155, 0x0000 }, /* R341 - Rate Estimator 4 */ + { 0x00000156, 0x0000 }, /* R342 - Rate Estimator 5 */ + { 0x00000171, 0x0000 }, /* R369 - FLL1 Control 1 */ + { 0x00000172, 0x0008 }, /* R370 - FLL1 Control 2 */ + { 0x00000173, 0x0018 }, /* R371 - FLL1 Control 3 */ + { 0x00000174, 0x007D }, /* R372 - FLL1 Control 4 */ + { 0x00000175, 0x0006 }, /* R373 - FLL1 Control 5 */ + { 0x00000176, 0x0000 }, /* R374 - FLL1 Control 6 */ + { 0x00000177, 0x0281 }, /* R375 - FLL1 Loop Filter Test 1 */ + { 0x00000178, 0x0000 }, /* R376 - FLL1 NCO Test 0 */ + { 0x00000179, 0x0000 }, /* R376 - FLL1 Control 7 */ + { 0x00000181, 0x0000 }, /* R385 - FLL1 Synchroniser 1 */ + { 0x00000182, 0x0000 }, /* R386 - FLL1 Synchroniser 2 */ + { 0x00000183, 0x0000 }, /* R387 - FLL1 Synchroniser 3 */ + { 0x00000184, 0x0000 }, /* R388 - FLL1 Synchroniser 4 */ + { 0x00000185, 0x0000 }, /* R389 - FLL1 Synchroniser 5 */ + { 0x00000186, 0x0000 }, /* R390 - FLL1 Synchroniser 6 */ + { 0x00000187, 0x0001 }, /* R390 - FLL1 Synchroniser 7 */ + { 0x00000189, 0x0000 }, /* R393 - FLL1 Spread Spectrum */ + { 0x0000018A, 0x0004 }, /* R394 - FLL1 GPIO Clock */ + { 0x00000191, 0x0000 }, /* R401 - FLL2 Control 1 */ + { 0x00000192, 0x0008 }, /* R402 - FLL2 Control 2 */ + { 0x00000193, 0x0018 }, /* R403 - FLL2 Control 3 */ + { 0x00000194, 0x007D }, /* R404 - FLL2 Control 4 */ + { 0x00000195, 0x000C }, /* R405 - FLL2 Control 5 */ + { 0x00000196, 0x0000 }, /* R406 - FLL2 Control 6 */ + { 0x00000197, 0x0000 }, /* R407 - FLL2 Loop Filter Test 1 */ + { 0x00000198, 0x0000 }, /* R408 - FLL2 NCO Test 0 */ + { 0x00000199, 0x0000 }, /* R408 - FLL2 Control 7 */ + { 0x000001A1, 0x0000 }, /* R417 - FLL2 Synchroniser 1 */ + { 0x000001A2, 0x0000 }, /* R418 - FLL2 Synchroniser 2 */ + { 0x000001A3, 0x0000 }, /* R419 - FLL2 Synchroniser 3 */ + { 0x000001A4, 0x0000 }, /* R420 - FLL2 Synchroniser 4 */ + { 0x000001A5, 0x0000 }, /* R421 - FLL2 Synchroniser 5 */ + { 0x000001A6, 0x0000 }, /* R422 - FLL2 Synchroniser 6 */ + { 0x000001A7, 0x0001 }, /* R422 - FLL2 Synchroniser 7 */ + { 0x000001A9, 0x0000 }, /* R425 - FLL2 Spread Spectrum */ + { 0x000001AA, 0x0004 }, /* R426 - FLL2 GPIO Clock */ + { 0x00000200, 0x0006 }, /* R512 - Mic Charge Pump 1 */ + { 0x00000210, 0x0184 }, /* R528 - LDO1 Control 1 */ + { 0x00000213, 0x03E4 }, /* R531 - LDO2 Control 1 */ + { 0x00000218, 0x01A6 }, /* R536 - Mic Bias Ctrl 1 */ + { 0x00000219, 0x01A6 }, /* R537 - Mic Bias Ctrl 2 */ + { 0x0000021A, 0x01A6 }, /* R538 - Mic Bias Ctrl 3 */ + { 0x00000293, 0x0000 }, /* R659 - Accessory Detect Mode 1 */ + { 0x0000029B, 0x0028 }, /* R667 - Headphone Detect 1 */ + { 0x0000029C, 0x0000 }, /* R668 - Headphone Detect 2 */ + { 0x000002A2, 0x0000 }, /* R674 - Micd clamp control */ + { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ + { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ + { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ + { 0x000002A6, 0x3737 }, /* R678 - Mic Detect Level 1 */ + { 0x000002A7, 0x372C }, /* R679 - Mic Detect Level 2 */ + { 0x000002A8, 0x1422 }, /* R680 - Mic Detect Level 3 */ + { 0x000002A9, 0x300A }, /* R681 - Mic Detect Level 4 */ + { 0x000002C3, 0x0000 }, /* R707 - Mic noise mix control 1 */ + { 0x000002CB, 0x0000 }, /* R715 - Isolation control */ + { 0x000002D3, 0x0000 }, /* R723 - Jack detect analogue */ + { 0x00000300, 0x0000 }, /* R768 - Input Enables */ + { 0x00000308, 0x0000 }, /* R776 - Input Rate */ + { 0x00000309, 0x0022 }, /* R777 - Input Volume Ramp */ + { 0x0000030C, 0x0002 }, /* R780 - HPF Control */ + { 0x00000310, 0x2080 }, /* R784 - IN1L Control */ + { 0x00000311, 0x0180 }, /* R785 - ADC Digital Volume 1L */ + { 0x00000312, 0x0000 }, /* R786 - DMIC1L Control */ + { 0x00000314, 0x0080 }, /* R788 - IN1R Control */ + { 0x00000315, 0x0180 }, /* R789 - ADC Digital Volume 1R */ + { 0x00000316, 0x0000 }, /* R790 - DMIC1R Control */ + { 0x00000318, 0x2080 }, /* R792 - IN2L Control */ + { 0x00000319, 0x0180 }, /* R793 - ADC Digital Volume 2L */ + { 0x0000031A, 0x0000 }, /* R794 - DMIC2L Control */ + { 0x0000031C, 0x0080 }, /* R796 - IN2R Control */ + { 0x0000031D, 0x0180 }, /* R797 - ADC Digital Volume 2R */ + { 0x0000031E, 0x0000 }, /* R798 - DMIC2R Control */ + { 0x00000320, 0x2080 }, /* R800 - IN3L Control */ + { 0x00000321, 0x0180 }, /* R801 - ADC Digital Volume 3L */ + { 0x00000322, 0x0000 }, /* R802 - DMIC3L Control */ + { 0x00000324, 0x0080 }, /* R804 - IN3R Control */ + { 0x00000325, 0x0180 }, /* R805 - ADC Digital Volume 3R */ + { 0x00000326, 0x0000 }, /* R806 - DMIC3R Control */ + { 0x00000328, 0x2000 }, /* R808 - IN4L Control */ + { 0x00000329, 0x0180 }, /* R809 - ADC Digital Volume 4L */ + { 0x0000032A, 0x0000 }, /* R810 - DMIC4L Control */ + { 0x0000032C, 0x0000 }, /* R812 - IN4R Control */ + { 0x0000032D, 0x0180 }, /* R813 - ADC Digital Volume 4R */ + { 0x0000032E, 0x0000 }, /* R814 - DMIC4R Control */ + { 0x00000400, 0x0000 }, /* R1024 - Output Enables 1 */ + { 0x00000408, 0x0000 }, /* R1032 - Output Rate 1 */ + { 0x00000409, 0x0022 }, /* R1033 - Output Volume Ramp */ + { 0x00000410, 0x0080 }, /* R1040 - Output Path Config 1L */ + { 0x00000411, 0x0180 }, /* R1041 - DAC Digital Volume 1L */ + { 0x00000412, 0x0080 }, /* R1042 - DAC Volume Limit 1L */ + { 0x00000413, 0x0001 }, /* R1043 - Noise Gate Select 1L */ + { 0x00000414, 0x0080 }, /* R1044 - Output Path Config 1R */ + { 0x00000415, 0x0180 }, /* R1045 - DAC Digital Volume 1R */ + { 0x00000416, 0x0080 }, /* R1046 - DAC Volume Limit 1R */ + { 0x00000417, 0x0002 }, /* R1047 - Noise Gate Select 1R */ + { 0x00000418, 0x0080 }, /* R1048 - Output Path Config 2L */ + { 0x00000419, 0x0180 }, /* R1049 - DAC Digital Volume 2L */ + { 0x0000041A, 0x0080 }, /* R1050 - DAC Volume Limit 2L */ + { 0x0000041B, 0x0004 }, /* R1051 - Noise Gate Select 2L */ + { 0x0000041C, 0x0080 }, /* R1052 - Output Path Config 2R */ + { 0x0000041D, 0x0180 }, /* R1053 - DAC Digital Volume 2R */ + { 0x0000041E, 0x0080 }, /* R1054 - DAC Volume Limit 2R */ + { 0x0000041F, 0x0008 }, /* R1055 - Noise Gate Select 2R */ + { 0x00000420, 0x0080 }, /* R1056 - Output Path Config 3L */ + { 0x00000421, 0x0180 }, /* R1057 - DAC Digital Volume 3L */ + { 0x00000422, 0x0080 }, /* R1058 - DAC Volume Limit 3L */ + { 0x00000423, 0x0010 }, /* R1059 - Noise Gate Select 3L */ + { 0x00000424, 0x0080 }, /* R1060 - Output Path Config 3R */ + { 0x00000425, 0x0180 }, /* R1061 - DAC Digital Volume 3R */ + { 0x00000426, 0x0080 }, /* R1062 - DAC Volume Limit 3R */ + { 0x00000427, 0x0020 }, /* R1063 - Noise Gate Select 3R */ + { 0x00000428, 0x0000 }, /* R1064 - Output Path Config 4L */ + { 0x00000429, 0x0180 }, /* R1065 - DAC Digital Volume 4L */ + { 0x0000042A, 0x0080 }, /* R1066 - Out Volume 4L */ + { 0x0000042B, 0x0040 }, /* R1067 - Noise Gate Select 4L */ + { 0x0000042C, 0x0000 }, /* R1068 - Output Path Config 4R */ + { 0x0000042D, 0x0180 }, /* R1069 - DAC Digital Volume 4R */ + { 0x0000042E, 0x0080 }, /* R1070 - Out Volume 4R */ + { 0x0000042F, 0x0080 }, /* R1071 - Noise Gate Select 4R */ + { 0x00000430, 0x0000 }, /* R1072 - Output Path Config 5L */ + { 0x00000431, 0x0180 }, /* R1073 - DAC Digital Volume 5L */ + { 0x00000432, 0x0080 }, /* R1074 - DAC Volume Limit 5L */ + { 0x00000433, 0x0100 }, /* R1075 - Noise Gate Select 5L */ + { 0x00000434, 0x0000 }, /* R1076 - Output Path Config 5R */ + { 0x00000435, 0x0180 }, /* R1077 - DAC Digital Volume 5R */ + { 0x00000436, 0x0080 }, /* R1078 - DAC Volume Limit 5R */ + { 0x00000437, 0x0200 }, /* R1079 - Noise Gate Select 5R */ + { 0x00000438, 0x0000 }, /* R1080 - Output Path Config 6L */ + { 0x00000439, 0x0180 }, /* R1081 - DAC Digital Volume 6L */ + { 0x0000043A, 0x0080 }, /* R1082 - DAC Volume Limit 6L */ + { 0x0000043B, 0x0400 }, /* R1083 - Noise Gate Select 6L */ + { 0x0000043C, 0x0000 }, /* R1084 - Output Path Config 6R */ + { 0x0000043D, 0x0180 }, /* R1085 - DAC Digital Volume 6R */ + { 0x0000043E, 0x0080 }, /* R1086 - DAC Volume Limit 6R */ + { 0x0000043F, 0x0800 }, /* R1087 - Noise Gate Select 6R */ + { 0x00000440, 0x8FFF }, /* R1088 - DRE Enable */ + { 0x00000450, 0x0000 }, /* R1104 - DAC AEC Control 1 */ + { 0x00000458, 0x0000 }, /* R1112 - Noise Gate Control */ + { 0x00000480, 0x0040 }, /* R1152 - Class W ANC Threshold 1 */ + { 0x00000481, 0x0040 }, /* R1153 - Class W ANC Threshold 2 */ + { 0x00000490, 0x0069 }, /* R1168 - PDM SPK1 CTRL 1 */ + { 0x00000491, 0x0000 }, /* R1169 - PDM SPK1 CTRL 2 */ + { 0x00000492, 0x0069 }, /* R1170 - PDM SPK2 CTRL 1 */ + { 0x00000493, 0x0000 }, /* R1171 - PDM SPK2 CTRL 2 */ + { 0x000004A0, 0x3480 }, /* R1184 - HP1 Short Circuit Ctrl */ + { 0x000004A1, 0x3480 }, /* R1185 - HP2 Short Circuit Ctrl */ + { 0x000004A2, 0x3480 }, /* R1186 - HP3 Short Circuit Ctrl */ + { 0x00000500, 0x000C }, /* R1280 - AIF1 BCLK Ctrl */ + { 0x00000501, 0x0008 }, /* R1281 - AIF1 Tx Pin Ctrl */ + { 0x00000502, 0x0000 }, /* R1282 - AIF1 Rx Pin Ctrl */ + { 0x00000503, 0x0000 }, /* R1283 - AIF1 Rate Ctrl */ + { 0x00000504, 0x0000 }, /* R1284 - AIF1 Format */ + { 0x00000505, 0x0040 }, /* R1285 - AIF1 Tx BCLK Rate */ + { 0x00000506, 0x0040 }, /* R1286 - AIF1 Rx BCLK Rate */ + { 0x00000507, 0x1818 }, /* R1287 - AIF1 Frame Ctrl 1 */ + { 0x00000508, 0x1818 }, /* R1288 - AIF1 Frame Ctrl 2 */ + { 0x00000509, 0x0000 }, /* R1289 - AIF1 Frame Ctrl 3 */ + { 0x0000050A, 0x0001 }, /* R1290 - AIF1 Frame Ctrl 4 */ + { 0x0000050B, 0x0002 }, /* R1291 - AIF1 Frame Ctrl 5 */ + { 0x0000050C, 0x0003 }, /* R1292 - AIF1 Frame Ctrl 6 */ + { 0x0000050D, 0x0004 }, /* R1293 - AIF1 Frame Ctrl 7 */ + { 0x0000050E, 0x0005 }, /* R1294 - AIF1 Frame Ctrl 8 */ + { 0x0000050F, 0x0006 }, /* R1295 - AIF1 Frame Ctrl 9 */ + { 0x00000510, 0x0007 }, /* R1296 - AIF1 Frame Ctrl 10 */ + { 0x00000511, 0x0000 }, /* R1297 - AIF1 Frame Ctrl 11 */ + { 0x00000512, 0x0001 }, /* R1298 - AIF1 Frame Ctrl 12 */ + { 0x00000513, 0x0002 }, /* R1299 - AIF1 Frame Ctrl 13 */ + { 0x00000514, 0x0003 }, /* R1300 - AIF1 Frame Ctrl 14 */ + { 0x00000515, 0x0004 }, /* R1301 - AIF1 Frame Ctrl 15 */ + { 0x00000516, 0x0005 }, /* R1302 - AIF1 Frame Ctrl 16 */ + { 0x00000517, 0x0006 }, /* R1303 - AIF1 Frame Ctrl 17 */ + { 0x00000518, 0x0007 }, /* R1304 - AIF1 Frame Ctrl 18 */ + { 0x00000519, 0x0000 }, /* R1305 - AIF1 Tx Enables */ + { 0x0000051A, 0x0000 }, /* R1306 - AIF1 Rx Enables */ + { 0x00000540, 0x000C }, /* R1344 - AIF2 BCLK Ctrl */ + { 0x00000541, 0x0008 }, /* R1345 - AIF2 Tx Pin Ctrl */ + { 0x00000542, 0x0000 }, /* R1346 - AIF2 Rx Pin Ctrl */ + { 0x00000543, 0x0000 }, /* R1347 - AIF2 Rate Ctrl */ + { 0x00000544, 0x0000 }, /* R1348 - AIF2 Format */ + { 0x00000545, 0x0040 }, /* R1349 - AIF2 Tx BCLK Rate */ + { 0x00000546, 0x0040 }, /* R1350 - AIF2 Rx BCLK Rate */ + { 0x00000547, 0x1818 }, /* R1351 - AIF2 Frame Ctrl 1 */ + { 0x00000548, 0x1818 }, /* R1352 - AIF2 Frame Ctrl 2 */ + { 0x00000549, 0x0000 }, /* R1353 - AIF2 Frame Ctrl 3 */ + { 0x0000054A, 0x0001 }, /* R1354 - AIF2 Frame Ctrl 4 */ + { 0x0000054B, 0x0002 }, /* R1355 - AIF2 Frame Ctrl 5 */ + { 0x0000054C, 0x0003 }, /* R1356 - AIF2 Frame Ctrl 6 */ + { 0x0000054D, 0x0004 }, /* R1357 - AIF2 Frame Ctrl 7 */ + { 0x0000054E, 0x0005 }, /* R1358 - AIF2 Frame Ctrl 8 */ + { 0x00000551, 0x0000 }, /* R1361 - AIF2 Frame Ctrl 11 */ + { 0x00000552, 0x0001 }, /* R1362 - AIF2 Frame Ctrl 12 */ + { 0x00000553, 0x0002 }, /* R1363 - AIF2 Frame Ctrl 13 */ + { 0x00000554, 0x0003 }, /* R1364 - AIF2 Frame Ctrl 14 */ + { 0x00000555, 0x0004 }, /* R1365 - AIF2 Frame Ctrl 15 */ + { 0x00000556, 0x0005 }, /* R1366 - AIF2 Frame Ctrl 16 */ + { 0x00000559, 0x0000 }, /* R1369 - AIF2 Tx Enables */ + { 0x0000055A, 0x0000 }, /* R1370 - AIF2 Rx Enables */ + { 0x00000580, 0x000C }, /* R1408 - AIF3 BCLK Ctrl */ + { 0x00000581, 0x0008 }, /* R1409 - AIF3 Tx Pin Ctrl */ + { 0x00000582, 0x0000 }, /* R1410 - AIF3 Rx Pin Ctrl */ + { 0x00000583, 0x0000 }, /* R1411 - AIF3 Rate Ctrl */ + { 0x00000584, 0x0000 }, /* R1412 - AIF3 Format */ + { 0x00000585, 0x0040 }, /* R1413 - AIF3 Tx BCLK Rate */ + { 0x00000586, 0x0040 }, /* R1414 - AIF3 Rx BCLK Rate */ + { 0x00000587, 0x1818 }, /* R1415 - AIF3 Frame Ctrl 1 */ + { 0x00000588, 0x1818 }, /* R1416 - AIF3 Frame Ctrl 2 */ + { 0x00000589, 0x0000 }, /* R1417 - AIF3 Frame Ctrl 3 */ + { 0x0000058A, 0x0001 }, /* R1418 - AIF3 Frame Ctrl 4 */ + { 0x00000591, 0x0000 }, /* R1425 - AIF3 Frame Ctrl 11 */ + { 0x00000592, 0x0001 }, /* R1426 - AIF3 Frame Ctrl 12 */ + { 0x00000599, 0x0000 }, /* R1433 - AIF3 Tx Enables */ + { 0x0000059A, 0x0000 }, /* R1434 - AIF3 Rx Enables */ + { 0x000005E3, 0x0004 }, /* R1507 - SLIMbus Framer Ref Gear */ + { 0x000005E5, 0x0000 }, /* R1509 - SLIMbus Rates 1 */ + { 0x000005E6, 0x0000 }, /* R1510 - SLIMbus Rates 2 */ + { 0x000005E7, 0x0000 }, /* R1511 - SLIMbus Rates 3 */ + { 0x000005E8, 0x0000 }, /* R1512 - SLIMbus Rates 4 */ + { 0x000005E9, 0x0000 }, /* R1513 - SLIMbus Rates 5 */ + { 0x000005EA, 0x0000 }, /* R1514 - SLIMbus Rates 6 */ + { 0x000005EB, 0x0000 }, /* R1515 - SLIMbus Rates 7 */ + { 0x000005EC, 0x0000 }, /* R1516 - SLIMbus Rates 8 */ + { 0x000005F5, 0x0000 }, /* R1525 - SLIMbus RX Channel Enable */ + { 0x000005F6, 0x0000 }, /* R1526 - SLIMbus TX Channel Enable */ + { 0x00000640, 0x0000 }, /* R1600 - PWM1MIX Input 1 Source */ + { 0x00000641, 0x0080 }, /* R1601 - PWM1MIX Input 1 Volume */ + { 0x00000642, 0x0000 }, /* R1602 - PWM1MIX Input 2 Source */ + { 0x00000643, 0x0080 }, /* R1603 - PWM1MIX Input 2 Volume */ + { 0x00000644, 0x0000 }, /* R1604 - PWM1MIX Input 3 Source */ + { 0x00000645, 0x0080 }, /* R1605 - PWM1MIX Input 3 Volume */ + { 0x00000646, 0x0000 }, /* R1606 - PWM1MIX Input 4 Source */ + { 0x00000647, 0x0080 }, /* R1607 - PWM1MIX Input 4 Volume */ + { 0x00000648, 0x0000 }, /* R1608 - PWM2MIX Input 1 Source */ + { 0x00000649, 0x0080 }, /* R1609 - PWM2MIX Input 1 Volume */ + { 0x0000064A, 0x0000 }, /* R1610 - PWM2MIX Input 2 Source */ + { 0x0000064B, 0x0080 }, /* R1611 - PWM2MIX Input 2 Volume */ + { 0x0000064C, 0x0000 }, /* R1612 - PWM2MIX Input 3 Source */ + { 0x0000064D, 0x0080 }, /* R1613 - PWM2MIX Input 3 Volume */ + { 0x0000064E, 0x0000 }, /* R1614 - PWM2MIX Input 4 Source */ + { 0x0000064F, 0x0080 }, /* R1615 - PWM2MIX Input 4 Volume */ + { 0x00000660, 0x0000 }, /* R1632 - MICMIX Input 1 Source */ + { 0x00000661, 0x0080 }, /* R1633 - MICMIX Input 1 Volume */ + { 0x00000662, 0x0000 }, /* R1634 - MICMIX Input 2 Source */ + { 0x00000663, 0x0080 }, /* R1635 - MICMIX Input 2 Volume */ + { 0x00000664, 0x0000 }, /* R1636 - MICMIX Input 3 Source */ + { 0x00000665, 0x0080 }, /* R1637 - MICMIX Input 3 Volume */ + { 0x00000666, 0x0000 }, /* R1638 - MICMIX Input 4 Source */ + { 0x00000667, 0x0080 }, /* R1639 - MICMIX Input 4 Volume */ + { 0x00000668, 0x0000 }, /* R1640 - NOISEMIX Input 1 Source */ + { 0x00000669, 0x0080 }, /* R1641 - NOISEMIX Input 1 Volume */ + { 0x0000066A, 0x0000 }, /* R1642 - NOISEMIX Input 2 Source */ + { 0x0000066B, 0x0080 }, /* R1643 - NOISEMIX Input 2 Volume */ + { 0x0000066C, 0x0000 }, /* R1644 - NOISEMIX Input 3 Source */ + { 0x0000066D, 0x0080 }, /* R1645 - NOISEMIX Input 3 Volume */ + { 0x0000066E, 0x0000 }, /* R1646 - NOISEMIX Input 4 Source */ + { 0x0000066F, 0x0080 }, /* R1647 - NOISEMIX Input 4 Volume */ + { 0x00000680, 0x0000 }, /* R1664 - OUT1LMIX Input 1 Source */ + { 0x00000681, 0x0080 }, /* R1665 - OUT1LMIX Input 1 Volume */ + { 0x00000682, 0x0000 }, /* R1666 - OUT1LMIX Input 2 Source */ + { 0x00000683, 0x0080 }, /* R1667 - OUT1LMIX Input 2 Volume */ + { 0x00000684, 0x0000 }, /* R1668 - OUT1LMIX Input 3 Source */ + { 0x00000685, 0x0080 }, /* R1669 - OUT1LMIX Input 3 Volume */ + { 0x00000686, 0x0000 }, /* R1670 - OUT1LMIX Input 4 Source */ + { 0x00000687, 0x0080 }, /* R1671 - OUT1LMIX Input 4 Volume */ + { 0x00000688, 0x0000 }, /* R1672 - OUT1RMIX Input 1 Source */ + { 0x00000689, 0x0080 }, /* R1673 - OUT1RMIX Input 1 Volume */ + { 0x0000068A, 0x0000 }, /* R1674 - OUT1RMIX Input 2 Source */ + { 0x0000068B, 0x0080 }, /* R1675 - OUT1RMIX Input 2 Volume */ + { 0x0000068C, 0x0000 }, /* R1676 - OUT1RMIX Input 3 Source */ + { 0x0000068D, 0x0080 }, /* R1677 - OUT1RMIX Input 3 Volume */ + { 0x0000068E, 0x0000 }, /* R1678 - OUT1RMIX Input 4 Source */ + { 0x0000068F, 0x0080 }, /* R1679 - OUT1RMIX Input 4 Volume */ + { 0x00000690, 0x0000 }, /* R1680 - OUT2LMIX Input 1 Source */ + { 0x00000691, 0x0080 }, /* R1681 - OUT2LMIX Input 1 Volume */ + { 0x00000692, 0x0000 }, /* R1682 - OUT2LMIX Input 2 Source */ + { 0x00000693, 0x0080 }, /* R1683 - OUT2LMIX Input 2 Volume */ + { 0x00000694, 0x0000 }, /* R1684 - OUT2LMIX Input 3 Source */ + { 0x00000695, 0x0080 }, /* R1685 - OUT2LMIX Input 3 Volume */ + { 0x00000696, 0x0000 }, /* R1686 - OUT2LMIX Input 4 Source */ + { 0x00000697, 0x0080 }, /* R1687 - OUT2LMIX Input 4 Volume */ + { 0x00000698, 0x0000 }, /* R1688 - OUT2RMIX Input 1 Source */ + { 0x00000699, 0x0080 }, /* R1689 - OUT2RMIX Input 1 Volume */ + { 0x0000069A, 0x0000 }, /* R1690 - OUT2RMIX Input 2 Source */ + { 0x0000069B, 0x0080 }, /* R1691 - OUT2RMIX Input 2 Volume */ + { 0x0000069C, 0x0000 }, /* R1692 - OUT2RMIX Input 3 Source */ + { 0x0000069D, 0x0080 }, /* R1693 - OUT2RMIX Input 3 Volume */ + { 0x0000069E, 0x0000 }, /* R1694 - OUT2RMIX Input 4 Source */ + { 0x0000069F, 0x0080 }, /* R1695 - OUT2RMIX Input 4 Volume */ + { 0x000006A0, 0x0000 }, /* R1696 - OUT3LMIX Input 1 Source */ + { 0x000006A1, 0x0080 }, /* R1697 - OUT3LMIX Input 1 Volume */ + { 0x000006A2, 0x0000 }, /* R1698 - OUT3LMIX Input 2 Source */ + { 0x000006A3, 0x0080 }, /* R1699 - OUT3LMIX Input 2 Volume */ + { 0x000006A4, 0x0000 }, /* R1700 - OUT3LMIX Input 3 Source */ + { 0x000006A5, 0x0080 }, /* R1701 - OUT3LMIX Input 3 Volume */ + { 0x000006A6, 0x0000 }, /* R1702 - OUT3LMIX Input 4 Source */ + { 0x000006A7, 0x0080 }, /* R1703 - OUT3LMIX Input 4 Volume */ + { 0x000006A8, 0x0000 }, /* R1704 - OUT3RMIX Input 1 Source */ + { 0x000006A9, 0x0080 }, /* R1705 - OUT3RMIX Input 1 Volume */ + { 0x000006AA, 0x0000 }, /* R1706 - OUT3RMIX Input 2 Source */ + { 0x000006AB, 0x0080 }, /* R1707 - OUT3RMIX Input 2 Volume */ + { 0x000006AC, 0x0000 }, /* R1708 - OUT3RMIX Input 3 Source */ + { 0x000006AD, 0x0080 }, /* R1709 - OUT3RMIX Input 3 Volume */ + { 0x000006AE, 0x0000 }, /* R1710 - OUT3RMIX Input 4 Source */ + { 0x000006AF, 0x0080 }, /* R1711 - OUT3RMIX Input 4 Volume */ + { 0x000006B0, 0x0000 }, /* R1712 - OUT4LMIX Input 1 Source */ + { 0x000006B1, 0x0080 }, /* R1713 - OUT4LMIX Input 1 Volume */ + { 0x000006B2, 0x0000 }, /* R1714 - OUT4LMIX Input 2 Source */ + { 0x000006B3, 0x0080 }, /* R1715 - OUT4LMIX Input 2 Volume */ + { 0x000006B4, 0x0000 }, /* R1716 - OUT4LMIX Input 3 Source */ + { 0x000006B5, 0x0080 }, /* R1717 - OUT4LMIX Input 3 Volume */ + { 0x000006B6, 0x0000 }, /* R1718 - OUT4LMIX Input 4 Source */ + { 0x000006B7, 0x0080 }, /* R1719 - OUT4LMIX Input 4 Volume */ + { 0x000006B8, 0x0000 }, /* R1720 - OUT4RMIX Input 1 Source */ + { 0x000006B9, 0x0080 }, /* R1721 - OUT4RMIX Input 1 Volume */ + { 0x000006BA, 0x0000 }, /* R1722 - OUT4RMIX Input 2 Source */ + { 0x000006BB, 0x0080 }, /* R1723 - OUT4RMIX Input 2 Volume */ + { 0x000006BC, 0x0000 }, /* R1724 - OUT4RMIX Input 3 Source */ + { 0x000006BD, 0x0080 }, /* R1725 - OUT4RMIX Input 3 Volume */ + { 0x000006BE, 0x0000 }, /* R1726 - OUT4RMIX Input 4 Source */ + { 0x000006BF, 0x0080 }, /* R1727 - OUT4RMIX Input 4 Volume */ + { 0x000006C0, 0x0000 }, /* R1728 - OUT5LMIX Input 1 Source */ + { 0x000006C1, 0x0080 }, /* R1729 - OUT5LMIX Input 1 Volume */ + { 0x000006C2, 0x0000 }, /* R1730 - OUT5LMIX Input 2 Source */ + { 0x000006C3, 0x0080 }, /* R1731 - OUT5LMIX Input 2 Volume */ + { 0x000006C4, 0x0000 }, /* R1732 - OUT5LMIX Input 3 Source */ + { 0x000006C5, 0x0080 }, /* R1733 - OUT5LMIX Input 3 Volume */ + { 0x000006C6, 0x0000 }, /* R1734 - OUT5LMIX Input 4 Source */ + { 0x000006C7, 0x0080 }, /* R1735 - OUT5LMIX Input 4 Volume */ + { 0x000006C8, 0x0000 }, /* R1736 - OUT5RMIX Input 1 Source */ + { 0x000006C9, 0x0080 }, /* R1737 - OUT5RMIX Input 1 Volume */ + { 0x000006CA, 0x0000 }, /* R1738 - OUT5RMIX Input 2 Source */ + { 0x000006CB, 0x0080 }, /* R1739 - OUT5RMIX Input 2 Volume */ + { 0x000006CC, 0x0000 }, /* R1740 - OUT5RMIX Input 3 Source */ + { 0x000006CD, 0x0080 }, /* R1741 - OUT5RMIX Input 3 Volume */ + { 0x000006CE, 0x0000 }, /* R1742 - OUT5RMIX Input 4 Source */ + { 0x000006CF, 0x0080 }, /* R1743 - OUT5RMIX Input 4 Volume */ + { 0x000006D0, 0x0000 }, /* R1744 - OUT6LMIX Input 1 Source */ + { 0x000006D1, 0x0080 }, /* R1745 - OUT6LMIX Input 1 Volume */ + { 0x000006D2, 0x0000 }, /* R1746 - OUT6LMIX Input 2 Source */ + { 0x000006D3, 0x0080 }, /* R1747 - OUT6LMIX Input 2 Volume */ + { 0x000006D4, 0x0000 }, /* R1748 - OUT6LMIX Input 3 Source */ + { 0x000006D5, 0x0080 }, /* R1749 - OUT6LMIX Input 3 Volume */ + { 0x000006D6, 0x0000 }, /* R1750 - OUT6LMIX Input 4 Source */ + { 0x000006D7, 0x0080 }, /* R1751 - OUT6LMIX Input 4 Volume */ + { 0x000006D8, 0x0000 }, /* R1752 - OUT6RMIX Input 1 Source */ + { 0x000006D9, 0x0080 }, /* R1753 - OUT6RMIX Input 1 Volume */ + { 0x000006DA, 0x0000 }, /* R1754 - OUT6RMIX Input 2 Source */ + { 0x000006DB, 0x0080 }, /* R1755 - OUT6RMIX Input 2 Volume */ + { 0x000006DC, 0x0000 }, /* R1756 - OUT6RMIX Input 3 Source */ + { 0x000006DD, 0x0080 }, /* R1757 - OUT6RMIX Input 3 Volume */ + { 0x000006DE, 0x0000 }, /* R1758 - OUT6RMIX Input 4 Source */ + { 0x000006DF, 0x0080 }, /* R1759 - OUT6RMIX Input 4 Volume */ + { 0x00000700, 0x0000 }, /* R1792 - AIF1TX1MIX Input 1 Source */ + { 0x00000701, 0x0080 }, /* R1793 - AIF1TX1MIX Input 1 Volume */ + { 0x00000702, 0x0000 }, /* R1794 - AIF1TX1MIX Input 2 Source */ + { 0x00000703, 0x0080 }, /* R1795 - AIF1TX1MIX Input 2 Volume */ + { 0x00000704, 0x0000 }, /* R1796 - AIF1TX1MIX Input 3 Source */ + { 0x00000705, 0x0080 }, /* R1797 - AIF1TX1MIX Input 3 Volume */ + { 0x00000706, 0x0000 }, /* R1798 - AIF1TX1MIX Input 4 Source */ + { 0x00000707, 0x0080 }, /* R1799 - AIF1TX1MIX Input 4 Volume */ + { 0x00000708, 0x0000 }, /* R1800 - AIF1TX2MIX Input 1 Source */ + { 0x00000709, 0x0080 }, /* R1801 - AIF1TX2MIX Input 1 Volume */ + { 0x0000070A, 0x0000 }, /* R1802 - AIF1TX2MIX Input 2 Source */ + { 0x0000070B, 0x0080 }, /* R1803 - AIF1TX2MIX Input 2 Volume */ + { 0x0000070C, 0x0000 }, /* R1804 - AIF1TX2MIX Input 3 Source */ + { 0x0000070D, 0x0080 }, /* R1805 - AIF1TX2MIX Input 3 Volume */ + { 0x0000070E, 0x0000 }, /* R1806 - AIF1TX2MIX Input 4 Source */ + { 0x0000070F, 0x0080 }, /* R1807 - AIF1TX2MIX Input 4 Volume */ + { 0x00000710, 0x0000 }, /* R1808 - AIF1TX3MIX Input 1 Source */ + { 0x00000711, 0x0080 }, /* R1809 - AIF1TX3MIX Input 1 Volume */ + { 0x00000712, 0x0000 }, /* R1810 - AIF1TX3MIX Input 2 Source */ + { 0x00000713, 0x0080 }, /* R1811 - AIF1TX3MIX Input 2 Volume */ + { 0x00000714, 0x0000 }, /* R1812 - AIF1TX3MIX Input 3 Source */ + { 0x00000715, 0x0080 }, /* R1813 - AIF1TX3MIX Input 3 Volume */ + { 0x00000716, 0x0000 }, /* R1814 - AIF1TX3MIX Input 4 Source */ + { 0x00000717, 0x0080 }, /* R1815 - AIF1TX3MIX Input 4 Volume */ + { 0x00000718, 0x0000 }, /* R1816 - AIF1TX4MIX Input 1 Source */ + { 0x00000719, 0x0080 }, /* R1817 - AIF1TX4MIX Input 1 Volume */ + { 0x0000071A, 0x0000 }, /* R1818 - AIF1TX4MIX Input 2 Source */ + { 0x0000071B, 0x0080 }, /* R1819 - AIF1TX4MIX Input 2 Volume */ + { 0x0000071C, 0x0000 }, /* R1820 - AIF1TX4MIX Input 3 Source */ + { 0x0000071D, 0x0080 }, /* R1821 - AIF1TX4MIX Input 3 Volume */ + { 0x0000071E, 0x0000 }, /* R1822 - AIF1TX4MIX Input 4 Source */ + { 0x0000071F, 0x0080 }, /* R1823 - AIF1TX4MIX Input 4 Volume */ + { 0x00000720, 0x0000 }, /* R1824 - AIF1TX5MIX Input 1 Source */ + { 0x00000721, 0x0080 }, /* R1825 - AIF1TX5MIX Input 1 Volume */ + { 0x00000722, 0x0000 }, /* R1826 - AIF1TX5MIX Input 2 Source */ + { 0x00000723, 0x0080 }, /* R1827 - AIF1TX5MIX Input 2 Volume */ + { 0x00000724, 0x0000 }, /* R1828 - AIF1TX5MIX Input 3 Source */ + { 0x00000725, 0x0080 }, /* R1829 - AIF1TX5MIX Input 3 Volume */ + { 0x00000726, 0x0000 }, /* R1830 - AIF1TX5MIX Input 4 Source */ + { 0x00000727, 0x0080 }, /* R1831 - AIF1TX5MIX Input 4 Volume */ + { 0x00000728, 0x0000 }, /* R1832 - AIF1TX6MIX Input 1 Source */ + { 0x00000729, 0x0080 }, /* R1833 - AIF1TX6MIX Input 1 Volume */ + { 0x0000072A, 0x0000 }, /* R1834 - AIF1TX6MIX Input 2 Source */ + { 0x0000072B, 0x0080 }, /* R1835 - AIF1TX6MIX Input 2 Volume */ + { 0x0000072C, 0x0000 }, /* R1836 - AIF1TX6MIX Input 3 Source */ + { 0x0000072D, 0x0080 }, /* R1837 - AIF1TX6MIX Input 3 Volume */ + { 0x0000072E, 0x0000 }, /* R1838 - AIF1TX6MIX Input 4 Source */ + { 0x0000072F, 0x0080 }, /* R1839 - AIF1TX6MIX Input 4 Volume */ + { 0x00000730, 0x0000 }, /* R1840 - AIF1TX7MIX Input 1 Source */ + { 0x00000731, 0x0080 }, /* R1841 - AIF1TX7MIX Input 1 Volume */ + { 0x00000732, 0x0000 }, /* R1842 - AIF1TX7MIX Input 2 Source */ + { 0x00000733, 0x0080 }, /* R1843 - AIF1TX7MIX Input 2 Volume */ + { 0x00000734, 0x0000 }, /* R1844 - AIF1TX7MIX Input 3 Source */ + { 0x00000735, 0x0080 }, /* R1845 - AIF1TX7MIX Input 3 Volume */ + { 0x00000736, 0x0000 }, /* R1846 - AIF1TX7MIX Input 4 Source */ + { 0x00000737, 0x0080 }, /* R1847 - AIF1TX7MIX Input 4 Volume */ + { 0x00000738, 0x0000 }, /* R1848 - AIF1TX8MIX Input 1 Source */ + { 0x00000739, 0x0080 }, /* R1849 - AIF1TX8MIX Input 1 Volume */ + { 0x0000073A, 0x0000 }, /* R1850 - AIF1TX8MIX Input 2 Source */ + { 0x0000073B, 0x0080 }, /* R1851 - AIF1TX8MIX Input 2 Volume */ + { 0x0000073C, 0x0000 }, /* R1852 - AIF1TX8MIX Input 3 Source */ + { 0x0000073D, 0x0080 }, /* R1853 - AIF1TX8MIX Input 3 Volume */ + { 0x0000073E, 0x0000 }, /* R1854 - AIF1TX8MIX Input 4 Source */ + { 0x0000073F, 0x0080 }, /* R1855 - AIF1TX8MIX Input 4 Volume */ + { 0x00000740, 0x0000 }, /* R1856 - AIF2TX1MIX Input 1 Source */ + { 0x00000741, 0x0080 }, /* R1857 - AIF2TX1MIX Input 1 Volume */ + { 0x00000742, 0x0000 }, /* R1858 - AIF2TX1MIX Input 2 Source */ + { 0x00000743, 0x0080 }, /* R1859 - AIF2TX1MIX Input 2 Volume */ + { 0x00000744, 0x0000 }, /* R1860 - AIF2TX1MIX Input 3 Source */ + { 0x00000745, 0x0080 }, /* R1861 - AIF2TX1MIX Input 3 Volume */ + { 0x00000746, 0x0000 }, /* R1862 - AIF2TX1MIX Input 4 Source */ + { 0x00000747, 0x0080 }, /* R1863 - AIF2TX1MIX Input 4 Volume */ + { 0x00000748, 0x0000 }, /* R1864 - AIF2TX2MIX Input 1 Source */ + { 0x00000749, 0x0080 }, /* R1865 - AIF2TX2MIX Input 1 Volume */ + { 0x0000074A, 0x0000 }, /* R1866 - AIF2TX2MIX Input 2 Source */ + { 0x0000074B, 0x0080 }, /* R1867 - AIF2TX2MIX Input 2 Volume */ + { 0x0000074C, 0x0000 }, /* R1868 - AIF2TX2MIX Input 3 Source */ + { 0x0000074D, 0x0080 }, /* R1869 - AIF2TX2MIX Input 3 Volume */ + { 0x0000074E, 0x0000 }, /* R1870 - AIF2TX2MIX Input 4 Source */ + { 0x0000074F, 0x0080 }, /* R1871 - AIF2TX2MIX Input 4 Volume */ + { 0x00000750, 0x0000 }, /* R1872 - AIF2TX3MIX Input 1 Source */ + { 0x00000751, 0x0080 }, /* R1873 - AIF2TX3MIX Input 1 Volume */ + { 0x00000752, 0x0000 }, /* R1874 - AIF2TX3MIX Input 2 Source */ + { 0x00000753, 0x0080 }, /* R1875 - AIF2TX3MIX Input 2 Volume */ + { 0x00000754, 0x0000 }, /* R1876 - AIF2TX3MIX Input 3 Source */ + { 0x00000755, 0x0080 }, /* R1877 - AIF2TX3MIX Input 3 Volume */ + { 0x00000756, 0x0000 }, /* R1878 - AIF2TX3MIX Input 4 Source */ + { 0x00000757, 0x0080 }, /* R1879 - AIF2TX3MIX Input 4 Volume */ + { 0x00000758, 0x0000 }, /* R1880 - AIF2TX4MIX Input 1 Source */ + { 0x00000759, 0x0080 }, /* R1881 - AIF2TX4MIX Input 1 Volume */ + { 0x0000075A, 0x0000 }, /* R1882 - AIF2TX4MIX Input 2 Source */ + { 0x0000075B, 0x0080 }, /* R1883 - AIF2TX4MIX Input 2 Volume */ + { 0x0000075C, 0x0000 }, /* R1884 - AIF2TX4MIX Input 3 Source */ + { 0x0000075D, 0x0080 }, /* R1885 - AIF2TX4MIX Input 3 Volume */ + { 0x0000075E, 0x0000 }, /* R1886 - AIF2TX4MIX Input 4 Source */ + { 0x0000075F, 0x0080 }, /* R1887 - AIF2TX4MIX Input 4 Volume */ + { 0x00000760, 0x0000 }, /* R1888 - AIF2TX5MIX Input 1 Source */ + { 0x00000761, 0x0080 }, /* R1889 - AIF2TX5MIX Input 1 Volume */ + { 0x00000762, 0x0000 }, /* R1890 - AIF2TX5MIX Input 2 Source */ + { 0x00000763, 0x0080 }, /* R1891 - AIF2TX5MIX Input 2 Volume */ + { 0x00000764, 0x0000 }, /* R1892 - AIF2TX5MIX Input 3 Source */ + { 0x00000765, 0x0080 }, /* R1893 - AIF2TX5MIX Input 3 Volume */ + { 0x00000766, 0x0000 }, /* R1894 - AIF2TX5MIX Input 4 Source */ + { 0x00000767, 0x0080 }, /* R1895 - AIF2TX5MIX Input 4 Volume */ + { 0x00000768, 0x0000 }, /* R1896 - AIF2TX6MIX Input 1 Source */ + { 0x00000769, 0x0080 }, /* R1897 - AIF2TX6MIX Input 1 Volume */ + { 0x0000076A, 0x0000 }, /* R1898 - AIF2TX6MIX Input 2 Source */ + { 0x0000076B, 0x0080 }, /* R1899 - AIF2TX6MIX Input 2 Volume */ + { 0x0000076C, 0x0000 }, /* R1900 - AIF2TX6MIX Input 3 Source */ + { 0x0000076D, 0x0080 }, /* R1901 - AIF2TX6MIX Input 3 Volume */ + { 0x0000076E, 0x0000 }, /* R1902 - AIF2TX6MIX Input 4 Source */ + { 0x0000076F, 0x0080 }, /* R1903 - AIF2TX6MIX Input 4 Volume */ + { 0x00000780, 0x0000 }, /* R1920 - AIF3TX1MIX Input 1 Source */ + { 0x00000781, 0x0080 }, /* R1921 - AIF3TX1MIX Input 1 Volume */ + { 0x00000782, 0x0000 }, /* R1922 - AIF3TX1MIX Input 2 Source */ + { 0x00000783, 0x0080 }, /* R1923 - AIF3TX1MIX Input 2 Volume */ + { 0x00000784, 0x0000 }, /* R1924 - AIF3TX1MIX Input 3 Source */ + { 0x00000785, 0x0080 }, /* R1925 - AIF3TX1MIX Input 3 Volume */ + { 0x00000786, 0x0000 }, /* R1926 - AIF3TX1MIX Input 4 Source */ + { 0x00000787, 0x0080 }, /* R1927 - AIF3TX1MIX Input 4 Volume */ + { 0x00000788, 0x0000 }, /* R1928 - AIF3TX2MIX Input 1 Source */ + { 0x00000789, 0x0080 }, /* R1929 - AIF3TX2MIX Input 1 Volume */ + { 0x0000078A, 0x0000 }, /* R1930 - AIF3TX2MIX Input 2 Source */ + { 0x0000078B, 0x0080 }, /* R1931 - AIF3TX2MIX Input 2 Volume */ + { 0x0000078C, 0x0000 }, /* R1932 - AIF3TX2MIX Input 3 Source */ + { 0x0000078D, 0x0080 }, /* R1933 - AIF3TX2MIX Input 3 Volume */ + { 0x0000078E, 0x0000 }, /* R1934 - AIF3TX2MIX Input 4 Source */ + { 0x0000078F, 0x0080 }, /* R1935 - AIF3TX2MIX Input 4 Volume */ + { 0x000007C0, 0x0000 }, /* R1984 - SLIMTX1MIX Input 1 Source */ + { 0x000007C1, 0x0080 }, /* R1985 - SLIMTX1MIX Input 1 Volume */ + { 0x000007C2, 0x0000 }, /* R1986 - SLIMTX1MIX Input 2 Source */ + { 0x000007C3, 0x0080 }, /* R1987 - SLIMTX1MIX Input 2 Volume */ + { 0x000007C4, 0x0000 }, /* R1988 - SLIMTX1MIX Input 3 Source */ + { 0x000007C5, 0x0080 }, /* R1989 - SLIMTX1MIX Input 3 Volume */ + { 0x000007C6, 0x0000 }, /* R1990 - SLIMTX1MIX Input 4 Source */ + { 0x000007C7, 0x0080 }, /* R1991 - SLIMTX1MIX Input 4 Volume */ + { 0x000007C8, 0x0000 }, /* R1992 - SLIMTX2MIX Input 1 Source */ + { 0x000007C9, 0x0080 }, /* R1993 - SLIMTX2MIX Input 1 Volume */ + { 0x000007CA, 0x0000 }, /* R1994 - SLIMTX2MIX Input 2 Source */ + { 0x000007CB, 0x0080 }, /* R1995 - SLIMTX2MIX Input 2 Volume */ + { 0x000007CC, 0x0000 }, /* R1996 - SLIMTX2MIX Input 3 Source */ + { 0x000007CD, 0x0080 }, /* R1997 - SLIMTX2MIX Input 3 Volume */ + { 0x000007CE, 0x0000 }, /* R1998 - SLIMTX2MIX Input 4 Source */ + { 0x000007CF, 0x0080 }, /* R1999 - SLIMTX2MIX Input 4 Volume */ + { 0x000007D0, 0x0000 }, /* R2000 - SLIMTX3MIX Input 1 Source */ + { 0x000007D1, 0x0080 }, /* R2001 - SLIMTX3MIX Input 1 Volume */ + { 0x000007D2, 0x0000 }, /* R2002 - SLIMTX3MIX Input 2 Source */ + { 0x000007D3, 0x0080 }, /* R2003 - SLIMTX3MIX Input 2 Volume */ + { 0x000007D4, 0x0000 }, /* R2004 - SLIMTX3MIX Input 3 Source */ + { 0x000007D5, 0x0080 }, /* R2005 - SLIMTX3MIX Input 3 Volume */ + { 0x000007D6, 0x0000 }, /* R2006 - SLIMTX3MIX Input 4 Source */ + { 0x000007D7, 0x0080 }, /* R2007 - SLIMTX3MIX Input 4 Volume */ + { 0x000007D8, 0x0000 }, /* R2008 - SLIMTX4MIX Input 1 Source */ + { 0x000007D9, 0x0080 }, /* R2009 - SLIMTX4MIX Input 1 Volume */ + { 0x000007DA, 0x0000 }, /* R2010 - SLIMTX4MIX Input 2 Source */ + { 0x000007DB, 0x0080 }, /* R2011 - SLIMTX4MIX Input 2 Volume */ + { 0x000007DC, 0x0000 }, /* R2012 - SLIMTX4MIX Input 3 Source */ + { 0x000007DD, 0x0080 }, /* R2013 - SLIMTX4MIX Input 3 Volume */ + { 0x000007DE, 0x0000 }, /* R2014 - SLIMTX4MIX Input 4 Source */ + { 0x000007DF, 0x0080 }, /* R2015 - SLIMTX4MIX Input 4 Volume */ + { 0x000007E0, 0x0000 }, /* R2016 - SLIMTX5MIX Input 1 Source */ + { 0x000007E1, 0x0080 }, /* R2017 - SLIMTX5MIX Input 1 Volume */ + { 0x000007E2, 0x0000 }, /* R2018 - SLIMTX5MIX Input 2 Source */ + { 0x000007E3, 0x0080 }, /* R2019 - SLIMTX5MIX Input 2 Volume */ + { 0x000007E4, 0x0000 }, /* R2020 - SLIMTX5MIX Input 3 Source */ + { 0x000007E5, 0x0080 }, /* R2021 - SLIMTX5MIX Input 3 Volume */ + { 0x000007E6, 0x0000 }, /* R2022 - SLIMTX5MIX Input 4 Source */ + { 0x000007E7, 0x0080 }, /* R2023 - SLIMTX5MIX Input 4 Volume */ + { 0x000007E8, 0x0000 }, /* R2024 - SLIMTX6MIX Input 1 Source */ + { 0x000007E9, 0x0080 }, /* R2025 - SLIMTX6MIX Input 1 Volume */ + { 0x000007EA, 0x0000 }, /* R2026 - SLIMTX6MIX Input 2 Source */ + { 0x000007EB, 0x0080 }, /* R2027 - SLIMTX6MIX Input 2 Volume */ + { 0x000007EC, 0x0000 }, /* R2028 - SLIMTX6MIX Input 3 Source */ + { 0x000007ED, 0x0080 }, /* R2029 - SLIMTX6MIX Input 3 Volume */ + { 0x000007EE, 0x0000 }, /* R2030 - SLIMTX6MIX Input 4 Source */ + { 0x000007EF, 0x0080 }, /* R2031 - SLIMTX6MIX Input 4 Volume */ + { 0x000007F0, 0x0000 }, /* R2032 - SLIMTX7MIX Input 1 Source */ + { 0x000007F1, 0x0080 }, /* R2033 - SLIMTX7MIX Input 1 Volume */ + { 0x000007F2, 0x0000 }, /* R2034 - SLIMTX7MIX Input 2 Source */ + { 0x000007F3, 0x0080 }, /* R2035 - SLIMTX7MIX Input 2 Volume */ + { 0x000007F4, 0x0000 }, /* R2036 - SLIMTX7MIX Input 3 Source */ + { 0x000007F5, 0x0080 }, /* R2037 - SLIMTX7MIX Input 3 Volume */ + { 0x000007F6, 0x0000 }, /* R2038 - SLIMTX7MIX Input 4 Source */ + { 0x000007F7, 0x0080 }, /* R2039 - SLIMTX7MIX Input 4 Volume */ + { 0x000007F8, 0x0000 }, /* R2040 - SLIMTX8MIX Input 1 Source */ + { 0x000007F9, 0x0080 }, /* R2041 - SLIMTX8MIX Input 1 Volume */ + { 0x000007FA, 0x0000 }, /* R2042 - SLIMTX8MIX Input 2 Source */ + { 0x000007FB, 0x0080 }, /* R2043 - SLIMTX8MIX Input 2 Volume */ + { 0x000007FC, 0x0000 }, /* R2044 - SLIMTX8MIX Input 3 Source */ + { 0x000007FD, 0x0080 }, /* R2045 - SLIMTX8MIX Input 3 Volume */ + { 0x000007FE, 0x0000 }, /* R2046 - SLIMTX8MIX Input 4 Source */ + { 0x000007FF, 0x0080 }, /* R2047 - SLIMTX8MIX Input 4 Volume */ + { 0x00000880, 0x0000 }, /* R2176 - EQ1MIX Input 1 Source */ + { 0x00000881, 0x0080 }, /* R2177 - EQ1MIX Input 1 Volume */ + { 0x00000882, 0x0000 }, /* R2178 - EQ1MIX Input 2 Source */ + { 0x00000883, 0x0080 }, /* R2179 - EQ1MIX Input 2 Volume */ + { 0x00000884, 0x0000 }, /* R2180 - EQ1MIX Input 3 Source */ + { 0x00000885, 0x0080 }, /* R2181 - EQ1MIX Input 3 Volume */ + { 0x00000886, 0x0000 }, /* R2182 - EQ1MIX Input 4 Source */ + { 0x00000887, 0x0080 }, /* R2183 - EQ1MIX Input 4 Volume */ + { 0x00000888, 0x0000 }, /* R2184 - EQ2MIX Input 1 Source */ + { 0x00000889, 0x0080 }, /* R2185 - EQ2MIX Input 1 Volume */ + { 0x0000088A, 0x0000 }, /* R2186 - EQ2MIX Input 2 Source */ + { 0x0000088B, 0x0080 }, /* R2187 - EQ2MIX Input 2 Volume */ + { 0x0000088C, 0x0000 }, /* R2188 - EQ2MIX Input 3 Source */ + { 0x0000088D, 0x0080 }, /* R2189 - EQ2MIX Input 3 Volume */ + { 0x0000088E, 0x0000 }, /* R2190 - EQ2MIX Input 4 Source */ + { 0x0000088F, 0x0080 }, /* R2191 - EQ2MIX Input 4 Volume */ + { 0x00000890, 0x0000 }, /* R2192 - EQ3MIX Input 1 Source */ + { 0x00000891, 0x0080 }, /* R2193 - EQ3MIX Input 1 Volume */ + { 0x00000892, 0x0000 }, /* R2194 - EQ3MIX Input 2 Source */ + { 0x00000893, 0x0080 }, /* R2195 - EQ3MIX Input 2 Volume */ + { 0x00000894, 0x0000 }, /* R2196 - EQ3MIX Input 3 Source */ + { 0x00000895, 0x0080 }, /* R2197 - EQ3MIX Input 3 Volume */ + { 0x00000896, 0x0000 }, /* R2198 - EQ3MIX Input 4 Source */ + { 0x00000897, 0x0080 }, /* R2199 - EQ3MIX Input 4 Volume */ + { 0x00000898, 0x0000 }, /* R2200 - EQ4MIX Input 1 Source */ + { 0x00000899, 0x0080 }, /* R2201 - EQ4MIX Input 1 Volume */ + { 0x0000089A, 0x0000 }, /* R2202 - EQ4MIX Input 2 Source */ + { 0x0000089B, 0x0080 }, /* R2203 - EQ4MIX Input 2 Volume */ + { 0x0000089C, 0x0000 }, /* R2204 - EQ4MIX Input 3 Source */ + { 0x0000089D, 0x0080 }, /* R2205 - EQ4MIX Input 3 Volume */ + { 0x0000089E, 0x0000 }, /* R2206 - EQ4MIX Input 4 Source */ + { 0x0000089F, 0x0080 }, /* R2207 - EQ4MIX Input 4 Volume */ + { 0x000008C0, 0x0000 }, /* R2240 - DRC1LMIX Input 1 Source */ + { 0x000008C1, 0x0080 }, /* R2241 - DRC1LMIX Input 1 Volume */ + { 0x000008C2, 0x0000 }, /* R2242 - DRC1LMIX Input 2 Source */ + { 0x000008C3, 0x0080 }, /* R2243 - DRC1LMIX Input 2 Volume */ + { 0x000008C4, 0x0000 }, /* R2244 - DRC1LMIX Input 3 Source */ + { 0x000008C5, 0x0080 }, /* R2245 - DRC1LMIX Input 3 Volume */ + { 0x000008C6, 0x0000 }, /* R2246 - DRC1LMIX Input 4 Source */ + { 0x000008C7, 0x0080 }, /* R2247 - DRC1LMIX Input 4 Volume */ + { 0x000008C8, 0x0000 }, /* R2248 - DRC1RMIX Input 1 Source */ + { 0x000008C9, 0x0080 }, /* R2249 - DRC1RMIX Input 1 Volume */ + { 0x000008CA, 0x0000 }, /* R2250 - DRC1RMIX Input 2 Source */ + { 0x000008CB, 0x0080 }, /* R2251 - DRC1RMIX Input 2 Volume */ + { 0x000008CC, 0x0000 }, /* R2252 - DRC1RMIX Input 3 Source */ + { 0x000008CD, 0x0080 }, /* R2253 - DRC1RMIX Input 3 Volume */ + { 0x000008CE, 0x0000 }, /* R2254 - DRC1RMIX Input 4 Source */ + { 0x000008CF, 0x0080 }, /* R2255 - DRC1RMIX Input 4 Volume */ + { 0x000008D0, 0x0000 }, /* R2256 - DRC2LMIX Input 1 Source */ + { 0x000008D1, 0x0080 }, /* R2257 - DRC2LMIX Input 1 Volume */ + { 0x000008D2, 0x0000 }, /* R2258 - DRC2LMIX Input 2 Source */ + { 0x000008D3, 0x0080 }, /* R2259 - DRC2LMIX Input 2 Volume */ + { 0x000008D4, 0x0000 }, /* R2260 - DRC2LMIX Input 3 Source */ + { 0x000008D5, 0x0080 }, /* R2261 - DRC2LMIX Input 3 Volume */ + { 0x000008D6, 0x0000 }, /* R2262 - DRC2LMIX Input 4 Source */ + { 0x000008D7, 0x0080 }, /* R2263 - DRC2LMIX Input 4 Volume */ + { 0x000008D8, 0x0000 }, /* R2264 - DRC2RMIX Input 1 Source */ + { 0x000008D9, 0x0080 }, /* R2265 - DRC2RMIX Input 1 Volume */ + { 0x000008DA, 0x0000 }, /* R2266 - DRC2RMIX Input 2 Source */ + { 0x000008DB, 0x0080 }, /* R2267 - DRC2RMIX Input 2 Volume */ + { 0x000008DC, 0x0000 }, /* R2268 - DRC2RMIX Input 3 Source */ + { 0x000008DD, 0x0080 }, /* R2269 - DRC2RMIX Input 3 Volume */ + { 0x000008DE, 0x0000 }, /* R2270 - DRC2RMIX Input 4 Source */ + { 0x000008DF, 0x0080 }, /* R2271 - DRC2RMIX Input 4 Volume */ + { 0x00000900, 0x0000 }, /* R2304 - HPLP1MIX Input 1 Source */ + { 0x00000901, 0x0080 }, /* R2305 - HPLP1MIX Input 1 Volume */ + { 0x00000902, 0x0000 }, /* R2306 - HPLP1MIX Input 2 Source */ + { 0x00000903, 0x0080 }, /* R2307 - HPLP1MIX Input 2 Volume */ + { 0x00000904, 0x0000 }, /* R2308 - HPLP1MIX Input 3 Source */ + { 0x00000905, 0x0080 }, /* R2309 - HPLP1MIX Input 3 Volume */ + { 0x00000906, 0x0000 }, /* R2310 - HPLP1MIX Input 4 Source */ + { 0x00000907, 0x0080 }, /* R2311 - HPLP1MIX Input 4 Volume */ + { 0x00000908, 0x0000 }, /* R2312 - HPLP2MIX Input 1 Source */ + { 0x00000909, 0x0080 }, /* R2313 - HPLP2MIX Input 1 Volume */ + { 0x0000090A, 0x0000 }, /* R2314 - HPLP2MIX Input 2 Source */ + { 0x0000090B, 0x0080 }, /* R2315 - HPLP2MIX Input 2 Volume */ + { 0x0000090C, 0x0000 }, /* R2316 - HPLP2MIX Input 3 Source */ + { 0x0000090D, 0x0080 }, /* R2317 - HPLP2MIX Input 3 Volume */ + { 0x0000090E, 0x0000 }, /* R2318 - HPLP2MIX Input 4 Source */ + { 0x0000090F, 0x0080 }, /* R2319 - HPLP2MIX Input 4 Volume */ + { 0x00000910, 0x0000 }, /* R2320 - HPLP3MIX Input 1 Source */ + { 0x00000911, 0x0080 }, /* R2321 - HPLP3MIX Input 1 Volume */ + { 0x00000912, 0x0000 }, /* R2322 - HPLP3MIX Input 2 Source */ + { 0x00000913, 0x0080 }, /* R2323 - HPLP3MIX Input 2 Volume */ + { 0x00000914, 0x0000 }, /* R2324 - HPLP3MIX Input 3 Source */ + { 0x00000915, 0x0080 }, /* R2325 - HPLP3MIX Input 3 Volume */ + { 0x00000916, 0x0000 }, /* R2326 - HPLP3MIX Input 4 Source */ + { 0x00000917, 0x0080 }, /* R2327 - HPLP3MIX Input 4 Volume */ + { 0x00000918, 0x0000 }, /* R2328 - HPLP4MIX Input 1 Source */ + { 0x00000919, 0x0080 }, /* R2329 - HPLP4MIX Input 1 Volume */ + { 0x0000091A, 0x0000 }, /* R2330 - HPLP4MIX Input 2 Source */ + { 0x0000091B, 0x0080 }, /* R2331 - HPLP4MIX Input 2 Volume */ + { 0x0000091C, 0x0000 }, /* R2332 - HPLP4MIX Input 3 Source */ + { 0x0000091D, 0x0080 }, /* R2333 - HPLP4MIX Input 3 Volume */ + { 0x0000091E, 0x0000 }, /* R2334 - HPLP4MIX Input 4 Source */ + { 0x0000091F, 0x0080 }, /* R2335 - HPLP4MIX Input 4 Volume */ + { 0x00000940, 0x0000 }, /* R2368 - DSP1LMIX Input 1 Source */ + { 0x00000941, 0x0080 }, /* R2369 - DSP1LMIX Input 1 Volume */ + { 0x00000942, 0x0000 }, /* R2370 - DSP1LMIX Input 2 Source */ + { 0x00000943, 0x0080 }, /* R2371 - DSP1LMIX Input 2 Volume */ + { 0x00000944, 0x0000 }, /* R2372 - DSP1LMIX Input 3 Source */ + { 0x00000945, 0x0080 }, /* R2373 - DSP1LMIX Input 3 Volume */ + { 0x00000946, 0x0000 }, /* R2374 - DSP1LMIX Input 4 Source */ + { 0x00000947, 0x0080 }, /* R2375 - DSP1LMIX Input 4 Volume */ + { 0x00000948, 0x0000 }, /* R2376 - DSP1RMIX Input 1 Source */ + { 0x00000949, 0x0080 }, /* R2377 - DSP1RMIX Input 1 Volume */ + { 0x0000094A, 0x0000 }, /* R2378 - DSP1RMIX Input 2 Source */ + { 0x0000094B, 0x0080 }, /* R2379 - DSP1RMIX Input 2 Volume */ + { 0x0000094C, 0x0000 }, /* R2380 - DSP1RMIX Input 3 Source */ + { 0x0000094D, 0x0080 }, /* R2381 - DSP1RMIX Input 3 Volume */ + { 0x0000094E, 0x0000 }, /* R2382 - DSP1RMIX Input 4 Source */ + { 0x0000094F, 0x0080 }, /* R2383 - DSP1RMIX Input 4 Volume */ + { 0x00000950, 0x0000 }, /* R2384 - DSP1AUX1MIX Input 1 Source */ + { 0x00000958, 0x0000 }, /* R2392 - DSP1AUX2MIX Input 1 Source */ + { 0x00000960, 0x0000 }, /* R2400 - DSP1AUX3MIX Input 1 Source */ + { 0x00000968, 0x0000 }, /* R2408 - DSP1AUX4MIX Input 1 Source */ + { 0x00000970, 0x0000 }, /* R2416 - DSP1AUX5MIX Input 1 Source */ + { 0x00000978, 0x0000 }, /* R2424 - DSP1AUX6MIX Input 1 Source */ + { 0x00000980, 0x0000 }, /* R2432 - DSP2LMIX Input 1 Source */ + { 0x00000981, 0x0080 }, /* R2433 - DSP2LMIX Input 1 Volume */ + { 0x00000982, 0x0000 }, /* R2434 - DSP2LMIX Input 2 Source */ + { 0x00000983, 0x0080 }, /* R2435 - DSP2LMIX Input 2 Volume */ + { 0x00000984, 0x0000 }, /* R2436 - DSP2LMIX Input 3 Source */ + { 0x00000985, 0x0080 }, /* R2437 - DSP2LMIX Input 3 Volume */ + { 0x00000986, 0x0000 }, /* R2438 - DSP2LMIX Input 4 Source */ + { 0x00000987, 0x0080 }, /* R2439 - DSP2LMIX Input 4 Volume */ + { 0x00000988, 0x0000 }, /* R2440 - DSP2RMIX Input 1 Source */ + { 0x00000989, 0x0080 }, /* R2441 - DSP2RMIX Input 1 Volume */ + { 0x0000098A, 0x0000 }, /* R2442 - DSP2RMIX Input 2 Source */ + { 0x0000098B, 0x0080 }, /* R2443 - DSP2RMIX Input 2 Volume */ + { 0x0000098C, 0x0000 }, /* R2444 - DSP2RMIX Input 3 Source */ + { 0x0000098D, 0x0080 }, /* R2445 - DSP2RMIX Input 3 Volume */ + { 0x0000098E, 0x0000 }, /* R2446 - DSP2RMIX Input 4 Source */ + { 0x0000098F, 0x0080 }, /* R2447 - DSP2RMIX Input 4 Volume */ + { 0x00000990, 0x0000 }, /* R2448 - DSP2AUX1MIX Input 1 Source */ + { 0x00000998, 0x0000 }, /* R2456 - DSP2AUX2MIX Input 1 Source */ + { 0x000009A0, 0x0000 }, /* R2464 - DSP2AUX3MIX Input 1 Source */ + { 0x000009A8, 0x0000 }, /* R2472 - DSP2AUX4MIX Input 1 Source */ + { 0x000009B0, 0x0000 }, /* R2480 - DSP2AUX5MIX Input 1 Source */ + { 0x000009B8, 0x0000 }, /* R2488 - DSP2AUX6MIX Input 1 Source */ + { 0x000009C0, 0x0000 }, /* R2496 - DSP3LMIX Input 1 Source */ + { 0x000009C1, 0x0080 }, /* R2497 - DSP3LMIX Input 1 Volume */ + { 0x000009C2, 0x0000 }, /* R2498 - DSP3LMIX Input 2 Source */ + { 0x000009C3, 0x0080 }, /* R2499 - DSP3LMIX Input 2 Volume */ + { 0x000009C4, 0x0000 }, /* R2500 - DSP3LMIX Input 3 Source */ + { 0x000009C5, 0x0080 }, /* R2501 - DSP3LMIX Input 3 Volume */ + { 0x000009C6, 0x0000 }, /* R2502 - DSP3LMIX Input 4 Source */ + { 0x000009C7, 0x0080 }, /* R2503 - DSP3LMIX Input 4 Volume */ + { 0x000009C8, 0x0000 }, /* R2504 - DSP3RMIX Input 1 Source */ + { 0x000009C9, 0x0080 }, /* R2505 - DSP3RMIX Input 1 Volume */ + { 0x000009CA, 0x0000 }, /* R2506 - DSP3RMIX Input 2 Source */ + { 0x000009CB, 0x0080 }, /* R2507 - DSP3RMIX Input 2 Volume */ + { 0x000009CC, 0x0000 }, /* R2508 - DSP3RMIX Input 3 Source */ + { 0x000009CD, 0x0080 }, /* R2509 - DSP3RMIX Input 3 Volume */ + { 0x000009CE, 0x0000 }, /* R2510 - DSP3RMIX Input 4 Source */ + { 0x000009CF, 0x0080 }, /* R2511 - DSP3RMIX Input 4 Volume */ + { 0x000009D0, 0x0000 }, /* R2512 - DSP3AUX1MIX Input 1 Source */ + { 0x000009D8, 0x0000 }, /* R2520 - DSP3AUX2MIX Input 1 Source */ + { 0x000009E0, 0x0000 }, /* R2528 - DSP3AUX3MIX Input 1 Source */ + { 0x000009E8, 0x0000 }, /* R2536 - DSP3AUX4MIX Input 1 Source */ + { 0x000009F0, 0x0000 }, /* R2544 - DSP3AUX5MIX Input 1 Source */ + { 0x000009F8, 0x0000 }, /* R2552 - DSP3AUX6MIX Input 1 Source */ + { 0x00000A00, 0x0000 }, /* R2560 - DSP4LMIX Input 1 Source */ + { 0x00000A01, 0x0080 }, /* R2561 - DSP4LMIX Input 1 Volume */ + { 0x00000A02, 0x0000 }, /* R2562 - DSP4LMIX Input 2 Source */ + { 0x00000A03, 0x0080 }, /* R2563 - DSP4LMIX Input 2 Volume */ + { 0x00000A04, 0x0000 }, /* R2564 - DSP4LMIX Input 3 Source */ + { 0x00000A05, 0x0080 }, /* R2565 - DSP4LMIX Input 3 Volume */ + { 0x00000A06, 0x0000 }, /* R2566 - DSP4LMIX Input 4 Source */ + { 0x00000A07, 0x0080 }, /* R2567 - DSP4LMIX Input 4 Volume */ + { 0x00000A08, 0x0000 }, /* R2568 - DSP4RMIX Input 1 Source */ + { 0x00000A09, 0x0080 }, /* R2569 - DSP4RMIX Input 1 Volume */ + { 0x00000A0A, 0x0000 }, /* R2570 - DSP4RMIX Input 2 Source */ + { 0x00000A0B, 0x0080 }, /* R2571 - DSP4RMIX Input 2 Volume */ + { 0x00000A0C, 0x0000 }, /* R2572 - DSP4RMIX Input 3 Source */ + { 0x00000A0D, 0x0080 }, /* R2573 - DSP4RMIX Input 3 Volume */ + { 0x00000A0E, 0x0000 }, /* R2574 - DSP4RMIX Input 4 Source */ + { 0x00000A0F, 0x0080 }, /* R2575 - DSP4RMIX Input 4 Volume */ + { 0x00000A10, 0x0000 }, /* R2576 - DSP4AUX1MIX Input 1 Source */ + { 0x00000A18, 0x0000 }, /* R2584 - DSP4AUX2MIX Input 1 Source */ + { 0x00000A20, 0x0000 }, /* R2592 - DSP4AUX3MIX Input 1 Source */ + { 0x00000A28, 0x0000 }, /* R2600 - DSP4AUX4MIX Input 1 Source */ + { 0x00000A30, 0x0000 }, /* R2608 - DSP4AUX5MIX Input 1 Source */ + { 0x00000A38, 0x0000 }, /* R2616 - DSP4AUX6MIX Input 1 Source */ + { 0x00000A80, 0x0000 }, /* R2688 - ASRC1LMIX Input 1 Source */ + { 0x00000A88, 0x0000 }, /* R2696 - ASRC1RMIX Input 1 Source */ + { 0x00000A90, 0x0000 }, /* R2704 - ASRC2LMIX Input 1 Source */ + { 0x00000A98, 0x0000 }, /* R2712 - ASRC2RMIX Input 1 Source */ + { 0x00000B00, 0x0000 }, /* R2816 - ISRC1DEC1MIX Input 1 Source */ + { 0x00000B08, 0x0000 }, /* R2824 - ISRC1DEC2MIX Input 1 Source */ + { 0x00000B10, 0x0000 }, /* R2832 - ISRC1DEC3MIX Input 1 Source */ + { 0x00000B18, 0x0000 }, /* R2840 - ISRC1DEC4MIX Input 1 Source */ + { 0x00000B20, 0x0000 }, /* R2848 - ISRC1INT1MIX Input 1 Source */ + { 0x00000B28, 0x0000 }, /* R2856 - ISRC1INT2MIX Input 1 Source */ + { 0x00000B30, 0x0000 }, /* R2864 - ISRC1INT3MIX Input 1 Source */ + { 0x00000B38, 0x0000 }, /* R2872 - ISRC1INT4MIX Input 1 Source */ + { 0x00000B40, 0x0000 }, /* R2880 - ISRC2DEC1MIX Input 1 Source */ + { 0x00000B48, 0x0000 }, /* R2888 - ISRC2DEC2MIX Input 1 Source */ + { 0x00000B50, 0x0000 }, /* R2896 - ISRC2DEC3MIX Input 1 Source */ + { 0x00000B58, 0x0000 }, /* R2904 - ISRC2DEC4MIX Input 1 Source */ + { 0x00000B60, 0x0000 }, /* R2912 - ISRC2INT1MIX Input 1 Source */ + { 0x00000B68, 0x0000 }, /* R2920 - ISRC2INT2MIX Input 1 Source */ + { 0x00000B70, 0x0000 }, /* R2928 - ISRC2INT3MIX Input 1 Source */ + { 0x00000B78, 0x0000 }, /* R2936 - ISRC2INT4MIX Input 1 Source */ + { 0x00000B80, 0x0000 }, /* R2944 - ISRC3DEC1MIX Input 1 Source */ + { 0x00000B88, 0x0000 }, /* R2952 - ISRC3DEC2MIX Input 1 Source */ + { 0x00000B90, 0x0000 }, /* R2960 - ISRC3DEC3MIX Input 1 Source */ + { 0x00000B98, 0x0000 }, /* R2968 - ISRC3DEC4MIX Input 1 Source */ + { 0x00000BA0, 0x0000 }, /* R2976 - ISRC3INT1MIX Input 1 Source */ + { 0x00000BA8, 0x0000 }, /* R2984 - ISRC3INT2MIX Input 1 Source */ + { 0x00000BB0, 0x0000 }, /* R2992 - ISRC3INT3MIX Input 1 Source */ + { 0x00000BB8, 0x0000 }, /* R3000 - ISRC3INT4MIX Input 1 Source */ + { 0x00000C00, 0xA101 }, /* R3072 - GPIO1 CTRL */ + { 0x00000C01, 0xA101 }, /* R3073 - GPIO2 CTRL */ + { 0x00000C02, 0xA101 }, /* R3074 - GPIO3 CTRL */ + { 0x00000C03, 0xA101 }, /* R3075 - GPIO4 CTRL */ + { 0x00000C04, 0xA101 }, /* R3076 - GPIO5 CTRL */ + { 0x00000C0F, 0x0400 }, /* R3087 - IRQ CTRL 1 */ + { 0x00000C10, 0x1000 }, /* R3088 - GPIO Debounce Config */ + { 0x00000C20, 0x8002 }, /* R3104 - Misc Pad Ctrl 1 */ + { 0x00000C21, 0x8001 }, /* R3105 - Misc Pad Ctrl 2 */ + { 0x00000C22, 0x0000 }, /* R3106 - Misc Pad Ctrl 3 */ + { 0x00000C23, 0x0000 }, /* R3107 - Misc Pad Ctrl 4 */ + { 0x00000C24, 0x0000 }, /* R3108 - Misc Pad Ctrl 5 */ + { 0x00000C25, 0x0000 }, /* R3109 - Misc Pad Ctrl 6 */ + { 0x00000C30, 0x8282 }, /* R3120 - Misc Pad Ctrl 7 */ + { 0x00000C31, 0x0082 }, /* R3121 - Misc Pad Ctrl 8 */ + { 0x00000C32, 0x8282 }, /* R3122 - Misc Pad Ctrl 9 */ + { 0x00000C33, 0x8282 }, /* R3123 - Misc Pad Ctrl 10 */ + { 0x00000C34, 0x8282 }, /* R3124 - Misc Pad Ctrl 11 */ + { 0x00000C35, 0x8282 }, /* R3125 - Misc Pad Ctrl 12 */ + { 0x00000C36, 0x8282 }, /* R3126 - Misc Pad Ctrl 13 */ + { 0x00000C37, 0x8282 }, /* R3127 - Misc Pad Ctrl 14 */ + { 0x00000C38, 0x8282 }, /* R3128 - Misc Pad Ctrl 15 */ + { 0x00000C39, 0x8282 }, /* R3129 - Misc Pad Ctrl 16 */ + { 0x00000C3A, 0x8282 }, /* R3130 - Misc Pad Ctrl 17 */ + { 0x00000C3B, 0x8282 }, /* R3131 - Misc Pad Ctrl 18 */ + { 0x00000D08, 0xFFFF }, /* R3336 - Interrupt Status 1 Mask */ + { 0x00000D09, 0xFFFF }, /* R3337 - Interrupt Status 2 Mask */ + { 0x00000D0A, 0xFFFF }, /* R3338 - Interrupt Status 3 Mask */ + { 0x00000D0B, 0xFFFF }, /* R3339 - Interrupt Status 4 Mask */ + { 0x00000D0C, 0xFEFF }, /* R3340 - Interrupt Status 5 Mask */ + { 0x00000D0D, 0xFFFF }, /* R3341 - Interrupt Status 6 Mask */ + { 0x00000D0F, 0x0000 }, /* R3343 - Interrupt Control */ + { 0x00000D18, 0xFFFF }, /* R3352 - IRQ2 Status 1 Mask */ + { 0x00000D19, 0xFFFF }, /* R3353 - IRQ2 Status 2 Mask */ + { 0x00000D1A, 0xFFFF }, /* R3354 - IRQ2 Status 3 Mask */ + { 0x00000D1B, 0xFFFF }, /* R3355 - IRQ2 Status 4 Mask */ + { 0x00000D1C, 0xFFFF }, /* R3356 - IRQ2 Status 5 Mask */ + { 0x00000D1D, 0xFFFF }, /* R3357 - IRQ2 Status 6 Mask */ + { 0x00000D1F, 0x0000 }, /* R3359 - IRQ2 Control */ + { 0x00000D53, 0xFFFF }, /* R3411 - AOD IRQ Mask IRQ1 */ + { 0x00000D54, 0xFFFF }, /* R3412 - AOD IRQ Mask IRQ2 */ + { 0x00000D56, 0x0000 }, /* R3414 - Jack detect debounce */ + { 0x00000E00, 0x0000 }, /* R3584 - FX_Ctrl1 */ + { 0x00000E01, 0x0000 }, /* R3585 - FX_Ctrl2 */ + { 0x00000E10, 0x6318 }, /* R3600 - EQ1_1 */ + { 0x00000E11, 0x6300 }, /* R3601 - EQ1_2 */ + { 0x00000E12, 0x0FC8 }, /* R3602 - EQ1_3 */ + { 0x00000E13, 0x03FE }, /* R3603 - EQ1_4 */ + { 0x00000E14, 0x00E0 }, /* R3604 - EQ1_5 */ + { 0x00000E15, 0x1EC4 }, /* R3605 - EQ1_6 */ + { 0x00000E16, 0xF136 }, /* R3606 - EQ1_7 */ + { 0x00000E17, 0x0409 }, /* R3607 - EQ1_8 */ + { 0x00000E18, 0x04CC }, /* R3608 - EQ1_9 */ + { 0x00000E19, 0x1C9B }, /* R3609 - EQ1_10 */ + { 0x00000E1A, 0xF337 }, /* R3610 - EQ1_11 */ + { 0x00000E1B, 0x040B }, /* R3611 - EQ1_12 */ + { 0x00000E1C, 0x0CBB }, /* R3612 - EQ1_13 */ + { 0x00000E1D, 0x16F8 }, /* R3613 - EQ1_14 */ + { 0x00000E1E, 0xF7D9 }, /* R3614 - EQ1_15 */ + { 0x00000E1F, 0x040A }, /* R3615 - EQ1_16 */ + { 0x00000E20, 0x1F14 }, /* R3616 - EQ1_17 */ + { 0x00000E21, 0x058C }, /* R3617 - EQ1_18 */ + { 0x00000E22, 0x0563 }, /* R3618 - EQ1_19 */ + { 0x00000E23, 0x4000 }, /* R3619 - EQ1_20 */ + { 0x00000E24, 0x0B75 }, /* R3620 - EQ1_21 */ + { 0x00000E26, 0x6318 }, /* R3622 - EQ2_1 */ + { 0x00000E27, 0x6300 }, /* R3623 - EQ2_2 */ + { 0x00000E28, 0x0FC8 }, /* R3624 - EQ2_3 */ + { 0x00000E29, 0x03FE }, /* R3625 - EQ2_4 */ + { 0x00000E2A, 0x00E0 }, /* R3626 - EQ2_5 */ + { 0x00000E2B, 0x1EC4 }, /* R3627 - EQ2_6 */ + { 0x00000E2C, 0xF136 }, /* R3628 - EQ2_7 */ + { 0x00000E2D, 0x0409 }, /* R3629 - EQ2_8 */ + { 0x00000E2E, 0x04CC }, /* R3630 - EQ2_9 */ + { 0x00000E2F, 0x1C9B }, /* R3631 - EQ2_10 */ + { 0x00000E30, 0xF337 }, /* R3632 - EQ2_11 */ + { 0x00000E31, 0x040B }, /* R3633 - EQ2_12 */ + { 0x00000E32, 0x0CBB }, /* R3634 - EQ2_13 */ + { 0x00000E33, 0x16F8 }, /* R3635 - EQ2_14 */ + { 0x00000E34, 0xF7D9 }, /* R3636 - EQ2_15 */ + { 0x00000E35, 0x040A }, /* R3637 - EQ2_16 */ + { 0x00000E36, 0x1F14 }, /* R3638 - EQ2_17 */ + { 0x00000E37, 0x058C }, /* R3639 - EQ2_18 */ + { 0x00000E38, 0x0563 }, /* R3640 - EQ2_19 */ + { 0x00000E39, 0x4000 }, /* R3641 - EQ2_20 */ + { 0x00000E3A, 0x0B75 }, /* R3642 - EQ2_21 */ + { 0x00000E3C, 0x6318 }, /* R3644 - EQ3_1 */ + { 0x00000E3D, 0x6300 }, /* R3645 - EQ3_2 */ + { 0x00000E3E, 0x0FC8 }, /* R3646 - EQ3_3 */ + { 0x00000E3F, 0x03FE }, /* R3647 - EQ3_4 */ + { 0x00000E40, 0x00E0 }, /* R3648 - EQ3_5 */ + { 0x00000E41, 0x1EC4 }, /* R3649 - EQ3_6 */ + { 0x00000E42, 0xF136 }, /* R3650 - EQ3_7 */ + { 0x00000E43, 0x0409 }, /* R3651 - EQ3_8 */ + { 0x00000E44, 0x04CC }, /* R3652 - EQ3_9 */ + { 0x00000E45, 0x1C9B }, /* R3653 - EQ3_10 */ + { 0x00000E46, 0xF337 }, /* R3654 - EQ3_11 */ + { 0x00000E47, 0x040B }, /* R3655 - EQ3_12 */ + { 0x00000E48, 0x0CBB }, /* R3656 - EQ3_13 */ + { 0x00000E49, 0x16F8 }, /* R3657 - EQ3_14 */ + { 0x00000E4A, 0xF7D9 }, /* R3658 - EQ3_15 */ + { 0x00000E4B, 0x040A }, /* R3659 - EQ3_16 */ + { 0x00000E4C, 0x1F14 }, /* R3660 - EQ3_17 */ + { 0x00000E4D, 0x058C }, /* R3661 - EQ3_18 */ + { 0x00000E4E, 0x0563 }, /* R3662 - EQ3_19 */ + { 0x00000E4F, 0x4000 }, /* R3663 - EQ3_20 */ + { 0x00000E50, 0x0B75 }, /* R3664 - EQ3_21 */ + { 0x00000E52, 0x6318 }, /* R3666 - EQ4_1 */ + { 0x00000E53, 0x6300 }, /* R3667 - EQ4_2 */ + { 0x00000E54, 0x0FC8 }, /* R3668 - EQ4_3 */ + { 0x00000E55, 0x03FE }, /* R3669 - EQ4_4 */ + { 0x00000E56, 0x00E0 }, /* R3670 - EQ4_5 */ + { 0x00000E57, 0x1EC4 }, /* R3671 - EQ4_6 */ + { 0x00000E58, 0xF136 }, /* R3672 - EQ4_7 */ + { 0x00000E59, 0x0409 }, /* R3673 - EQ4_8 */ + { 0x00000E5A, 0x04CC }, /* R3674 - EQ4_9 */ + { 0x00000E5B, 0x1C9B }, /* R3675 - EQ4_10 */ + { 0x00000E5C, 0xF337 }, /* R3676 - EQ4_11 */ + { 0x00000E5D, 0x040B }, /* R3677 - EQ4_12 */ + { 0x00000E5E, 0x0CBB }, /* R3678 - EQ4_13 */ + { 0x00000E5F, 0x16F8 }, /* R3679 - EQ4_14 */ + { 0x00000E60, 0xF7D9 }, /* R3680 - EQ4_15 */ + { 0x00000E61, 0x040A }, /* R3681 - EQ4_16 */ + { 0x00000E62, 0x1F14 }, /* R3682 - EQ4_17 */ + { 0x00000E63, 0x058C }, /* R3683 - EQ4_18 */ + { 0x00000E64, 0x0563 }, /* R3684 - EQ4_19 */ + { 0x00000E65, 0x4000 }, /* R3685 - EQ4_20 */ + { 0x00000E66, 0x0B75 }, /* R3686 - EQ4_21 */ + { 0x00000E80, 0x0018 }, /* R3712 - DRC1 ctrl1 */ + { 0x00000E81, 0x0933 }, /* R3713 - DRC1 ctrl2 */ + { 0x00000E82, 0x0018 }, /* R3714 - DRC1 ctrl3 */ + { 0x00000E83, 0x0000 }, /* R3715 - DRC1 ctrl4 */ + { 0x00000E84, 0x0000 }, /* R3716 - DRC1 ctrl5 */ + { 0x00000E89, 0x0018 }, /* R3721 - DRC2 ctrl1 */ + { 0x00000E8A, 0x0933 }, /* R3722 - DRC2 ctrl2 */ + { 0x00000E8B, 0x0018 }, /* R3723 - DRC2 ctrl3 */ + { 0x00000E8C, 0x0000 }, /* R3724 - DRC2 ctrl4 */ + { 0x00000E8D, 0x0000 }, /* R3725 - DRC2 ctrl5 */ + { 0x00000EC0, 0x0000 }, /* R3776 - HPLPF1_1 */ + { 0x00000EC1, 0x0000 }, /* R3777 - HPLPF1_2 */ + { 0x00000EC4, 0x0000 }, /* R3780 - HPLPF2_1 */ + { 0x00000EC5, 0x0000 }, /* R3781 - HPLPF2_2 */ + { 0x00000EC8, 0x0000 }, /* R3784 - HPLPF3_1 */ + { 0x00000EC9, 0x0000 }, /* R3785 - HPLPF3_2 */ + { 0x00000ECC, 0x0000 }, /* R3788 - HPLPF4_1 */ + { 0x00000ECD, 0x0000 }, /* R3789 - HPLPF4_2 */ + { 0x00000EE0, 0x0000 }, /* R3808 - ASRC_ENABLE */ + { 0x00000EE2, 0x0000 }, /* R3810 - ASRC_RATE1 */ + { 0x00000EF0, 0x0000 }, /* R3824 - ISRC 1 CTRL 1 */ + { 0x00000EF1, 0x0000 }, /* R3825 - ISRC 1 CTRL 2 */ + { 0x00000EF2, 0x0000 }, /* R3826 - ISRC 1 CTRL 3 */ + { 0x00000EF3, 0x0000 }, /* R3827 - ISRC 2 CTRL 1 */ + { 0x00000EF4, 0x0000 }, /* R3828 - ISRC 2 CTRL 2 */ + { 0x00000EF5, 0x0000 }, /* R3829 - ISRC 2 CTRL 3 */ + { 0x00000EF6, 0x0000 }, /* R3830 - ISRC 3 CTRL 1 */ + { 0x00000EF7, 0x0000 }, /* R3831 - ISRC 3 CTRL 2 */ + { 0x00000EF8, 0x0000 }, /* R3832 - ISRC 3 CTRL 3 */ + { 0x00000F00, 0x0000 }, /* R3840 - Clock Control */ + { 0x00000F01, 0x0000 }, /* R3841 - ANC_SRC */ + { 0x00001100, 0x0010 }, /* R4352 - DSP1 Control 1 */ + { 0x00001101, 0x0000 }, /* R4353 - DSP1 Clocking 1 */ + { 0x00001200, 0x0010 }, /* R4608 - DSP2 Control 1 */ + { 0x00001201, 0x0000 }, /* R4609 - DSP2 Clocking 1 */ + { 0x00001300, 0x0010 }, /* R4864 - DSP3 Control 1 */ + { 0x00001301, 0x0000 }, /* R4865 - DSP3 Clocking 1 */ + { 0x00001400, 0x0010 }, /* R5120 - DSP4 Control 1 */ + { 0x00001401, 0x0000 }, /* R5121 - DSP4 Clocking 1 */ + { 0x00001404, 0x0000 }, /* R5124 - DSP4 Status 1 */ +}; + +static bool wm5110_is_rev_b_adsp_memory(unsigned int reg) +{ + if ((reg >= 0x100000 && reg < 0x103000) || + (reg >= 0x180000 && reg < 0x181000) || + (reg >= 0x190000 && reg < 0x192000) || + (reg >= 0x1a8000 && reg < 0x1a9000) || + (reg >= 0x200000 && reg < 0x209000) || + (reg >= 0x280000 && reg < 0x281000) || + (reg >= 0x290000 && reg < 0x29a000) || + (reg >= 0x2a8000 && reg < 0x2aa000) || + (reg >= 0x300000 && reg < 0x30f000) || + (reg >= 0x380000 && reg < 0x382000) || + (reg >= 0x390000 && reg < 0x39e000) || + (reg >= 0x3a8000 && reg < 0x3b6000) || + (reg >= 0x400000 && reg < 0x403000) || + (reg >= 0x480000 && reg < 0x481000) || + (reg >= 0x490000 && reg < 0x492000) || + (reg >= 0x4a8000 && reg < 0x4a9000)) + return true; + else + return false; +} + +static bool wm5110_is_rev_d_adsp_memory(unsigned int reg) +{ + if ((reg >= 0x100000 && reg < 0x106000) || + (reg >= 0x180000 && reg < 0x182000) || + (reg >= 0x190000 && reg < 0x198000) || + (reg >= 0x1a8000 && reg < 0x1aa000) || + (reg >= 0x200000 && reg < 0x20f000) || + (reg >= 0x280000 && reg < 0x282000) || + (reg >= 0x290000 && reg < 0x29c000) || + (reg >= 0x2a6000 && reg < 0x2b4000) || + (reg >= 0x300000 && reg < 0x30f000) || + (reg >= 0x380000 && reg < 0x382000) || + (reg >= 0x390000 && reg < 0x3a2000) || + (reg >= 0x3a6000 && reg < 0x3b4000) || + (reg >= 0x400000 && reg < 0x406000) || + (reg >= 0x480000 && reg < 0x482000) || + (reg >= 0x490000 && reg < 0x498000) || + (reg >= 0x4a8000 && reg < 0x4aa000)) + return true; + else + return false; +} + +static bool wm5110_is_adsp_memory(struct device *dev, unsigned int reg) +{ + struct arizona *arizona = dev_get_drvdata(dev); + + switch (arizona->rev) { + case 0 ... 2: + return wm5110_is_rev_b_adsp_memory(reg); + default: + return wm5110_is_rev_d_adsp_memory(reg); + } +} + +static bool wm5110_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ARIZONA_SOFTWARE_RESET: + case ARIZONA_DEVICE_REVISION: + case ARIZONA_CTRL_IF_SPI_CFG_1: + case ARIZONA_CTRL_IF_I2C1_CFG_1: + case ARIZONA_CTRL_IF_I2C2_CFG_1: + case ARIZONA_CTRL_IF_I2C1_CFG_2: + case ARIZONA_CTRL_IF_I2C2_CFG_2: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_TONE_GENERATOR_1: + case ARIZONA_TONE_GENERATOR_2: + case ARIZONA_TONE_GENERATOR_3: + case ARIZONA_TONE_GENERATOR_4: + case ARIZONA_TONE_GENERATOR_5: + case ARIZONA_PWM_DRIVE_1: + case ARIZONA_PWM_DRIVE_2: + case ARIZONA_PWM_DRIVE_3: + case ARIZONA_WAKE_CONTROL: + case ARIZONA_SEQUENCE_CONTROL: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_1: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_2: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_3: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_4: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_1: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_2: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_3: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_4: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_5: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_6: + case ARIZONA_COMFORT_NOISE_GENERATOR: + case ARIZONA_HAPTICS_CONTROL_1: + case ARIZONA_HAPTICS_CONTROL_2: + case ARIZONA_HAPTICS_PHASE_1_INTENSITY: + case ARIZONA_HAPTICS_PHASE_1_DURATION: + case ARIZONA_HAPTICS_PHASE_2_INTENSITY: + case ARIZONA_HAPTICS_PHASE_2_DURATION: + case ARIZONA_HAPTICS_PHASE_3_INTENSITY: + case ARIZONA_HAPTICS_PHASE_3_DURATION: + case ARIZONA_HAPTICS_STATUS: + case ARIZONA_CLOCK_32K_1: + case ARIZONA_SYSTEM_CLOCK_1: + case ARIZONA_SAMPLE_RATE_1: + case ARIZONA_SAMPLE_RATE_2: + case ARIZONA_SAMPLE_RATE_3: + case ARIZONA_SAMPLE_RATE_1_STATUS: + case ARIZONA_SAMPLE_RATE_2_STATUS: + case ARIZONA_SAMPLE_RATE_3_STATUS: + case ARIZONA_ASYNC_CLOCK_1: + case ARIZONA_ASYNC_SAMPLE_RATE_1: + case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: + case ARIZONA_OUTPUT_SYSTEM_CLOCK: + case ARIZONA_OUTPUT_ASYNC_CLOCK: + case ARIZONA_RATE_ESTIMATOR_1: + case ARIZONA_RATE_ESTIMATOR_2: + case ARIZONA_RATE_ESTIMATOR_3: + case ARIZONA_RATE_ESTIMATOR_4: + case ARIZONA_RATE_ESTIMATOR_5: + case ARIZONA_FLL1_CONTROL_1: + case ARIZONA_FLL1_CONTROL_2: + case ARIZONA_FLL1_CONTROL_3: + case ARIZONA_FLL1_CONTROL_4: + case ARIZONA_FLL1_CONTROL_5: + case ARIZONA_FLL1_CONTROL_6: + case ARIZONA_FLL1_CONTROL_7: + case ARIZONA_FLL1_LOOP_FILTER_TEST_1: + case ARIZONA_FLL1_NCO_TEST_0: + case ARIZONA_FLL1_SYNCHRONISER_1: + case ARIZONA_FLL1_SYNCHRONISER_2: + case ARIZONA_FLL1_SYNCHRONISER_3: + case ARIZONA_FLL1_SYNCHRONISER_4: + case ARIZONA_FLL1_SYNCHRONISER_5: + case ARIZONA_FLL1_SYNCHRONISER_6: + case ARIZONA_FLL1_SYNCHRONISER_7: + case ARIZONA_FLL1_SPREAD_SPECTRUM: + case ARIZONA_FLL1_GPIO_CLOCK: + case ARIZONA_FLL2_CONTROL_1: + case ARIZONA_FLL2_CONTROL_2: + case ARIZONA_FLL2_CONTROL_3: + case ARIZONA_FLL2_CONTROL_4: + case ARIZONA_FLL2_CONTROL_5: + case ARIZONA_FLL2_CONTROL_6: + case ARIZONA_FLL2_CONTROL_7: + case ARIZONA_FLL2_LOOP_FILTER_TEST_1: + case ARIZONA_FLL2_NCO_TEST_0: + case ARIZONA_FLL2_SYNCHRONISER_1: + case ARIZONA_FLL2_SYNCHRONISER_2: + case ARIZONA_FLL2_SYNCHRONISER_3: + case ARIZONA_FLL2_SYNCHRONISER_4: + case ARIZONA_FLL2_SYNCHRONISER_5: + case ARIZONA_FLL2_SYNCHRONISER_6: + case ARIZONA_FLL2_SYNCHRONISER_7: + case ARIZONA_FLL2_SPREAD_SPECTRUM: + case ARIZONA_FLL2_GPIO_CLOCK: + case ARIZONA_MIC_CHARGE_PUMP_1: + case ARIZONA_LDO1_CONTROL_1: + case ARIZONA_LDO2_CONTROL_1: + case ARIZONA_MIC_BIAS_CTRL_1: + case ARIZONA_MIC_BIAS_CTRL_2: + case ARIZONA_MIC_BIAS_CTRL_3: + case ARIZONA_HP_CTRL_1L: + case ARIZONA_HP_CTRL_1R: + case ARIZONA_ACCESSORY_DETECT_MODE_1: + case ARIZONA_HEADPHONE_DETECT_1: + case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_MICD_CLAMP_CONTROL: + case ARIZONA_MIC_DETECT_1: + case ARIZONA_MIC_DETECT_2: + case ARIZONA_MIC_DETECT_3: + case ARIZONA_MIC_DETECT_LEVEL_1: + case ARIZONA_MIC_DETECT_LEVEL_2: + case ARIZONA_MIC_DETECT_LEVEL_3: + case ARIZONA_MIC_DETECT_LEVEL_4: + case ARIZONA_MIC_NOISE_MIX_CONTROL_1: + case ARIZONA_ISOLATION_CONTROL: + case ARIZONA_JACK_DETECT_ANALOGUE: + case ARIZONA_INPUT_ENABLES: + case ARIZONA_INPUT_ENABLES_STATUS: + case ARIZONA_INPUT_RATE: + case ARIZONA_INPUT_VOLUME_RAMP: + case ARIZONA_HPF_CONTROL: + case ARIZONA_IN1L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_1L: + case ARIZONA_DMIC1L_CONTROL: + case ARIZONA_IN1R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_1R: + case ARIZONA_DMIC1R_CONTROL: + case ARIZONA_IN2L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_2L: + case ARIZONA_DMIC2L_CONTROL: + case ARIZONA_IN2R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_2R: + case ARIZONA_DMIC2R_CONTROL: + case ARIZONA_IN3L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_3L: + case ARIZONA_DMIC3L_CONTROL: + case ARIZONA_IN3R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_3R: + case ARIZONA_DMIC3R_CONTROL: + case ARIZONA_IN4L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_4L: + case ARIZONA_DMIC4L_CONTROL: + case ARIZONA_IN4R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_4R: + case ARIZONA_DMIC4R_CONTROL: + case ARIZONA_OUTPUT_ENABLES_1: + case ARIZONA_OUTPUT_STATUS_1: + case ARIZONA_RAW_OUTPUT_STATUS_1: + case ARIZONA_OUTPUT_RATE_1: + case ARIZONA_OUTPUT_VOLUME_RAMP: + case ARIZONA_OUTPUT_PATH_CONFIG_1L: + case ARIZONA_DAC_DIGITAL_VOLUME_1L: + case ARIZONA_DAC_VOLUME_LIMIT_1L: + case ARIZONA_NOISE_GATE_SELECT_1L: + case ARIZONA_OUTPUT_PATH_CONFIG_1R: + case ARIZONA_DAC_DIGITAL_VOLUME_1R: + case ARIZONA_DAC_VOLUME_LIMIT_1R: + case ARIZONA_NOISE_GATE_SELECT_1R: + case ARIZONA_OUTPUT_PATH_CONFIG_2L: + case ARIZONA_DAC_DIGITAL_VOLUME_2L: + case ARIZONA_DAC_VOLUME_LIMIT_2L: + case ARIZONA_NOISE_GATE_SELECT_2L: + case ARIZONA_OUTPUT_PATH_CONFIG_2R: + case ARIZONA_DAC_DIGITAL_VOLUME_2R: + case ARIZONA_DAC_VOLUME_LIMIT_2R: + case ARIZONA_NOISE_GATE_SELECT_2R: + case ARIZONA_OUTPUT_PATH_CONFIG_3L: + case ARIZONA_DAC_DIGITAL_VOLUME_3L: + case ARIZONA_DAC_VOLUME_LIMIT_3L: + case ARIZONA_NOISE_GATE_SELECT_3L: + case ARIZONA_OUTPUT_PATH_CONFIG_3R: + case ARIZONA_DAC_DIGITAL_VOLUME_3R: + case ARIZONA_DAC_VOLUME_LIMIT_3R: + case ARIZONA_NOISE_GATE_SELECT_3R: + case ARIZONA_OUTPUT_PATH_CONFIG_4L: + case ARIZONA_DAC_DIGITAL_VOLUME_4L: + case ARIZONA_OUT_VOLUME_4L: + case ARIZONA_NOISE_GATE_SELECT_4L: + case ARIZONA_OUTPUT_PATH_CONFIG_4R: + case ARIZONA_DAC_DIGITAL_VOLUME_4R: + case ARIZONA_OUT_VOLUME_4R: + case ARIZONA_NOISE_GATE_SELECT_4R: + case ARIZONA_OUTPUT_PATH_CONFIG_5L: + case ARIZONA_DAC_DIGITAL_VOLUME_5L: + case ARIZONA_DAC_VOLUME_LIMIT_5L: + case ARIZONA_NOISE_GATE_SELECT_5L: + case ARIZONA_OUTPUT_PATH_CONFIG_5R: + case ARIZONA_DAC_DIGITAL_VOLUME_5R: + case ARIZONA_DAC_VOLUME_LIMIT_5R: + case ARIZONA_NOISE_GATE_SELECT_5R: + case ARIZONA_OUTPUT_PATH_CONFIG_6L: + case ARIZONA_DAC_DIGITAL_VOLUME_6L: + case ARIZONA_DAC_VOLUME_LIMIT_6L: + case ARIZONA_NOISE_GATE_SELECT_6L: + case ARIZONA_OUTPUT_PATH_CONFIG_6R: + case ARIZONA_DAC_DIGITAL_VOLUME_6R: + case ARIZONA_DAC_VOLUME_LIMIT_6R: + case ARIZONA_NOISE_GATE_SELECT_6R: + case ARIZONA_DRE_ENABLE: + case ARIZONA_DAC_AEC_CONTROL_1: + case ARIZONA_NOISE_GATE_CONTROL: + case ARIZONA_PDM_SPK1_CTRL_1: + case ARIZONA_PDM_SPK1_CTRL_2: + case ARIZONA_PDM_SPK2_CTRL_1: + case ARIZONA_PDM_SPK2_CTRL_2: + case ARIZONA_HP1_SHORT_CIRCUIT_CTRL: + case ARIZONA_HP2_SHORT_CIRCUIT_CTRL: + case ARIZONA_HP3_SHORT_CIRCUIT_CTRL: + case ARIZONA_AIF1_BCLK_CTRL: + case ARIZONA_AIF1_TX_PIN_CTRL: + case ARIZONA_AIF1_RX_PIN_CTRL: + case ARIZONA_AIF1_RATE_CTRL: + case ARIZONA_AIF1_FORMAT: + case ARIZONA_AIF1_TX_BCLK_RATE: + case ARIZONA_AIF1_RX_BCLK_RATE: + case ARIZONA_AIF1_FRAME_CTRL_1: + case ARIZONA_AIF1_FRAME_CTRL_2: + case ARIZONA_AIF1_FRAME_CTRL_3: + case ARIZONA_AIF1_FRAME_CTRL_4: + case ARIZONA_AIF1_FRAME_CTRL_5: + case ARIZONA_AIF1_FRAME_CTRL_6: + case ARIZONA_AIF1_FRAME_CTRL_7: + case ARIZONA_AIF1_FRAME_CTRL_8: + case ARIZONA_AIF1_FRAME_CTRL_9: + case ARIZONA_AIF1_FRAME_CTRL_10: + case ARIZONA_AIF1_FRAME_CTRL_11: + case ARIZONA_AIF1_FRAME_CTRL_12: + case ARIZONA_AIF1_FRAME_CTRL_13: + case ARIZONA_AIF1_FRAME_CTRL_14: + case ARIZONA_AIF1_FRAME_CTRL_15: + case ARIZONA_AIF1_FRAME_CTRL_16: + case ARIZONA_AIF1_FRAME_CTRL_17: + case ARIZONA_AIF1_FRAME_CTRL_18: + case ARIZONA_AIF1_TX_ENABLES: + case ARIZONA_AIF1_RX_ENABLES: + case ARIZONA_AIF2_BCLK_CTRL: + case ARIZONA_AIF2_TX_PIN_CTRL: + case ARIZONA_AIF2_RX_PIN_CTRL: + case ARIZONA_AIF2_RATE_CTRL: + case ARIZONA_AIF2_FORMAT: + case ARIZONA_AIF2_TX_BCLK_RATE: + case ARIZONA_AIF2_RX_BCLK_RATE: + case ARIZONA_AIF2_FRAME_CTRL_1: + case ARIZONA_AIF2_FRAME_CTRL_2: + case ARIZONA_AIF2_FRAME_CTRL_3: + case ARIZONA_AIF2_FRAME_CTRL_4: + case ARIZONA_AIF2_FRAME_CTRL_5: + case ARIZONA_AIF2_FRAME_CTRL_6: + case ARIZONA_AIF2_FRAME_CTRL_7: + case ARIZONA_AIF2_FRAME_CTRL_8: + case ARIZONA_AIF2_FRAME_CTRL_11: + case ARIZONA_AIF2_FRAME_CTRL_12: + case ARIZONA_AIF2_FRAME_CTRL_13: + case ARIZONA_AIF2_FRAME_CTRL_14: + case ARIZONA_AIF2_FRAME_CTRL_15: + case ARIZONA_AIF2_FRAME_CTRL_16: + case ARIZONA_AIF2_TX_ENABLES: + case ARIZONA_AIF2_RX_ENABLES: + case ARIZONA_AIF3_BCLK_CTRL: + case ARIZONA_AIF3_TX_PIN_CTRL: + case ARIZONA_AIF3_RX_PIN_CTRL: + case ARIZONA_AIF3_RATE_CTRL: + case ARIZONA_AIF3_FORMAT: + case ARIZONA_AIF3_TX_BCLK_RATE: + case ARIZONA_AIF3_RX_BCLK_RATE: + case ARIZONA_AIF3_FRAME_CTRL_1: + case ARIZONA_AIF3_FRAME_CTRL_2: + case ARIZONA_AIF3_FRAME_CTRL_3: + case ARIZONA_AIF3_FRAME_CTRL_4: + case ARIZONA_AIF3_FRAME_CTRL_11: + case ARIZONA_AIF3_FRAME_CTRL_12: + case ARIZONA_AIF3_TX_ENABLES: + case ARIZONA_AIF3_RX_ENABLES: + case ARIZONA_SLIMBUS_FRAMER_REF_GEAR: + case ARIZONA_SLIMBUS_RATES_1: + case ARIZONA_SLIMBUS_RATES_2: + case ARIZONA_SLIMBUS_RATES_3: + case ARIZONA_SLIMBUS_RATES_4: + case ARIZONA_SLIMBUS_RATES_5: + case ARIZONA_SLIMBUS_RATES_6: + case ARIZONA_SLIMBUS_RATES_7: + case ARIZONA_SLIMBUS_RATES_8: + case ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE: + case ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE: + case ARIZONA_SLIMBUS_RX_PORT_STATUS: + case ARIZONA_SLIMBUS_TX_PORT_STATUS: + case ARIZONA_PWM1MIX_INPUT_1_SOURCE: + case ARIZONA_PWM1MIX_INPUT_1_VOLUME: + case ARIZONA_PWM1MIX_INPUT_2_SOURCE: + case ARIZONA_PWM1MIX_INPUT_2_VOLUME: + case ARIZONA_PWM1MIX_INPUT_3_SOURCE: + case ARIZONA_PWM1MIX_INPUT_3_VOLUME: + case ARIZONA_PWM1MIX_INPUT_4_SOURCE: + case ARIZONA_PWM1MIX_INPUT_4_VOLUME: + case ARIZONA_PWM2MIX_INPUT_1_SOURCE: + case ARIZONA_PWM2MIX_INPUT_1_VOLUME: + case ARIZONA_PWM2MIX_INPUT_2_SOURCE: + case ARIZONA_PWM2MIX_INPUT_2_VOLUME: + case ARIZONA_PWM2MIX_INPUT_3_SOURCE: + case ARIZONA_PWM2MIX_INPUT_3_VOLUME: + case ARIZONA_PWM2MIX_INPUT_4_SOURCE: + case ARIZONA_PWM2MIX_INPUT_4_VOLUME: + case ARIZONA_MICMIX_INPUT_1_SOURCE: + case ARIZONA_MICMIX_INPUT_1_VOLUME: + case ARIZONA_MICMIX_INPUT_2_SOURCE: + case ARIZONA_MICMIX_INPUT_2_VOLUME: + case ARIZONA_MICMIX_INPUT_3_SOURCE: + case ARIZONA_MICMIX_INPUT_3_VOLUME: + case ARIZONA_MICMIX_INPUT_4_SOURCE: + case ARIZONA_MICMIX_INPUT_4_VOLUME: + case ARIZONA_NOISEMIX_INPUT_1_SOURCE: + case ARIZONA_NOISEMIX_INPUT_1_VOLUME: + case ARIZONA_NOISEMIX_INPUT_2_SOURCE: + case ARIZONA_NOISEMIX_INPUT_2_VOLUME: + case ARIZONA_NOISEMIX_INPUT_3_SOURCE: + case ARIZONA_NOISEMIX_INPUT_3_VOLUME: + case ARIZONA_NOISEMIX_INPUT_4_SOURCE: + case ARIZONA_NOISEMIX_INPUT_4_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT2LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT2LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT2RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT2RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT3RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT3RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT3RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT3RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT3RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT3RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT3RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT3RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT4RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT4RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT6LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT6LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT6LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT6LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT6LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT6LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT6LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT6LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT6RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT6RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT6RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT6RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT6RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT6RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT6RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT6RMIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX3MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX3MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX3MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX3MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX3MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX3MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX3MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX4MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX4MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX4MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX4MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX4MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX4MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX4MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX5MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX5MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX5MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX5MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX5MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX5MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX5MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX6MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX6MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX6MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX6MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX6MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX6MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX6MIX_INPUT_4_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF3TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF3TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF3TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF3TX2MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_4_VOLUME: + case ARIZONA_EQ1MIX_INPUT_1_SOURCE: + case ARIZONA_EQ1MIX_INPUT_1_VOLUME: + case ARIZONA_EQ1MIX_INPUT_2_SOURCE: + case ARIZONA_EQ1MIX_INPUT_2_VOLUME: + case ARIZONA_EQ1MIX_INPUT_3_SOURCE: + case ARIZONA_EQ1MIX_INPUT_3_VOLUME: + case ARIZONA_EQ1MIX_INPUT_4_SOURCE: + case ARIZONA_EQ1MIX_INPUT_4_VOLUME: + case ARIZONA_EQ2MIX_INPUT_1_SOURCE: + case ARIZONA_EQ2MIX_INPUT_1_VOLUME: + case ARIZONA_EQ2MIX_INPUT_2_SOURCE: + case ARIZONA_EQ2MIX_INPUT_2_VOLUME: + case ARIZONA_EQ2MIX_INPUT_3_SOURCE: + case ARIZONA_EQ2MIX_INPUT_3_VOLUME: + case ARIZONA_EQ2MIX_INPUT_4_SOURCE: + case ARIZONA_EQ2MIX_INPUT_4_VOLUME: + case ARIZONA_EQ3MIX_INPUT_1_SOURCE: + case ARIZONA_EQ3MIX_INPUT_1_VOLUME: + case ARIZONA_EQ3MIX_INPUT_2_SOURCE: + case ARIZONA_EQ3MIX_INPUT_2_VOLUME: + case ARIZONA_EQ3MIX_INPUT_3_SOURCE: + case ARIZONA_EQ3MIX_INPUT_3_VOLUME: + case ARIZONA_EQ3MIX_INPUT_4_SOURCE: + case ARIZONA_EQ3MIX_INPUT_4_VOLUME: + case ARIZONA_EQ4MIX_INPUT_1_SOURCE: + case ARIZONA_EQ4MIX_INPUT_1_VOLUME: + case ARIZONA_EQ4MIX_INPUT_2_SOURCE: + case ARIZONA_EQ4MIX_INPUT_2_VOLUME: + case ARIZONA_EQ4MIX_INPUT_3_SOURCE: + case ARIZONA_EQ4MIX_INPUT_3_VOLUME: + case ARIZONA_EQ4MIX_INPUT_4_SOURCE: + case ARIZONA_EQ4MIX_INPUT_4_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_1_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_1_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_2_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_2_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_3_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_3_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_4_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_4_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_1_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_1_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_2_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_2_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_3_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_3_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_4_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_4_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_1_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_1_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_2_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_2_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_3_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_3_VOLUME: + case ARIZONA_DRC2LMIX_INPUT_4_SOURCE: + case ARIZONA_DRC2LMIX_INPUT_4_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_1_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_1_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_2_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_2_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_3_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_3_VOLUME: + case ARIZONA_DRC2RMIX_INPUT_4_SOURCE: + case ARIZONA_DRC2RMIX_INPUT_4_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_4_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_1_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_1_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_2_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_2_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_3_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_3_VOLUME: + case ARIZONA_DSP1LMIX_INPUT_4_SOURCE: + case ARIZONA_DSP1LMIX_INPUT_4_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_1_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_1_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_2_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_2_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_3_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_3_VOLUME: + case ARIZONA_DSP1RMIX_INPUT_4_SOURCE: + case ARIZONA_DSP1RMIX_INPUT_4_VOLUME: + case ARIZONA_DSP1AUX1MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX2MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX3MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX4MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX5MIX_INPUT_1_SOURCE: + case ARIZONA_DSP1AUX6MIX_INPUT_1_SOURCE: + case ARIZONA_DSP2LMIX_INPUT_1_SOURCE: + case ARIZONA_DSP2LMIX_INPUT_1_VOLUME: + case ARIZONA_DSP2LMIX_INPUT_2_SOURCE: + case ARIZONA_DSP2LMIX_INPUT_2_VOLUME: + case ARIZONA_DSP2LMIX_INPUT_3_SOURCE: + case ARIZONA_DSP2LMIX_INPUT_3_VOLUME: + case ARIZONA_DSP2LMIX_INPUT_4_SOURCE: + case ARIZONA_DSP2LMIX_INPUT_4_VOLUME: + case ARIZONA_DSP2RMIX_INPUT_1_SOURCE: + case ARIZONA_DSP2RMIX_INPUT_1_VOLUME: + case ARIZONA_DSP2RMIX_INPUT_2_SOURCE: + case ARIZONA_DSP2RMIX_INPUT_2_VOLUME: + case ARIZONA_DSP2RMIX_INPUT_3_SOURCE: + case ARIZONA_DSP2RMIX_INPUT_3_VOLUME: + case ARIZONA_DSP2RMIX_INPUT_4_SOURCE: + case ARIZONA_DSP2RMIX_INPUT_4_VOLUME: + case ARIZONA_DSP2AUX1MIX_INPUT_1_SOURCE: + case ARIZONA_DSP2AUX2MIX_INPUT_1_SOURCE: + case ARIZONA_DSP2AUX3MIX_INPUT_1_SOURCE: + case ARIZONA_DSP2AUX4MIX_INPUT_1_SOURCE: + case ARIZONA_DSP2AUX5MIX_INPUT_1_SOURCE: + case ARIZONA_DSP2AUX6MIX_INPUT_1_SOURCE: + case ARIZONA_DSP3LMIX_INPUT_1_SOURCE: + case ARIZONA_DSP3LMIX_INPUT_1_VOLUME: + case ARIZONA_DSP3LMIX_INPUT_2_SOURCE: + case ARIZONA_DSP3LMIX_INPUT_2_VOLUME: + case ARIZONA_DSP3LMIX_INPUT_3_SOURCE: + case ARIZONA_DSP3LMIX_INPUT_3_VOLUME: + case ARIZONA_DSP3LMIX_INPUT_4_SOURCE: + case ARIZONA_DSP3LMIX_INPUT_4_VOLUME: + case ARIZONA_DSP3RMIX_INPUT_1_SOURCE: + case ARIZONA_DSP3RMIX_INPUT_1_VOLUME: + case ARIZONA_DSP3RMIX_INPUT_2_SOURCE: + case ARIZONA_DSP3RMIX_INPUT_2_VOLUME: + case ARIZONA_DSP3RMIX_INPUT_3_SOURCE: + case ARIZONA_DSP3RMIX_INPUT_3_VOLUME: + case ARIZONA_DSP3RMIX_INPUT_4_SOURCE: + case ARIZONA_DSP3RMIX_INPUT_4_VOLUME: + case ARIZONA_DSP3AUX1MIX_INPUT_1_SOURCE: + case ARIZONA_DSP3AUX2MIX_INPUT_1_SOURCE: + case ARIZONA_DSP3AUX3MIX_INPUT_1_SOURCE: + case ARIZONA_DSP3AUX4MIX_INPUT_1_SOURCE: + case ARIZONA_DSP3AUX5MIX_INPUT_1_SOURCE: + case ARIZONA_DSP3AUX6MIX_INPUT_1_SOURCE: + case ARIZONA_DSP4LMIX_INPUT_1_SOURCE: + case ARIZONA_DSP4LMIX_INPUT_1_VOLUME: + case ARIZONA_DSP4LMIX_INPUT_2_SOURCE: + case ARIZONA_DSP4LMIX_INPUT_2_VOLUME: + case ARIZONA_DSP4LMIX_INPUT_3_SOURCE: + case ARIZONA_DSP4LMIX_INPUT_3_VOLUME: + case ARIZONA_DSP4LMIX_INPUT_4_SOURCE: + case ARIZONA_DSP4LMIX_INPUT_4_VOLUME: + case ARIZONA_DSP4RMIX_INPUT_1_SOURCE: + case ARIZONA_DSP4RMIX_INPUT_1_VOLUME: + case ARIZONA_DSP4RMIX_INPUT_2_SOURCE: + case ARIZONA_DSP4RMIX_INPUT_2_VOLUME: + case ARIZONA_DSP4RMIX_INPUT_3_SOURCE: + case ARIZONA_DSP4RMIX_INPUT_3_VOLUME: + case ARIZONA_DSP4RMIX_INPUT_4_SOURCE: + case ARIZONA_DSP4RMIX_INPUT_4_VOLUME: + case ARIZONA_DSP4AUX1MIX_INPUT_1_SOURCE: + case ARIZONA_DSP4AUX2MIX_INPUT_1_SOURCE: + case ARIZONA_DSP4AUX3MIX_INPUT_1_SOURCE: + case ARIZONA_DSP4AUX4MIX_INPUT_1_SOURCE: + case ARIZONA_DSP4AUX5MIX_INPUT_1_SOURCE: + case ARIZONA_DSP4AUX6MIX_INPUT_1_SOURCE: + case ARIZONA_ASRC1LMIX_INPUT_1_SOURCE: + case ARIZONA_ASRC1RMIX_INPUT_1_SOURCE: + case ARIZONA_ASRC2LMIX_INPUT_1_SOURCE: + case ARIZONA_ASRC2RMIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC3MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC4MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT3MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT4MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC3MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC4MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT3MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT4MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3DEC3MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3DEC4MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3INT2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3INT3MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC3INT4MIX_INPUT_1_SOURCE: + case ARIZONA_GPIO1_CTRL: + case ARIZONA_GPIO2_CTRL: + case ARIZONA_GPIO3_CTRL: + case ARIZONA_GPIO4_CTRL: + case ARIZONA_GPIO5_CTRL: + case ARIZONA_IRQ_CTRL_1: + case ARIZONA_GPIO_DEBOUNCE_CONFIG: + case ARIZONA_MISC_PAD_CTRL_1: + case ARIZONA_MISC_PAD_CTRL_2: + case ARIZONA_MISC_PAD_CTRL_3: + case ARIZONA_MISC_PAD_CTRL_4: + case ARIZONA_MISC_PAD_CTRL_5: + case ARIZONA_MISC_PAD_CTRL_6: + case ARIZONA_MISC_PAD_CTRL_7: + case ARIZONA_MISC_PAD_CTRL_8: + case ARIZONA_MISC_PAD_CTRL_9: + case ARIZONA_MISC_PAD_CTRL_10: + case ARIZONA_MISC_PAD_CTRL_11: + case ARIZONA_MISC_PAD_CTRL_12: + case ARIZONA_MISC_PAD_CTRL_13: + case ARIZONA_MISC_PAD_CTRL_14: + case ARIZONA_MISC_PAD_CTRL_15: + case ARIZONA_MISC_PAD_CTRL_16: + case ARIZONA_MISC_PAD_CTRL_17: + case ARIZONA_MISC_PAD_CTRL_18: + case ARIZONA_INTERRUPT_STATUS_1: + case ARIZONA_INTERRUPT_STATUS_2: + case ARIZONA_INTERRUPT_STATUS_3: + case ARIZONA_INTERRUPT_STATUS_4: + case ARIZONA_INTERRUPT_STATUS_5: + case ARIZONA_INTERRUPT_STATUS_6: + case ARIZONA_INTERRUPT_STATUS_1_MASK: + case ARIZONA_INTERRUPT_STATUS_2_MASK: + case ARIZONA_INTERRUPT_STATUS_3_MASK: + case ARIZONA_INTERRUPT_STATUS_4_MASK: + case ARIZONA_INTERRUPT_STATUS_5_MASK: + case ARIZONA_INTERRUPT_STATUS_6_MASK: + case ARIZONA_INTERRUPT_CONTROL: + case ARIZONA_IRQ2_STATUS_1: + case ARIZONA_IRQ2_STATUS_2: + case ARIZONA_IRQ2_STATUS_3: + case ARIZONA_IRQ2_STATUS_4: + case ARIZONA_IRQ2_STATUS_5: + case ARIZONA_IRQ2_STATUS_6: + case ARIZONA_IRQ2_STATUS_1_MASK: + case ARIZONA_IRQ2_STATUS_2_MASK: + case ARIZONA_IRQ2_STATUS_3_MASK: + case ARIZONA_IRQ2_STATUS_4_MASK: + case ARIZONA_IRQ2_STATUS_5_MASK: + case ARIZONA_IRQ2_STATUS_6_MASK: + case ARIZONA_IRQ2_CONTROL: + case ARIZONA_INTERRUPT_RAW_STATUS_2: + case ARIZONA_INTERRUPT_RAW_STATUS_3: + case ARIZONA_INTERRUPT_RAW_STATUS_4: + case ARIZONA_INTERRUPT_RAW_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_7: + case ARIZONA_INTERRUPT_RAW_STATUS_8: + case ARIZONA_INTERRUPT_RAW_STATUS_9: + case ARIZONA_IRQ_PIN_STATUS: + case ARIZONA_AOD_WKUP_AND_TRIG: + case ARIZONA_AOD_IRQ1: + case ARIZONA_AOD_IRQ2: + case ARIZONA_AOD_IRQ_MASK_IRQ1: + case ARIZONA_AOD_IRQ_MASK_IRQ2: + case ARIZONA_AOD_IRQ_RAW_STATUS: + case ARIZONA_JACK_DETECT_DEBOUNCE: + case ARIZONA_FX_CTRL1: + case ARIZONA_FX_CTRL2: + case ARIZONA_EQ1_1: + case ARIZONA_EQ1_2: + case ARIZONA_EQ1_3: + case ARIZONA_EQ1_4: + case ARIZONA_EQ1_5: + case ARIZONA_EQ1_6: + case ARIZONA_EQ1_7: + case ARIZONA_EQ1_8: + case ARIZONA_EQ1_9: + case ARIZONA_EQ1_10: + case ARIZONA_EQ1_11: + case ARIZONA_EQ1_12: + case ARIZONA_EQ1_13: + case ARIZONA_EQ1_14: + case ARIZONA_EQ1_15: + case ARIZONA_EQ1_16: + case ARIZONA_EQ1_17: + case ARIZONA_EQ1_18: + case ARIZONA_EQ1_19: + case ARIZONA_EQ1_20: + case ARIZONA_EQ1_21: + case ARIZONA_EQ2_1: + case ARIZONA_EQ2_2: + case ARIZONA_EQ2_3: + case ARIZONA_EQ2_4: + case ARIZONA_EQ2_5: + case ARIZONA_EQ2_6: + case ARIZONA_EQ2_7: + case ARIZONA_EQ2_8: + case ARIZONA_EQ2_9: + case ARIZONA_EQ2_10: + case ARIZONA_EQ2_11: + case ARIZONA_EQ2_12: + case ARIZONA_EQ2_13: + case ARIZONA_EQ2_14: + case ARIZONA_EQ2_15: + case ARIZONA_EQ2_16: + case ARIZONA_EQ2_17: + case ARIZONA_EQ2_18: + case ARIZONA_EQ2_19: + case ARIZONA_EQ2_20: + case ARIZONA_EQ2_21: + case ARIZONA_EQ3_1: + case ARIZONA_EQ3_2: + case ARIZONA_EQ3_3: + case ARIZONA_EQ3_4: + case ARIZONA_EQ3_5: + case ARIZONA_EQ3_6: + case ARIZONA_EQ3_7: + case ARIZONA_EQ3_8: + case ARIZONA_EQ3_9: + case ARIZONA_EQ3_10: + case ARIZONA_EQ3_11: + case ARIZONA_EQ3_12: + case ARIZONA_EQ3_13: + case ARIZONA_EQ3_14: + case ARIZONA_EQ3_15: + case ARIZONA_EQ3_16: + case ARIZONA_EQ3_17: + case ARIZONA_EQ3_18: + case ARIZONA_EQ3_19: + case ARIZONA_EQ3_20: + case ARIZONA_EQ3_21: + case ARIZONA_EQ4_1: + case ARIZONA_EQ4_2: + case ARIZONA_EQ4_3: + case ARIZONA_EQ4_4: + case ARIZONA_EQ4_5: + case ARIZONA_EQ4_6: + case ARIZONA_EQ4_7: + case ARIZONA_EQ4_8: + case ARIZONA_EQ4_9: + case ARIZONA_EQ4_10: + case ARIZONA_EQ4_11: + case ARIZONA_EQ4_12: + case ARIZONA_EQ4_13: + case ARIZONA_EQ4_14: + case ARIZONA_EQ4_15: + case ARIZONA_EQ4_16: + case ARIZONA_EQ4_17: + case ARIZONA_EQ4_18: + case ARIZONA_EQ4_19: + case ARIZONA_EQ4_20: + case ARIZONA_EQ4_21: + case ARIZONA_DRC1_CTRL1: + case ARIZONA_DRC1_CTRL2: + case ARIZONA_DRC1_CTRL3: + case ARIZONA_DRC1_CTRL4: + case ARIZONA_DRC1_CTRL5: + case ARIZONA_DRC2_CTRL1: + case ARIZONA_DRC2_CTRL2: + case ARIZONA_DRC2_CTRL3: + case ARIZONA_DRC2_CTRL4: + case ARIZONA_DRC2_CTRL5: + case ARIZONA_HPLPF1_1: + case ARIZONA_HPLPF1_2: + case ARIZONA_HPLPF2_1: + case ARIZONA_HPLPF2_2: + case ARIZONA_HPLPF3_1: + case ARIZONA_HPLPF3_2: + case ARIZONA_HPLPF4_1: + case ARIZONA_HPLPF4_2: + case ARIZONA_ASRC_ENABLE: + case ARIZONA_ASRC_STATUS: + case ARIZONA_ASRC_RATE1: + case ARIZONA_ISRC_1_CTRL_1: + case ARIZONA_ISRC_1_CTRL_2: + case ARIZONA_ISRC_1_CTRL_3: + case ARIZONA_ISRC_2_CTRL_1: + case ARIZONA_ISRC_2_CTRL_2: + case ARIZONA_ISRC_2_CTRL_3: + case ARIZONA_ISRC_3_CTRL_1: + case ARIZONA_ISRC_3_CTRL_2: + case ARIZONA_ISRC_3_CTRL_3: + case ARIZONA_CLOCK_CONTROL: + case ARIZONA_ANC_SRC: + case ARIZONA_DSP_STATUS: + case ARIZONA_DSP1_CONTROL_1: + case ARIZONA_DSP1_CLOCKING_1: + case ARIZONA_DSP1_STATUS_1: + case ARIZONA_DSP1_STATUS_2: + case ARIZONA_DSP1_STATUS_3: + case ARIZONA_DSP1_STATUS_4: + case ARIZONA_DSP1_WDMA_BUFFER_1: + case ARIZONA_DSP1_WDMA_BUFFER_2: + case ARIZONA_DSP1_WDMA_BUFFER_3: + case ARIZONA_DSP1_WDMA_BUFFER_4: + case ARIZONA_DSP1_WDMA_BUFFER_5: + case ARIZONA_DSP1_WDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_BUFFER_7: + case ARIZONA_DSP1_WDMA_BUFFER_8: + case ARIZONA_DSP1_RDMA_BUFFER_1: + case ARIZONA_DSP1_RDMA_BUFFER_2: + case ARIZONA_DSP1_RDMA_BUFFER_3: + case ARIZONA_DSP1_RDMA_BUFFER_4: + case ARIZONA_DSP1_RDMA_BUFFER_5: + case ARIZONA_DSP1_RDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_CONFIG_1: + case ARIZONA_DSP1_WDMA_CONFIG_2: + case ARIZONA_DSP1_WDMA_OFFSET_1: + case ARIZONA_DSP1_RDMA_CONFIG_1: + case ARIZONA_DSP1_RDMA_OFFSET_1: + case ARIZONA_DSP1_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP1_SCRATCH_0: + case ARIZONA_DSP1_SCRATCH_1: + case ARIZONA_DSP1_SCRATCH_2: + case ARIZONA_DSP1_SCRATCH_3: + case ARIZONA_DSP2_CONTROL_1: + case ARIZONA_DSP2_CLOCKING_1: + case ARIZONA_DSP2_STATUS_1: + case ARIZONA_DSP2_STATUS_2: + case ARIZONA_DSP2_STATUS_3: + case ARIZONA_DSP2_STATUS_4: + case ARIZONA_DSP2_WDMA_BUFFER_1: + case ARIZONA_DSP2_WDMA_BUFFER_2: + case ARIZONA_DSP2_WDMA_BUFFER_3: + case ARIZONA_DSP2_WDMA_BUFFER_4: + case ARIZONA_DSP2_WDMA_BUFFER_5: + case ARIZONA_DSP2_WDMA_BUFFER_6: + case ARIZONA_DSP2_WDMA_BUFFER_7: + case ARIZONA_DSP2_WDMA_BUFFER_8: + case ARIZONA_DSP2_RDMA_BUFFER_1: + case ARIZONA_DSP2_RDMA_BUFFER_2: + case ARIZONA_DSP2_RDMA_BUFFER_3: + case ARIZONA_DSP2_RDMA_BUFFER_4: + case ARIZONA_DSP2_RDMA_BUFFER_5: + case ARIZONA_DSP2_RDMA_BUFFER_6: + case ARIZONA_DSP2_WDMA_CONFIG_1: + case ARIZONA_DSP2_WDMA_CONFIG_2: + case ARIZONA_DSP2_WDMA_OFFSET_1: + case ARIZONA_DSP2_RDMA_CONFIG_1: + case ARIZONA_DSP2_RDMA_OFFSET_1: + case ARIZONA_DSP2_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP2_SCRATCH_0: + case ARIZONA_DSP2_SCRATCH_1: + case ARIZONA_DSP2_SCRATCH_2: + case ARIZONA_DSP2_SCRATCH_3: + case ARIZONA_DSP3_CONTROL_1: + case ARIZONA_DSP3_CLOCKING_1: + case ARIZONA_DSP3_STATUS_1: + case ARIZONA_DSP3_STATUS_2: + case ARIZONA_DSP3_STATUS_3: + case ARIZONA_DSP3_STATUS_4: + case ARIZONA_DSP3_WDMA_BUFFER_1: + case ARIZONA_DSP3_WDMA_BUFFER_2: + case ARIZONA_DSP3_WDMA_BUFFER_3: + case ARIZONA_DSP3_WDMA_BUFFER_4: + case ARIZONA_DSP3_WDMA_BUFFER_5: + case ARIZONA_DSP3_WDMA_BUFFER_6: + case ARIZONA_DSP3_WDMA_BUFFER_7: + case ARIZONA_DSP3_WDMA_BUFFER_8: + case ARIZONA_DSP3_RDMA_BUFFER_1: + case ARIZONA_DSP3_RDMA_BUFFER_2: + case ARIZONA_DSP3_RDMA_BUFFER_3: + case ARIZONA_DSP3_RDMA_BUFFER_4: + case ARIZONA_DSP3_RDMA_BUFFER_5: + case ARIZONA_DSP3_RDMA_BUFFER_6: + case ARIZONA_DSP3_WDMA_CONFIG_1: + case ARIZONA_DSP3_WDMA_CONFIG_2: + case ARIZONA_DSP3_WDMA_OFFSET_1: + case ARIZONA_DSP3_RDMA_CONFIG_1: + case ARIZONA_DSP3_RDMA_OFFSET_1: + case ARIZONA_DSP3_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP3_SCRATCH_0: + case ARIZONA_DSP3_SCRATCH_1: + case ARIZONA_DSP3_SCRATCH_2: + case ARIZONA_DSP3_SCRATCH_3: + case ARIZONA_DSP4_CONTROL_1: + case ARIZONA_DSP4_CLOCKING_1: + case ARIZONA_DSP4_STATUS_1: + case ARIZONA_DSP4_STATUS_2: + case ARIZONA_DSP4_STATUS_3: + case ARIZONA_DSP4_STATUS_4: + case ARIZONA_DSP4_WDMA_BUFFER_1: + case ARIZONA_DSP4_WDMA_BUFFER_2: + case ARIZONA_DSP4_WDMA_BUFFER_3: + case ARIZONA_DSP4_WDMA_BUFFER_4: + case ARIZONA_DSP4_WDMA_BUFFER_5: + case ARIZONA_DSP4_WDMA_BUFFER_6: + case ARIZONA_DSP4_WDMA_BUFFER_7: + case ARIZONA_DSP4_WDMA_BUFFER_8: + case ARIZONA_DSP4_RDMA_BUFFER_1: + case ARIZONA_DSP4_RDMA_BUFFER_2: + case ARIZONA_DSP4_RDMA_BUFFER_3: + case ARIZONA_DSP4_RDMA_BUFFER_4: + case ARIZONA_DSP4_RDMA_BUFFER_5: + case ARIZONA_DSP4_RDMA_BUFFER_6: + case ARIZONA_DSP4_WDMA_CONFIG_1: + case ARIZONA_DSP4_WDMA_CONFIG_2: + case ARIZONA_DSP4_WDMA_OFFSET_1: + case ARIZONA_DSP4_RDMA_CONFIG_1: + case ARIZONA_DSP4_RDMA_OFFSET_1: + case ARIZONA_DSP4_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP4_SCRATCH_0: + case ARIZONA_DSP4_SCRATCH_1: + case ARIZONA_DSP4_SCRATCH_2: + case ARIZONA_DSP4_SCRATCH_3: + return true; + default: + return wm5110_is_adsp_memory(dev, reg); + } +} + +static bool wm5110_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ARIZONA_SOFTWARE_RESET: + case ARIZONA_DEVICE_REVISION: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_HAPTICS_STATUS: + case ARIZONA_SAMPLE_RATE_1_STATUS: + case ARIZONA_SAMPLE_RATE_2_STATUS: + case ARIZONA_SAMPLE_RATE_3_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: + case ARIZONA_MIC_DETECT_3: + case ARIZONA_HP_CTRL_1L: + case ARIZONA_HP_CTRL_1R: + case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_INPUT_ENABLES_STATUS: + case ARIZONA_OUTPUT_STATUS_1: + case ARIZONA_RAW_OUTPUT_STATUS_1: + case ARIZONA_SLIMBUS_RX_PORT_STATUS: + case ARIZONA_SLIMBUS_TX_PORT_STATUS: + case ARIZONA_INTERRUPT_STATUS_1: + case ARIZONA_INTERRUPT_STATUS_2: + case ARIZONA_INTERRUPT_STATUS_3: + case ARIZONA_INTERRUPT_STATUS_4: + case ARIZONA_INTERRUPT_STATUS_5: + case ARIZONA_INTERRUPT_STATUS_6: + case ARIZONA_IRQ2_STATUS_1: + case ARIZONA_IRQ2_STATUS_2: + case ARIZONA_IRQ2_STATUS_3: + case ARIZONA_IRQ2_STATUS_4: + case ARIZONA_IRQ2_STATUS_5: + case ARIZONA_IRQ2_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_2: + case ARIZONA_INTERRUPT_RAW_STATUS_3: + case ARIZONA_INTERRUPT_RAW_STATUS_4: + case ARIZONA_INTERRUPT_RAW_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_7: + case ARIZONA_INTERRUPT_RAW_STATUS_8: + case ARIZONA_INTERRUPT_RAW_STATUS_9: + case ARIZONA_IRQ_PIN_STATUS: + case ARIZONA_AOD_WKUP_AND_TRIG: + case ARIZONA_AOD_IRQ1: + case ARIZONA_AOD_IRQ2: + case ARIZONA_AOD_IRQ_RAW_STATUS: + case ARIZONA_FX_CTRL2: + case ARIZONA_ASRC_STATUS: + case ARIZONA_DSP_STATUS: + case ARIZONA_DSP1_STATUS_1: + case ARIZONA_DSP1_STATUS_2: + case ARIZONA_DSP1_STATUS_3: + case ARIZONA_DSP1_STATUS_4: + case ARIZONA_DSP1_WDMA_BUFFER_1: + case ARIZONA_DSP1_WDMA_BUFFER_2: + case ARIZONA_DSP1_WDMA_BUFFER_3: + case ARIZONA_DSP1_WDMA_BUFFER_4: + case ARIZONA_DSP1_WDMA_BUFFER_5: + case ARIZONA_DSP1_WDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_BUFFER_7: + case ARIZONA_DSP1_WDMA_BUFFER_8: + case ARIZONA_DSP1_RDMA_BUFFER_1: + case ARIZONA_DSP1_RDMA_BUFFER_2: + case ARIZONA_DSP1_RDMA_BUFFER_3: + case ARIZONA_DSP1_RDMA_BUFFER_4: + case ARIZONA_DSP1_RDMA_BUFFER_5: + case ARIZONA_DSP1_RDMA_BUFFER_6: + case ARIZONA_DSP1_WDMA_CONFIG_1: + case ARIZONA_DSP1_WDMA_CONFIG_2: + case ARIZONA_DSP1_WDMA_OFFSET_1: + case ARIZONA_DSP1_RDMA_CONFIG_1: + case ARIZONA_DSP1_RDMA_OFFSET_1: + case ARIZONA_DSP1_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP1_SCRATCH_0: + case ARIZONA_DSP1_SCRATCH_1: + case ARIZONA_DSP1_SCRATCH_2: + case ARIZONA_DSP1_SCRATCH_3: + case ARIZONA_DSP1_CLOCKING_1: + case ARIZONA_DSP2_STATUS_1: + case ARIZONA_DSP2_STATUS_2: + case ARIZONA_DSP2_STATUS_3: + case ARIZONA_DSP2_STATUS_4: + case ARIZONA_DSP2_WDMA_BUFFER_1: + case ARIZONA_DSP2_WDMA_BUFFER_2: + case ARIZONA_DSP2_WDMA_BUFFER_3: + case ARIZONA_DSP2_WDMA_BUFFER_4: + case ARIZONA_DSP2_WDMA_BUFFER_5: + case ARIZONA_DSP2_WDMA_BUFFER_6: + case ARIZONA_DSP2_WDMA_BUFFER_7: + case ARIZONA_DSP2_WDMA_BUFFER_8: + case ARIZONA_DSP2_RDMA_BUFFER_1: + case ARIZONA_DSP2_RDMA_BUFFER_2: + case ARIZONA_DSP2_RDMA_BUFFER_3: + case ARIZONA_DSP2_RDMA_BUFFER_4: + case ARIZONA_DSP2_RDMA_BUFFER_5: + case ARIZONA_DSP2_RDMA_BUFFER_6: + case ARIZONA_DSP2_WDMA_CONFIG_1: + case ARIZONA_DSP2_WDMA_CONFIG_2: + case ARIZONA_DSP2_WDMA_OFFSET_1: + case ARIZONA_DSP2_RDMA_CONFIG_1: + case ARIZONA_DSP2_RDMA_OFFSET_1: + case ARIZONA_DSP2_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP2_SCRATCH_0: + case ARIZONA_DSP2_SCRATCH_1: + case ARIZONA_DSP2_SCRATCH_2: + case ARIZONA_DSP2_SCRATCH_3: + case ARIZONA_DSP2_CLOCKING_1: + case ARIZONA_DSP3_STATUS_1: + case ARIZONA_DSP3_STATUS_2: + case ARIZONA_DSP3_STATUS_3: + case ARIZONA_DSP3_STATUS_4: + case ARIZONA_DSP3_WDMA_BUFFER_1: + case ARIZONA_DSP3_WDMA_BUFFER_2: + case ARIZONA_DSP3_WDMA_BUFFER_3: + case ARIZONA_DSP3_WDMA_BUFFER_4: + case ARIZONA_DSP3_WDMA_BUFFER_5: + case ARIZONA_DSP3_WDMA_BUFFER_6: + case ARIZONA_DSP3_WDMA_BUFFER_7: + case ARIZONA_DSP3_WDMA_BUFFER_8: + case ARIZONA_DSP3_RDMA_BUFFER_1: + case ARIZONA_DSP3_RDMA_BUFFER_2: + case ARIZONA_DSP3_RDMA_BUFFER_3: + case ARIZONA_DSP3_RDMA_BUFFER_4: + case ARIZONA_DSP3_RDMA_BUFFER_5: + case ARIZONA_DSP3_RDMA_BUFFER_6: + case ARIZONA_DSP3_WDMA_CONFIG_1: + case ARIZONA_DSP3_WDMA_CONFIG_2: + case ARIZONA_DSP3_WDMA_OFFSET_1: + case ARIZONA_DSP3_RDMA_CONFIG_1: + case ARIZONA_DSP3_RDMA_OFFSET_1: + case ARIZONA_DSP3_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP3_SCRATCH_0: + case ARIZONA_DSP3_SCRATCH_1: + case ARIZONA_DSP3_SCRATCH_2: + case ARIZONA_DSP3_SCRATCH_3: + case ARIZONA_DSP3_CLOCKING_1: + case ARIZONA_DSP4_STATUS_1: + case ARIZONA_DSP4_STATUS_2: + case ARIZONA_DSP4_STATUS_3: + case ARIZONA_DSP4_STATUS_4: + case ARIZONA_DSP4_WDMA_BUFFER_1: + case ARIZONA_DSP4_WDMA_BUFFER_2: + case ARIZONA_DSP4_WDMA_BUFFER_3: + case ARIZONA_DSP4_WDMA_BUFFER_4: + case ARIZONA_DSP4_WDMA_BUFFER_5: + case ARIZONA_DSP4_WDMA_BUFFER_6: + case ARIZONA_DSP4_WDMA_BUFFER_7: + case ARIZONA_DSP4_WDMA_BUFFER_8: + case ARIZONA_DSP4_RDMA_BUFFER_1: + case ARIZONA_DSP4_RDMA_BUFFER_2: + case ARIZONA_DSP4_RDMA_BUFFER_3: + case ARIZONA_DSP4_RDMA_BUFFER_4: + case ARIZONA_DSP4_RDMA_BUFFER_5: + case ARIZONA_DSP4_RDMA_BUFFER_6: + case ARIZONA_DSP4_WDMA_CONFIG_1: + case ARIZONA_DSP4_WDMA_CONFIG_2: + case ARIZONA_DSP4_WDMA_OFFSET_1: + case ARIZONA_DSP4_RDMA_CONFIG_1: + case ARIZONA_DSP4_RDMA_OFFSET_1: + case ARIZONA_DSP4_EXTERNAL_START_SELECT_1: + case ARIZONA_DSP4_SCRATCH_0: + case ARIZONA_DSP4_SCRATCH_1: + case ARIZONA_DSP4_SCRATCH_2: + case ARIZONA_DSP4_SCRATCH_3: + case ARIZONA_DSP4_CLOCKING_1: + return true; + default: + return wm5110_is_adsp_memory(dev, reg); + } +} + +#define WM5110_MAX_REGISTER 0x4a9fff + +const struct regmap_config wm5110_spi_regmap = { + .reg_bits = 32, + .pad_bits = 16, + .val_bits = 16, + + .max_register = WM5110_MAX_REGISTER, + .readable_reg = wm5110_readable_register, + .volatile_reg = wm5110_volatile_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm5110_reg_default, + .num_reg_defaults = ARRAY_SIZE(wm5110_reg_default), +}; +EXPORT_SYMBOL_GPL(wm5110_spi_regmap); + +const struct regmap_config wm5110_i2c_regmap = { + .reg_bits = 32, + .val_bits = 16, + + .max_register = WM5110_MAX_REGISTER, + .readable_reg = wm5110_readable_register, + .volatile_reg = wm5110_volatile_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm5110_reg_default, + .num_reg_defaults = ARRAY_SIZE(wm5110_reg_default), +}; +EXPORT_SYMBOL_GPL(wm5110_i2c_regmap); diff --git a/kernel/drivers/mfd/wm831x-auxadc.c b/kernel/drivers/mfd/wm831x-auxadc.c new file mode 100644 index 000000000..6ee3018d8 --- /dev/null +++ b/kernel/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->auxadc_read = wm831x_auxadc_read_irq; + + ret = request_threaded_irq(wm831x_irq(wm831x, + 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/kernel/drivers/mfd/wm831x-core.c b/kernel/drivers/mfd/wm831x-core.c new file mode 100644 index 000000000..28366a90e --- /dev/null +++ b/kernel/drivers/mfd/wm831x-core.c @@ -0,0 +1,1937 @@ +/* + * wm831x-core.c -- Device access for Wolfson WM831x PMICs + * + * Copyright 2009 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/bcd.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/slab.h> +#include <linux/err.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/pmu.h> +#include <linux/mfd/wm831x/regulator.h> + +/* Current settings - values are 2*2^(reg_val/4) microamps. These are + * exported since they are used by multiple drivers. + */ +int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL + 1] = { + 2, + 2, + 3, + 3, + 4, + 5, + 6, + 7, + 8, + 10, + 11, + 13, + 16, + 19, + 23, + 27, + 32, + 38, + 45, + 54, + 64, + 76, + 91, + 108, + 128, + 152, + 181, + 215, + 256, + 304, + 362, + 431, + 512, + 609, + 724, + 861, + 1024, + 1218, + 1448, + 1722, + 2048, + 2435, + 2896, + 3444, + 4096, + 4871, + 5793, + 6889, + 8192, + 9742, + 11585, + 13777, + 16384, + 19484, + 23170, + 27554, +}; +EXPORT_SYMBOL_GPL(wm831x_isinkv_values); + +static int wm831x_reg_locked(struct wm831x *wm831x, unsigned short reg) +{ + if (!wm831x->locked) + return 0; + + switch (reg) { + case WM831X_WATCHDOG: + case WM831X_DC4_CONTROL: + case WM831X_ON_PIN_CONTROL: + case WM831X_BACKUP_CHARGER_CONTROL: + case WM831X_CHARGER_CONTROL_1: + case WM831X_CHARGER_CONTROL_2: + return 1; + + default: + return 0; + } +} + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * allowing writes to them. + */ +void wm831x_reg_lock(struct wm831x *wm831x) +{ + int ret; + + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers locked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(wm831x->locked); + wm831x->locked = 1; + mutex_unlock(&wm831x->io_lock); + } else { + dev_err(wm831x->dev, "Failed to lock registers: %d\n", ret); + } + +} +EXPORT_SYMBOL_GPL(wm831x_reg_lock); + +/** + * wm831x_reg_unlock: Unlock user keyed registers + * + * The WM831x has a user key preventing writes to particularly + * critical registers. This function locks those registers, + * preventing spurious writes. + */ +int wm831x_reg_unlock(struct wm831x *wm831x) +{ + int ret; + + /* 0x9716 is the value required to unlock the registers */ + ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0x9716); + if (ret == 0) { + dev_vdbg(wm831x->dev, "Registers unlocked\n"); + + mutex_lock(&wm831x->io_lock); + WARN_ON(!wm831x->locked); + wm831x->locked = 0; + mutex_unlock(&wm831x->io_lock); + } + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_unlock); + +static bool wm831x_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM831X_RESET_ID: + case WM831X_REVISION: + case WM831X_PARENT_ID: + case WM831X_SYSVDD_CONTROL: + case WM831X_THERMAL_MONITORING: + case WM831X_POWER_STATE: + case WM831X_WATCHDOG: + case WM831X_ON_PIN_CONTROL: + case WM831X_RESET_CONTROL: + case WM831X_CONTROL_INTERFACE: + case WM831X_SECURITY_KEY: + case WM831X_SOFTWARE_SCRATCH: + case WM831X_OTP_CONTROL: + case WM831X_GPIO_LEVEL: + case WM831X_SYSTEM_STATUS: + case WM831X_ON_SOURCE: + case WM831X_OFF_SOURCE: + case WM831X_SYSTEM_INTERRUPTS: + case WM831X_INTERRUPT_STATUS_1: + case WM831X_INTERRUPT_STATUS_2: + case WM831X_INTERRUPT_STATUS_3: + case WM831X_INTERRUPT_STATUS_4: + case WM831X_INTERRUPT_STATUS_5: + case WM831X_IRQ_CONFIG: + case WM831X_SYSTEM_INTERRUPTS_MASK: + case WM831X_INTERRUPT_STATUS_1_MASK: + case WM831X_INTERRUPT_STATUS_2_MASK: + case WM831X_INTERRUPT_STATUS_3_MASK: + case WM831X_INTERRUPT_STATUS_4_MASK: + case WM831X_INTERRUPT_STATUS_5_MASK: + case WM831X_RTC_WRITE_COUNTER: + case WM831X_RTC_TIME_1: + case WM831X_RTC_TIME_2: + case WM831X_RTC_ALARM_1: + case WM831X_RTC_ALARM_2: + case WM831X_RTC_CONTROL: + case WM831X_RTC_TRIM: + case WM831X_TOUCH_CONTROL_1: + case WM831X_TOUCH_CONTROL_2: + case WM831X_TOUCH_DATA_X: + case WM831X_TOUCH_DATA_Y: + case WM831X_TOUCH_DATA_Z: + case WM831X_AUXADC_DATA: + case WM831X_AUXADC_CONTROL: + case WM831X_AUXADC_SOURCE: + case WM831X_COMPARATOR_CONTROL: + case WM831X_COMPARATOR_1: + case WM831X_COMPARATOR_2: + case WM831X_COMPARATOR_3: + case WM831X_COMPARATOR_4: + case WM831X_GPIO1_CONTROL: + case WM831X_GPIO2_CONTROL: + case WM831X_GPIO3_CONTROL: + case WM831X_GPIO4_CONTROL: + case WM831X_GPIO5_CONTROL: + case WM831X_GPIO6_CONTROL: + case WM831X_GPIO7_CONTROL: + case WM831X_GPIO8_CONTROL: + case WM831X_GPIO9_CONTROL: + case WM831X_GPIO10_CONTROL: + case WM831X_GPIO11_CONTROL: + case WM831X_GPIO12_CONTROL: + case WM831X_GPIO13_CONTROL: + case WM831X_GPIO14_CONTROL: + case WM831X_GPIO15_CONTROL: + case WM831X_GPIO16_CONTROL: + case WM831X_CHARGER_CONTROL_1: + case WM831X_CHARGER_CONTROL_2: + case WM831X_CHARGER_STATUS: + case WM831X_BACKUP_CHARGER_CONTROL: + case WM831X_STATUS_LED_1: + case WM831X_STATUS_LED_2: + case WM831X_CURRENT_SINK_1: + case WM831X_CURRENT_SINK_2: + case WM831X_DCDC_ENABLE: + case WM831X_LDO_ENABLE: + case WM831X_DCDC_STATUS: + case WM831X_LDO_STATUS: + case WM831X_DCDC_UV_STATUS: + case WM831X_LDO_UV_STATUS: + case WM831X_DC1_CONTROL_1: + case WM831X_DC1_CONTROL_2: + case WM831X_DC1_ON_CONFIG: + case WM831X_DC1_SLEEP_CONTROL: + case WM831X_DC1_DVS_CONTROL: + case WM831X_DC2_CONTROL_1: + case WM831X_DC2_CONTROL_2: + case WM831X_DC2_ON_CONFIG: + case WM831X_DC2_SLEEP_CONTROL: + case WM831X_DC2_DVS_CONTROL: + case WM831X_DC3_CONTROL_1: + case WM831X_DC3_CONTROL_2: + case WM831X_DC3_ON_CONFIG: + case WM831X_DC3_SLEEP_CONTROL: + case WM831X_DC4_CONTROL: + case WM831X_DC4_SLEEP_CONTROL: + case WM831X_EPE1_CONTROL: + case WM831X_EPE2_CONTROL: + case WM831X_LDO1_CONTROL: + case WM831X_LDO1_ON_CONTROL: + case WM831X_LDO1_SLEEP_CONTROL: + case WM831X_LDO2_CONTROL: + case WM831X_LDO2_ON_CONTROL: + case WM831X_LDO2_SLEEP_CONTROL: + case WM831X_LDO3_CONTROL: + case WM831X_LDO3_ON_CONTROL: + case WM831X_LDO3_SLEEP_CONTROL: + case WM831X_LDO4_CONTROL: + case WM831X_LDO4_ON_CONTROL: + case WM831X_LDO4_SLEEP_CONTROL: + case WM831X_LDO5_CONTROL: + case WM831X_LDO5_ON_CONTROL: + case WM831X_LDO5_SLEEP_CONTROL: + case WM831X_LDO6_CONTROL: + case WM831X_LDO6_ON_CONTROL: + case WM831X_LDO6_SLEEP_CONTROL: + case WM831X_LDO7_CONTROL: + case WM831X_LDO7_ON_CONTROL: + case WM831X_LDO7_SLEEP_CONTROL: + case WM831X_LDO8_CONTROL: + case WM831X_LDO8_ON_CONTROL: + case WM831X_LDO8_SLEEP_CONTROL: + case WM831X_LDO9_CONTROL: + case WM831X_LDO9_ON_CONTROL: + case WM831X_LDO9_SLEEP_CONTROL: + case WM831X_LDO10_CONTROL: + case WM831X_LDO10_ON_CONTROL: + case WM831X_LDO10_SLEEP_CONTROL: + case WM831X_LDO11_ON_CONTROL: + case WM831X_LDO11_SLEEP_CONTROL: + case WM831X_POWER_GOOD_SOURCE_1: + case WM831X_POWER_GOOD_SOURCE_2: + case WM831X_CLOCK_CONTROL_1: + case WM831X_CLOCK_CONTROL_2: + case WM831X_FLL_CONTROL_1: + case WM831X_FLL_CONTROL_2: + case WM831X_FLL_CONTROL_3: + case WM831X_FLL_CONTROL_4: + case WM831X_FLL_CONTROL_5: + case WM831X_UNIQUE_ID_1: + case WM831X_UNIQUE_ID_2: + case WM831X_UNIQUE_ID_3: + case WM831X_UNIQUE_ID_4: + case WM831X_UNIQUE_ID_5: + case WM831X_UNIQUE_ID_6: + case WM831X_UNIQUE_ID_7: + case WM831X_UNIQUE_ID_8: + case WM831X_FACTORY_OTP_ID: + case WM831X_FACTORY_OTP_1: + case WM831X_FACTORY_OTP_2: + case WM831X_FACTORY_OTP_3: + case WM831X_FACTORY_OTP_4: + case WM831X_FACTORY_OTP_5: + case WM831X_CUSTOMER_OTP_ID: + case WM831X_DC1_OTP_CONTROL: + case WM831X_DC2_OTP_CONTROL: + case WM831X_DC3_OTP_CONTROL: + case WM831X_LDO1_2_OTP_CONTROL: + case WM831X_LDO3_4_OTP_CONTROL: + case WM831X_LDO5_6_OTP_CONTROL: + case WM831X_LDO7_8_OTP_CONTROL: + case WM831X_LDO9_10_OTP_CONTROL: + case WM831X_LDO11_EPE_CONTROL: + case WM831X_GPIO1_OTP_CONTROL: + case WM831X_GPIO2_OTP_CONTROL: + case WM831X_GPIO3_OTP_CONTROL: + case WM831X_GPIO4_OTP_CONTROL: + case WM831X_GPIO5_OTP_CONTROL: + case WM831X_GPIO6_OTP_CONTROL: + case WM831X_DBE_CHECK_DATA: + return true; + default: + return false; + } +} + +static bool wm831x_reg_writeable(struct device *dev, unsigned int reg) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + + if (wm831x_reg_locked(wm831x, reg)) + return false; + + switch (reg) { + case WM831X_SYSVDD_CONTROL: + case WM831X_THERMAL_MONITORING: + case WM831X_POWER_STATE: + case WM831X_WATCHDOG: + case WM831X_ON_PIN_CONTROL: + case WM831X_RESET_CONTROL: + case WM831X_CONTROL_INTERFACE: + case WM831X_SECURITY_KEY: + case WM831X_SOFTWARE_SCRATCH: + case WM831X_OTP_CONTROL: + case WM831X_GPIO_LEVEL: + case WM831X_INTERRUPT_STATUS_1: + case WM831X_INTERRUPT_STATUS_2: + case WM831X_INTERRUPT_STATUS_3: + case WM831X_INTERRUPT_STATUS_4: + case WM831X_INTERRUPT_STATUS_5: + case WM831X_IRQ_CONFIG: + case WM831X_SYSTEM_INTERRUPTS_MASK: + case WM831X_INTERRUPT_STATUS_1_MASK: + case WM831X_INTERRUPT_STATUS_2_MASK: + case WM831X_INTERRUPT_STATUS_3_MASK: + case WM831X_INTERRUPT_STATUS_4_MASK: + case WM831X_INTERRUPT_STATUS_5_MASK: + case WM831X_RTC_TIME_1: + case WM831X_RTC_TIME_2: + case WM831X_RTC_ALARM_1: + case WM831X_RTC_ALARM_2: + case WM831X_RTC_CONTROL: + case WM831X_RTC_TRIM: + case WM831X_TOUCH_CONTROL_1: + case WM831X_TOUCH_CONTROL_2: + case WM831X_AUXADC_CONTROL: + case WM831X_AUXADC_SOURCE: + case WM831X_COMPARATOR_CONTROL: + case WM831X_COMPARATOR_1: + case WM831X_COMPARATOR_2: + case WM831X_COMPARATOR_3: + case WM831X_COMPARATOR_4: + case WM831X_GPIO1_CONTROL: + case WM831X_GPIO2_CONTROL: + case WM831X_GPIO3_CONTROL: + case WM831X_GPIO4_CONTROL: + case WM831X_GPIO5_CONTROL: + case WM831X_GPIO6_CONTROL: + case WM831X_GPIO7_CONTROL: + case WM831X_GPIO8_CONTROL: + case WM831X_GPIO9_CONTROL: + case WM831X_GPIO10_CONTROL: + case WM831X_GPIO11_CONTROL: + case WM831X_GPIO12_CONTROL: + case WM831X_GPIO13_CONTROL: + case WM831X_GPIO14_CONTROL: + case WM831X_GPIO15_CONTROL: + case WM831X_GPIO16_CONTROL: + case WM831X_CHARGER_CONTROL_1: + case WM831X_CHARGER_CONTROL_2: + case WM831X_CHARGER_STATUS: + case WM831X_BACKUP_CHARGER_CONTROL: + case WM831X_STATUS_LED_1: + case WM831X_STATUS_LED_2: + case WM831X_CURRENT_SINK_1: + case WM831X_CURRENT_SINK_2: + case WM831X_DCDC_ENABLE: + case WM831X_LDO_ENABLE: + case WM831X_DC1_CONTROL_1: + case WM831X_DC1_CONTROL_2: + case WM831X_DC1_ON_CONFIG: + case WM831X_DC1_SLEEP_CONTROL: + case WM831X_DC1_DVS_CONTROL: + case WM831X_DC2_CONTROL_1: + case WM831X_DC2_CONTROL_2: + case WM831X_DC2_ON_CONFIG: + case WM831X_DC2_SLEEP_CONTROL: + case WM831X_DC2_DVS_CONTROL: + case WM831X_DC3_CONTROL_1: + case WM831X_DC3_CONTROL_2: + case WM831X_DC3_ON_CONFIG: + case WM831X_DC3_SLEEP_CONTROL: + case WM831X_DC4_CONTROL: + case WM831X_DC4_SLEEP_CONTROL: + case WM831X_EPE1_CONTROL: + case WM831X_EPE2_CONTROL: + case WM831X_LDO1_CONTROL: + case WM831X_LDO1_ON_CONTROL: + case WM831X_LDO1_SLEEP_CONTROL: + case WM831X_LDO2_CONTROL: + case WM831X_LDO2_ON_CONTROL: + case WM831X_LDO2_SLEEP_CONTROL: + case WM831X_LDO3_CONTROL: + case WM831X_LDO3_ON_CONTROL: + case WM831X_LDO3_SLEEP_CONTROL: + case WM831X_LDO4_CONTROL: + case WM831X_LDO4_ON_CONTROL: + case WM831X_LDO4_SLEEP_CONTROL: + case WM831X_LDO5_CONTROL: + case WM831X_LDO5_ON_CONTROL: + case WM831X_LDO5_SLEEP_CONTROL: + case WM831X_LDO6_CONTROL: + case WM831X_LDO6_ON_CONTROL: + case WM831X_LDO6_SLEEP_CONTROL: + case WM831X_LDO7_CONTROL: + case WM831X_LDO7_ON_CONTROL: + case WM831X_LDO7_SLEEP_CONTROL: + case WM831X_LDO8_CONTROL: + case WM831X_LDO8_ON_CONTROL: + case WM831X_LDO8_SLEEP_CONTROL: + case WM831X_LDO9_CONTROL: + case WM831X_LDO9_ON_CONTROL: + case WM831X_LDO9_SLEEP_CONTROL: + case WM831X_LDO10_CONTROL: + case WM831X_LDO10_ON_CONTROL: + case WM831X_LDO10_SLEEP_CONTROL: + case WM831X_LDO11_ON_CONTROL: + case WM831X_LDO11_SLEEP_CONTROL: + case WM831X_POWER_GOOD_SOURCE_1: + case WM831X_POWER_GOOD_SOURCE_2: + case WM831X_CLOCK_CONTROL_1: + case WM831X_CLOCK_CONTROL_2: + case WM831X_FLL_CONTROL_1: + case WM831X_FLL_CONTROL_2: + case WM831X_FLL_CONTROL_3: + case WM831X_FLL_CONTROL_4: + case WM831X_FLL_CONTROL_5: + return true; + default: + return false; + } +} + +static bool wm831x_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM831X_SYSTEM_STATUS: + case WM831X_ON_SOURCE: + case WM831X_OFF_SOURCE: + case WM831X_GPIO_LEVEL: + case WM831X_SYSTEM_INTERRUPTS: + case WM831X_INTERRUPT_STATUS_1: + case WM831X_INTERRUPT_STATUS_2: + case WM831X_INTERRUPT_STATUS_3: + case WM831X_INTERRUPT_STATUS_4: + case WM831X_INTERRUPT_STATUS_5: + case WM831X_RTC_TIME_1: + case WM831X_RTC_TIME_2: + case WM831X_TOUCH_DATA_X: + case WM831X_TOUCH_DATA_Y: + case WM831X_TOUCH_DATA_Z: + case WM831X_AUXADC_DATA: + case WM831X_CHARGER_STATUS: + case WM831X_DCDC_STATUS: + case WM831X_LDO_STATUS: + case WM831X_DCDC_UV_STATUS: + case WM831X_LDO_UV_STATUS: + return true; + default: + return false; + } +} + +/** + * wm831x_reg_read: Read a single WM831x register. + * + * @wm831x: Device to read from. + * @reg: Register to read. + */ +int wm831x_reg_read(struct wm831x *wm831x, unsigned short reg) +{ + unsigned int val; + int ret; + + ret = regmap_read(wm831x->regmap, reg, &val); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wm831x_reg_read); + +/** + * wm831x_bulk_read: Read multiple WM831x registers + * + * @wm831x: Device to read from + * @reg: First register + * @count: Number of registers + * @buf: Buffer to fill. + */ +int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg, + int count, u16 *buf) +{ + return regmap_bulk_read(wm831x->regmap, reg, buf, count); +} +EXPORT_SYMBOL_GPL(wm831x_bulk_read); + +static int wm831x_write(struct wm831x *wm831x, unsigned short reg, + int bytes, void *src) +{ + u16 *buf = src; + int i, ret; + + BUG_ON(bytes % 2); + BUG_ON(bytes <= 0); + + for (i = 0; i < bytes / 2; i++) { + if (wm831x_reg_locked(wm831x, reg)) + return -EPERM; + + dev_vdbg(wm831x->dev, "Write %04x to R%d(0x%x)\n", + buf[i], reg + i, reg + i); + ret = regmap_write(wm831x->regmap, reg + i, buf[i]); + if (ret != 0) + return ret; + } + + return 0; +} + +/** + * wm831x_reg_write: Write a single WM831x register. + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @val: Value to write. + */ +int wm831x_reg_write(struct wm831x *wm831x, unsigned short reg, + unsigned short val) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + ret = wm831x_write(wm831x, reg, 2, &val); + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_reg_write); + +/** + * wm831x_set_bits: Set the value of a bitfield in a WM831x register + * + * @wm831x: Device to write to. + * @reg: Register to write to. + * @mask: Mask of bits to set. + * @val: Value to set (unshifted) + */ +int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg, + unsigned short mask, unsigned short val) +{ + int ret; + + mutex_lock(&wm831x->io_lock); + + if (!wm831x_reg_locked(wm831x, reg)) + ret = regmap_update_bits(wm831x->regmap, reg, mask, val); + else + ret = -EPERM; + + mutex_unlock(&wm831x->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm831x_set_bits); + +static struct resource wm831x_dcdc1_resources[] = { + { + .start = WM831X_DC1_CONTROL_1, + .end = WM831X_DC1_DVS_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC1, + .end = WM831X_IRQ_UV_DC1, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC1, + .end = WM831X_IRQ_HC_DC1, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_dcdc2_resources[] = { + { + .start = WM831X_DC2_CONTROL_1, + .end = WM831X_DC2_DVS_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC2, + .end = WM831X_IRQ_UV_DC2, + .flags = IORESOURCE_IRQ, + }, + { + .name = "HC", + .start = WM831X_IRQ_HC_DC2, + .end = WM831X_IRQ_HC_DC2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc3_resources[] = { + { + .start = WM831X_DC3_CONTROL_1, + .end = WM831X_DC3_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC3, + .end = WM831X_IRQ_UV_DC3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_dcdc4_resources[] = { + { + .start = WM831X_DC4_CONTROL, + .end = WM831X_DC4_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC4, + .end = WM831X_IRQ_UV_DC4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm8320_dcdc4_buck_resources[] = { + { + .start = WM831X_DC4_CONTROL, + .end = WM832X_DC4_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_DC4, + .end = WM831X_IRQ_UV_DC4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_gpio_resources[] = { + { + .start = WM831X_IRQ_GPIO_1, + .end = WM831X_IRQ_GPIO_16, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink1_resources[] = { + { + .start = WM831X_CURRENT_SINK_1, + .end = WM831X_CURRENT_SINK_1, + .flags = IORESOURCE_REG, + }, + { + .start = WM831X_IRQ_CS1, + .end = WM831X_IRQ_CS1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_isink2_resources[] = { + { + .start = WM831X_CURRENT_SINK_2, + .end = WM831X_CURRENT_SINK_2, + .flags = IORESOURCE_REG, + }, + { + .start = WM831X_IRQ_CS2, + .end = WM831X_IRQ_CS2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo1_resources[] = { + { + .start = WM831X_LDO1_CONTROL, + .end = WM831X_LDO1_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO1, + .end = WM831X_IRQ_UV_LDO1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo2_resources[] = { + { + .start = WM831X_LDO2_CONTROL, + .end = WM831X_LDO2_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO2, + .end = WM831X_IRQ_UV_LDO2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo3_resources[] = { + { + .start = WM831X_LDO3_CONTROL, + .end = WM831X_LDO3_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO3, + .end = WM831X_IRQ_UV_LDO3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo4_resources[] = { + { + .start = WM831X_LDO4_CONTROL, + .end = WM831X_LDO4_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO4, + .end = WM831X_IRQ_UV_LDO4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo5_resources[] = { + { + .start = WM831X_LDO5_CONTROL, + .end = WM831X_LDO5_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO5, + .end = WM831X_IRQ_UV_LDO5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo6_resources[] = { + { + .start = WM831X_LDO6_CONTROL, + .end = WM831X_LDO6_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO6, + .end = WM831X_IRQ_UV_LDO6, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo7_resources[] = { + { + .start = WM831X_LDO7_CONTROL, + .end = WM831X_LDO7_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO7, + .end = WM831X_IRQ_UV_LDO7, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo8_resources[] = { + { + .start = WM831X_LDO8_CONTROL, + .end = WM831X_LDO8_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO8, + .end = WM831X_IRQ_UV_LDO8, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo9_resources[] = { + { + .start = WM831X_LDO9_CONTROL, + .end = WM831X_LDO9_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO9, + .end = WM831X_IRQ_UV_LDO9, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo10_resources[] = { + { + .start = WM831X_LDO10_CONTROL, + .end = WM831X_LDO10_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, + { + .name = "UV", + .start = WM831X_IRQ_UV_LDO10, + .end = WM831X_IRQ_UV_LDO10, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_ldo11_resources[] = { + { + .start = WM831X_LDO11_ON_CONTROL, + .end = WM831X_LDO11_SLEEP_CONTROL, + .flags = IORESOURCE_REG, + }, +}; + +static struct resource wm831x_on_resources[] = { + { + .start = WM831X_IRQ_ON, + .end = WM831X_IRQ_ON, + .flags = IORESOURCE_IRQ, + }, +}; + + +static struct resource wm831x_power_resources[] = { + { + .name = "SYSLO", + .start = WM831X_IRQ_PPM_SYSLO, + .end = WM831X_IRQ_PPM_SYSLO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "PWR SRC", + .start = WM831X_IRQ_PPM_PWR_SRC, + .end = WM831X_IRQ_PPM_PWR_SRC, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB CURR", + .start = WM831X_IRQ_PPM_USB_CURR, + .end = WM831X_IRQ_PPM_USB_CURR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT HOT", + .start = WM831X_IRQ_CHG_BATT_HOT, + .end = WM831X_IRQ_CHG_BATT_HOT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT COLD", + .start = WM831X_IRQ_CHG_BATT_COLD, + .end = WM831X_IRQ_CHG_BATT_COLD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT FAIL", + .start = WM831X_IRQ_CHG_BATT_FAIL, + .end = WM831X_IRQ_CHG_BATT_FAIL, + .flags = IORESOURCE_IRQ, + }, + { + .name = "OV", + .start = WM831X_IRQ_CHG_OV, + .end = WM831X_IRQ_CHG_OV, + .flags = IORESOURCE_IRQ, + }, + { + .name = "END", + .start = WM831X_IRQ_CHG_END, + .end = WM831X_IRQ_CHG_END, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TO", + .start = WM831X_IRQ_CHG_TO, + .end = WM831X_IRQ_CHG_TO, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MODE", + .start = WM831X_IRQ_CHG_MODE, + .end = WM831X_IRQ_CHG_MODE, + .flags = IORESOURCE_IRQ, + }, + { + .name = "START", + .start = WM831X_IRQ_CHG_START, + .end = WM831X_IRQ_CHG_START, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_rtc_resources[] = { + { + .name = "PER", + .start = WM831X_IRQ_RTC_PER, + .end = WM831X_IRQ_RTC_PER, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ALM", + .start = WM831X_IRQ_RTC_ALM, + .end = WM831X_IRQ_RTC_ALM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_status1_resources[] = { + { + .start = WM831X_STATUS_LED_1, + .end = WM831X_STATUS_LED_1, + .flags = IORESOURCE_REG, + }, +}; + +static struct resource wm831x_status2_resources[] = { + { + .start = WM831X_STATUS_LED_2, + .end = WM831X_STATUS_LED_2, + .flags = IORESOURCE_REG, + }, +}; + +static struct resource wm831x_touch_resources[] = { + { + .name = "TCHPD", + .start = WM831X_IRQ_TCHPD, + .end = WM831X_IRQ_TCHPD, + .flags = IORESOURCE_IRQ, + }, + { + .name = "TCHDATA", + .start = WM831X_IRQ_TCHDATA, + .end = WM831X_IRQ_TCHDATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm831x_wdt_resources[] = { + { + .start = WM831X_IRQ_WDOG_TO, + .end = WM831X_IRQ_WDOG_TO, + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell wm8310_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-clk", + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static const struct mfd_cell wm8311_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-clk", + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static const struct mfd_cell wm8312_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-boostp", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources), + .resources = wm831x_dcdc4_resources, + }, + { + .name = "wm831x-clk", + }, + { + .name = "wm831x-epe", + .id = 1, + }, + { + .name = "wm831x-epe", + .id = 2, + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-isink", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_isink1_resources), + .resources = wm831x_isink1_resources, + }, + { + .name = "wm831x-isink", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_isink2_resources), + .resources = wm831x_isink2_resources, + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-power", + .num_resources = ARRAY_SIZE(wm831x_power_resources), + .resources = wm831x_power_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static const struct mfd_cell wm8320_devs[] = { + { + .name = "wm831x-backup", + }, + { + .name = "wm831x-buckv", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources), + .resources = wm831x_dcdc1_resources, + }, + { + .name = "wm831x-buckv", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources), + .resources = wm831x_dcdc2_resources, + }, + { + .name = "wm831x-buckp", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources), + .resources = wm831x_dcdc3_resources, + }, + { + .name = "wm831x-buckp", + .id = 4, + .num_resources = ARRAY_SIZE(wm8320_dcdc4_buck_resources), + .resources = wm8320_dcdc4_buck_resources, + }, + { + .name = "wm831x-clk", + }, + { + .name = "wm831x-gpio", + .num_resources = ARRAY_SIZE(wm831x_gpio_resources), + .resources = wm831x_gpio_resources, + }, + { + .name = "wm831x-hwmon", + }, + { + .name = "wm831x-ldo", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_ldo1_resources), + .resources = wm831x_ldo1_resources, + }, + { + .name = "wm831x-ldo", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_ldo2_resources), + .resources = wm831x_ldo2_resources, + }, + { + .name = "wm831x-ldo", + .id = 3, + .num_resources = ARRAY_SIZE(wm831x_ldo3_resources), + .resources = wm831x_ldo3_resources, + }, + { + .name = "wm831x-ldo", + .id = 4, + .num_resources = ARRAY_SIZE(wm831x_ldo4_resources), + .resources = wm831x_ldo4_resources, + }, + { + .name = "wm831x-ldo", + .id = 5, + .num_resources = ARRAY_SIZE(wm831x_ldo5_resources), + .resources = wm831x_ldo5_resources, + }, + { + .name = "wm831x-ldo", + .id = 6, + .num_resources = ARRAY_SIZE(wm831x_ldo6_resources), + .resources = wm831x_ldo6_resources, + }, + { + .name = "wm831x-aldo", + .id = 7, + .num_resources = ARRAY_SIZE(wm831x_ldo7_resources), + .resources = wm831x_ldo7_resources, + }, + { + .name = "wm831x-aldo", + .id = 8, + .num_resources = ARRAY_SIZE(wm831x_ldo8_resources), + .resources = wm831x_ldo8_resources, + }, + { + .name = "wm831x-aldo", + .id = 9, + .num_resources = ARRAY_SIZE(wm831x_ldo9_resources), + .resources = wm831x_ldo9_resources, + }, + { + .name = "wm831x-aldo", + .id = 10, + .num_resources = ARRAY_SIZE(wm831x_ldo10_resources), + .resources = wm831x_ldo10_resources, + }, + { + .name = "wm831x-alive-ldo", + .id = 11, + .num_resources = ARRAY_SIZE(wm831x_ldo11_resources), + .resources = wm831x_ldo11_resources, + }, + { + .name = "wm831x-on", + .num_resources = ARRAY_SIZE(wm831x_on_resources), + .resources = wm831x_on_resources, + }, + { + .name = "wm831x-status", + .id = 1, + .num_resources = ARRAY_SIZE(wm831x_status1_resources), + .resources = wm831x_status1_resources, + }, + { + .name = "wm831x-status", + .id = 2, + .num_resources = ARRAY_SIZE(wm831x_status2_resources), + .resources = wm831x_status2_resources, + }, + { + .name = "wm831x-watchdog", + .num_resources = ARRAY_SIZE(wm831x_wdt_resources), + .resources = wm831x_wdt_resources, + }, +}; + +static const struct mfd_cell touch_devs[] = { + { + .name = "wm831x-touch", + .num_resources = ARRAY_SIZE(wm831x_touch_resources), + .resources = wm831x_touch_resources, + }, +}; + +static const struct mfd_cell rtc_devs[] = { + { + .name = "wm831x-rtc", + .num_resources = ARRAY_SIZE(wm831x_rtc_resources), + .resources = wm831x_rtc_resources, + }, +}; + +static const struct mfd_cell backlight_devs[] = { + { + .name = "wm831x-backlight", + }, +}; + +struct regmap_config wm831x_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + + .max_register = WM831X_DBE_CHECK_DATA, + .readable_reg = wm831x_reg_readable, + .writeable_reg = wm831x_reg_writeable, + .volatile_reg = wm831x_reg_volatile, +}; +EXPORT_SYMBOL_GPL(wm831x_regmap_config); + +/* + * Instantiate the generic non-control parts of the device. + */ +int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) +{ + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + int rev, wm831x_num; + enum wm831x_parent parent; + int ret, i; + + mutex_init(&wm831x->io_lock); + mutex_init(&wm831x->key_lock); + dev_set_drvdata(wm831x->dev, wm831x); + wm831x->soft_shutdown = pdata->soft_shutdown; + + ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read parent ID: %d\n", ret); + goto err; + } + switch (ret) { + case 0x6204: + case 0x6246: + break; + default: + dev_err(wm831x->dev, "Device is not a WM831x: ID %x\n", ret); + ret = -EINVAL; + goto err; + } + + ret = wm831x_reg_read(wm831x, WM831X_REVISION); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read revision: %d\n", ret); + goto err; + } + rev = (ret & WM831X_PARENT_REV_MASK) >> WM831X_PARENT_REV_SHIFT; + + ret = wm831x_reg_read(wm831x, WM831X_RESET_ID); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read device ID: %d\n", ret); + goto err; + } + + /* Some engineering samples do not have the ID set, rely on + * the device being registered correctly. + */ + if (ret == 0) { + dev_info(wm831x->dev, "Device is an engineering sample\n"); + ret = id; + } + + switch (ret) { + case WM8310: + parent = WM8310; + wm831x->num_gpio = 16; + wm831x->charger_irq_wake = 1; + if (rev > 0) { + wm831x->has_gpio_ena = 1; + wm831x->has_cs_sts = 1; + } + + dev_info(wm831x->dev, "WM8310 revision %c\n", 'A' + rev); + break; + + case WM8311: + parent = WM8311; + wm831x->num_gpio = 16; + wm831x->charger_irq_wake = 1; + if (rev > 0) { + wm831x->has_gpio_ena = 1; + wm831x->has_cs_sts = 1; + } + + dev_info(wm831x->dev, "WM8311 revision %c\n", 'A' + rev); + break; + + case WM8312: + parent = WM8312; + wm831x->num_gpio = 16; + wm831x->charger_irq_wake = 1; + if (rev > 0) { + wm831x->has_gpio_ena = 1; + wm831x->has_cs_sts = 1; + } + + dev_info(wm831x->dev, "WM8312 revision %c\n", 'A' + rev); + break; + + case WM8320: + parent = WM8320; + wm831x->num_gpio = 12; + dev_info(wm831x->dev, "WM8320 revision %c\n", 'A' + rev); + break; + + case WM8321: + parent = WM8321; + wm831x->num_gpio = 12; + dev_info(wm831x->dev, "WM8321 revision %c\n", 'A' + rev); + break; + + case WM8325: + parent = WM8325; + wm831x->num_gpio = 12; + dev_info(wm831x->dev, "WM8325 revision %c\n", 'A' + rev); + break; + + case WM8326: + parent = WM8326; + wm831x->num_gpio = 12; + dev_info(wm831x->dev, "WM8326 revision %c\n", 'A' + rev); + break; + + default: + dev_err(wm831x->dev, "Unknown WM831x device %04x\n", ret); + ret = -EINVAL; + goto err; + } + + /* This will need revisiting in future but is OK for all + * current parts. + */ + if (parent != id) + dev_warn(wm831x->dev, "Device was registered as a WM%lx\n", + id); + + /* Bootstrap the user key */ + ret = wm831x_reg_read(wm831x, WM831X_SECURITY_KEY); + if (ret < 0) { + dev_err(wm831x->dev, "Failed to read security key: %d\n", ret); + goto err; + } + if (ret != 0) { + dev_warn(wm831x->dev, "Security key had non-zero value %x\n", + ret); + wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0); + } + wm831x->locked = 1; + + if (pdata && pdata->pre_init) { + ret = pdata->pre_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "pre_init() failed: %d\n", ret); + goto err; + } + } + + if (pdata) { + for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) { + if (!pdata->gpio_defaults[i]) + continue; + + wm831x_reg_write(wm831x, + WM831X_GPIO1_CONTROL + i, + pdata->gpio_defaults[i] & 0xffff); + } + } + + /* 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; + + wm831x_auxadc_init(wm831x); + + /* The core device is up, instantiate the subdevices. */ + switch (parent) { + case WM8310: + ret = mfd_add_devices(wm831x->dev, wm831x_num, + wm8310_devs, ARRAY_SIZE(wm8310_devs), + NULL, 0, NULL); + break; + + case WM8311: + ret = mfd_add_devices(wm831x->dev, wm831x_num, + wm8311_devs, ARRAY_SIZE(wm8311_devs), + NULL, 0, NULL); + if (!pdata || !pdata->disable_touch) + mfd_add_devices(wm831x->dev, wm831x_num, + touch_devs, ARRAY_SIZE(touch_devs), + NULL, 0, NULL); + break; + + case WM8312: + ret = mfd_add_devices(wm831x->dev, wm831x_num, + wm8312_devs, ARRAY_SIZE(wm8312_devs), + NULL, 0, NULL); + if (!pdata || !pdata->disable_touch) + mfd_add_devices(wm831x->dev, wm831x_num, + touch_devs, ARRAY_SIZE(touch_devs), + NULL, 0, NULL); + break; + + case WM8320: + case WM8321: + case WM8325: + case WM8326: + ret = mfd_add_devices(wm831x->dev, wm831x_num, + wm8320_devs, ARRAY_SIZE(wm8320_devs), + NULL, 0, NULL); + break; + + default: + /* If this happens the bus probe function is buggy */ + BUG(); + } + + if (ret != 0) { + dev_err(wm831x->dev, "Failed to add children\n"); + 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, 0, NULL); + 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, wm831x_num, backlight_devs, + ARRAY_SIZE(backlight_devs), NULL, + 0, NULL); + if (ret < 0) + dev_err(wm831x->dev, "Failed to add backlight: %d\n", + ret); + } + + wm831x_otp_init(wm831x); + + if (pdata && pdata->post_init) { + ret = pdata->post_init(wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "post_init() failed: %d\n", ret); + goto err_irq; + } + } + + return 0; + +err_irq: + wm831x_irq_exit(wm831x); +err: + mfd_remove_devices(wm831x->dev); + return ret; +} + +void wm831x_device_exit(struct wm831x *wm831x) +{ + wm831x_otp_exit(wm831x); + mfd_remove_devices(wm831x->dev); + free_irq(wm831x_irq(wm831x, WM831X_IRQ_AUXADC_DATA), wm831x); + wm831x_irq_exit(wm831x); +} + +int wm831x_device_suspend(struct wm831x *wm831x) +{ + int reg, mask; + + /* If the charger IRQs are a wake source then make sure we ack + * them even if they're not actively being used (eg, no power + * driver or no IRQ line wired up) then acknowledge the + * interrupts otherwise suspend won't last very long. + */ + if (wm831x->charger_irq_wake) { + reg = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_2_MASK); + + mask = WM831X_CHG_BATT_HOT_EINT | + WM831X_CHG_BATT_COLD_EINT | + WM831X_CHG_BATT_FAIL_EINT | + WM831X_CHG_OV_EINT | WM831X_CHG_END_EINT | + WM831X_CHG_TO_EINT | WM831X_CHG_MODE_EINT | + WM831X_CHG_START_EINT; + + /* If any of the interrupts are masked read the statuses */ + if (reg & mask) + reg = wm831x_reg_read(wm831x, + WM831X_INTERRUPT_STATUS_2); + + if (reg & mask) { + dev_info(wm831x->dev, + "Acknowledging masked charger IRQs: %x\n", + reg & mask); + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_2, + reg & mask); + } + } + + return 0; +} + +void wm831x_device_shutdown(struct wm831x *wm831x) +{ + if (wm831x->soft_shutdown) { + dev_info(wm831x->dev, "Initiating shutdown...\n"); + wm831x_set_bits(wm831x, WM831X_POWER_STATE, WM831X_CHIP_ON, 0); + } +} +EXPORT_SYMBOL_GPL(wm831x_device_shutdown); + +MODULE_DESCRIPTION("Core support for the WM831X AudioPlus PMIC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown"); diff --git a/kernel/drivers/mfd/wm831x-i2c.c b/kernel/drivers/mfd/wm831x-i2c.c new file mode 100644 index 000000000..a4cbefe54 --- /dev/null +++ b/kernel/drivers/mfd/wm831x-i2c.c @@ -0,0 +1,120 @@ +/* + * wm831x-i2c.c -- I2C access for Wolfson WM831x PMICs + * + * Copyright 2009,2010 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/i2c.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/regmap.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> + +static int wm831x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm831x *wm831x; + int ret; + + wm831x = devm_kzalloc(&i2c->dev, sizeof(struct wm831x), GFP_KERNEL); + if (wm831x == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm831x); + wm831x->dev = &i2c->dev; + + wm831x->regmap = devm_regmap_init_i2c(i2c, &wm831x_regmap_config); + if (IS_ERR(wm831x->regmap)) { + ret = PTR_ERR(wm831x->regmap); + dev_err(wm831x->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + return wm831x_device_init(wm831x, id->driver_data, i2c->irq); +} + +static int wm831x_i2c_remove(struct i2c_client *i2c) +{ + struct wm831x *wm831x = i2c_get_clientdata(i2c); + + wm831x_device_exit(wm831x); + + return 0; +} + +static int wm831x_i2c_suspend(struct device *dev) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + + return wm831x_device_suspend(wm831x); +} + +static int wm831x_i2c_poweroff(struct device *dev) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + + wm831x_device_shutdown(wm831x); + + return 0; +} + +static const struct i2c_device_id wm831x_i2c_id[] = { + { "wm8310", WM8310 }, + { "wm8311", WM8311 }, + { "wm8312", WM8312 }, + { "wm8320", WM8320 }, + { "wm8321", WM8321 }, + { "wm8325", WM8325 }, + { "wm8326", WM8326 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm831x_i2c_id); + +static const struct dev_pm_ops wm831x_pm_ops = { + .suspend = wm831x_i2c_suspend, + .poweroff = wm831x_i2c_poweroff, +}; + +static struct i2c_driver wm831x_i2c_driver = { + .driver = { + .name = "wm831x", + .owner = THIS_MODULE, + .pm = &wm831x_pm_ops, + }, + .probe = wm831x_i2c_probe, + .remove = wm831x_i2c_remove, + .id_table = wm831x_i2c_id, +}; + +static int __init wm831x_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&wm831x_i2c_driver); + if (ret != 0) + pr_err("Failed to register wm831x I2C driver: %d\n", ret); + + return ret; +} +subsys_initcall(wm831x_i2c_init); + +static void __exit wm831x_i2c_exit(void) +{ + i2c_del_driver(&wm831x_i2c_driver); +} +module_exit(wm831x_i2c_exit); diff --git a/kernel/drivers/mfd/wm831x-irq.c b/kernel/drivers/mfd/wm831x-irq.c new file mode 100644 index 000000000..64e512ead --- /dev/null +++ b/kernel/drivers/mfd/wm831x-irq.c @@ -0,0 +1,665 @@ +/* + * wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs + * + * Copyright 2009 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/i2c.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/gpio.h> +#include <linux/mfd/wm831x/irq.h> + +#include <linux/delay.h> + +struct wm831x_irq_data { + int primary; + int reg; + int mask; +}; + +static struct wm831x_irq_data wm831x_irqs[] = { + [WM831X_IRQ_TEMP_THW] = { + .primary = WM831X_TEMP_INT, + .reg = 1, + .mask = WM831X_TEMP_THW_EINT, + }, + [WM831X_IRQ_GPIO_1] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP1_EINT, + }, + [WM831X_IRQ_GPIO_2] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP2_EINT, + }, + [WM831X_IRQ_GPIO_3] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP3_EINT, + }, + [WM831X_IRQ_GPIO_4] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP4_EINT, + }, + [WM831X_IRQ_GPIO_5] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP5_EINT, + }, + [WM831X_IRQ_GPIO_6] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP6_EINT, + }, + [WM831X_IRQ_GPIO_7] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP7_EINT, + }, + [WM831X_IRQ_GPIO_8] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP8_EINT, + }, + [WM831X_IRQ_GPIO_9] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP9_EINT, + }, + [WM831X_IRQ_GPIO_10] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP10_EINT, + }, + [WM831X_IRQ_GPIO_11] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP11_EINT, + }, + [WM831X_IRQ_GPIO_12] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP12_EINT, + }, + [WM831X_IRQ_GPIO_13] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP13_EINT, + }, + [WM831X_IRQ_GPIO_14] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP14_EINT, + }, + [WM831X_IRQ_GPIO_15] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP15_EINT, + }, + [WM831X_IRQ_GPIO_16] = { + .primary = WM831X_GP_INT, + .reg = 5, + .mask = WM831X_GP16_EINT, + }, + [WM831X_IRQ_ON] = { + .primary = WM831X_ON_PIN_INT, + .reg = 1, + .mask = WM831X_ON_PIN_EINT, + }, + [WM831X_IRQ_PPM_SYSLO] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_SYSLO_EINT, + }, + [WM831X_IRQ_PPM_PWR_SRC] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_PWR_SRC_EINT, + }, + [WM831X_IRQ_PPM_USB_CURR] = { + .primary = WM831X_PPM_INT, + .reg = 1, + .mask = WM831X_PPM_USB_CURR_EINT, + }, + [WM831X_IRQ_WDOG_TO] = { + .primary = WM831X_WDOG_INT, + .reg = 1, + .mask = WM831X_WDOG_TO_EINT, + }, + [WM831X_IRQ_RTC_PER] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_PER_EINT, + }, + [WM831X_IRQ_RTC_ALM] = { + .primary = WM831X_RTC_INT, + .reg = 1, + .mask = WM831X_RTC_ALM_EINT, + }, + [WM831X_IRQ_CHG_BATT_HOT] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_HOT_EINT, + }, + [WM831X_IRQ_CHG_BATT_COLD] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_COLD_EINT, + }, + [WM831X_IRQ_CHG_BATT_FAIL] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_BATT_FAIL_EINT, + }, + [WM831X_IRQ_CHG_OV] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_OV_EINT, + }, + [WM831X_IRQ_CHG_END] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_END_EINT, + }, + [WM831X_IRQ_CHG_TO] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_TO_EINT, + }, + [WM831X_IRQ_CHG_MODE] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_MODE_EINT, + }, + [WM831X_IRQ_CHG_START] = { + .primary = WM831X_CHG_INT, + .reg = 2, + .mask = WM831X_CHG_START_EINT, + }, + [WM831X_IRQ_TCHDATA] = { + .primary = WM831X_TCHDATA_INT, + .reg = 1, + .mask = WM831X_TCHDATA_EINT, + }, + [WM831X_IRQ_TCHPD] = { + .primary = WM831X_TCHPD_INT, + .reg = 1, + .mask = WM831X_TCHPD_EINT, + }, + [WM831X_IRQ_AUXADC_DATA] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DATA_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP1] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP1_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP2] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP2_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP3] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP3_EINT, + }, + [WM831X_IRQ_AUXADC_DCOMP4] = { + .primary = WM831X_AUXADC_INT, + .reg = 1, + .mask = WM831X_AUXADC_DCOMP4_EINT, + }, + [WM831X_IRQ_CS1] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS1_EINT, + }, + [WM831X_IRQ_CS2] = { + .primary = WM831X_CS_INT, + .reg = 2, + .mask = WM831X_CS2_EINT, + }, + [WM831X_IRQ_HC_DC1] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC1_EINT, + }, + [WM831X_IRQ_HC_DC2] = { + .primary = WM831X_HC_INT, + .reg = 4, + .mask = WM831X_HC_DC2_EINT, + }, + [WM831X_IRQ_UV_LDO1] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO1_EINT, + }, + [WM831X_IRQ_UV_LDO2] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO2_EINT, + }, + [WM831X_IRQ_UV_LDO3] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO3_EINT, + }, + [WM831X_IRQ_UV_LDO4] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO4_EINT, + }, + [WM831X_IRQ_UV_LDO5] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO5_EINT, + }, + [WM831X_IRQ_UV_LDO6] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO6_EINT, + }, + [WM831X_IRQ_UV_LDO7] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO7_EINT, + }, + [WM831X_IRQ_UV_LDO8] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO8_EINT, + }, + [WM831X_IRQ_UV_LDO9] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO9_EINT, + }, + [WM831X_IRQ_UV_LDO10] = { + .primary = WM831X_UV_INT, + .reg = 3, + .mask = WM831X_UV_LDO10_EINT, + }, + [WM831X_IRQ_UV_DC1] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC1_EINT, + }, + [WM831X_IRQ_UV_DC2] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC2_EINT, + }, + [WM831X_IRQ_UV_DC3] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC3_EINT, + }, + [WM831X_IRQ_UV_DC4] = { + .primary = WM831X_UV_INT, + .reg = 4, + .mask = WM831X_UV_DC4_EINT, + }, +}; + +static inline int irq_data_to_status_reg(struct wm831x_irq_data *irq_data) +{ + return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data->reg; +} + +static inline struct wm831x_irq_data *irq_to_wm831x_irq(struct wm831x *wm831x, + int irq) +{ + return &wm831x_irqs[irq]; +} + +static void wm831x_irq_lock(struct irq_data *data) +{ + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + + mutex_lock(&wm831x->irq_lock); +} + +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. */ + if (wm831x->irq_masks_cur[i] != wm831x->irq_masks_cache[i]) { + dev_dbg(wm831x->dev, "IRQ mask sync: %x = %x\n", + WM831X_INTERRUPT_STATUS_1_MASK + i, + wm831x->irq_masks_cur[i]); + + wm831x->irq_masks_cache[i] = wm831x->irq_masks_cur[i]; + wm831x_reg_write(wm831x, + WM831X_INTERRUPT_STATUS_1_MASK + i, + wm831x->irq_masks_cur[i]); + } + } + + mutex_unlock(&wm831x->irq_lock); +} + +static void wm831x_irq_enable(struct irq_data *data) +{ + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x, + data->hwirq); + + wm831x->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void wm831x_irq_disable(struct irq_data *data) +{ + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x, + data->hwirq); + + wm831x->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static int wm831x_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + int irq; + + irq = data->hwirq; + + if (irq < WM831X_IRQ_GPIO_1 || irq > WM831X_IRQ_GPIO_11) { + /* Ignore internal-only IRQs */ + if (irq >= 0 && irq < WM831X_NUM_IRQS) + return 0; + else + 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. + */ + wm831x->gpio_level_low[irq] = false; + wm831x->gpio_level_high[irq] = false; + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_INT_MODE; + break; + case IRQ_TYPE_EDGE_RISING: + wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL; + break; + case IRQ_TYPE_EDGE_FALLING: + wm831x->gpio_update[irq] = 0x10000; + break; + case IRQ_TYPE_LEVEL_HIGH: + wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL; + wm831x->gpio_level_high[irq] = true; + break; + case IRQ_TYPE_LEVEL_LOW: + wm831x->gpio_update[irq] = 0x10000; + wm831x->gpio_level_low[irq] = true; + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct irq_chip wm831x_irq_chip = { + .name = "wm831x", + .irq_bus_lock = wm831x_irq_lock, + .irq_bus_sync_unlock = wm831x_irq_sync_unlock, + .irq_disable = wm831x_irq_disable, + .irq_enable = wm831x_irq_enable, + .irq_set_type = wm831x_irq_set_type, +}; + +/* The processing of the primary interrupt occurs in a thread so that + * we can interact with the device over I2C or SPI. */ +static irqreturn_t wm831x_irq_thread(int irq, void *data) +{ + struct wm831x *wm831x = data; + unsigned int i; + int primary, status_addr, ret; + int status_regs[WM831X_NUM_IRQ_REGS] = { 0 }; + int read[WM831X_NUM_IRQ_REGS] = { 0 }; + int *status; + + primary = wm831x_reg_read(wm831x, WM831X_SYSTEM_INTERRUPTS); + if (primary < 0) { + dev_err(wm831x->dev, "Failed to read system interrupt: %d\n", + primary); + goto out; + } + + /* The touch interrupts are visible in the primary register as + * an optimisation; open code this to avoid complicating the + * main handling loop and so we can also skip iterating the + * descriptors. + */ + if (primary & WM831X_TCHPD_INT) + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + WM831X_IRQ_TCHPD)); + if (primary & WM831X_TCHDATA_INT) + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + WM831X_IRQ_TCHDATA)); + primary &= ~(WM831X_TCHDATA_EINT | WM831X_TCHPD_EINT); + + for (i = 0; i < ARRAY_SIZE(wm831x_irqs); i++) { + int offset = wm831x_irqs[i].reg - 1; + + if (!(primary & wm831x_irqs[i].primary)) + continue; + + status = &status_regs[offset]; + + /* Hopefully there should only be one register to read + * each time otherwise we ought to do a block read. */ + if (!read[offset]) { + 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", + *status); + goto out; + } + + 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); + } + + if (*status & wm831x_irqs[i].mask) + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + i)); + + /* Simulate an edge triggered IRQ by polling the input + * status. This is sucky but improves interoperability. + */ + if (primary == WM831X_GP_INT && + wm831x->gpio_level_high[i - WM831X_IRQ_GPIO_1]) { + ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); + while (ret & 1 << (i - WM831X_IRQ_GPIO_1)) { + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + i)); + ret = wm831x_reg_read(wm831x, + WM831X_GPIO_LEVEL); + } + } + + if (primary == WM831X_GP_INT && + wm831x->gpio_level_low[i - WM831X_IRQ_GPIO_1]) { + ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); + while (!(ret & 1 << (i - WM831X_IRQ_GPIO_1))) { + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + i)); + ret = wm831x_reg_read(wm831x, + WM831X_GPIO_LEVEL); + } + } + } + +out: + return IRQ_HANDLED; +} + +static int wm831x_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_data(virq, h->host_data); + irq_set_chip_and_handler(virq, &wm831x_irq_chip, handle_edge_irq); + irq_set_nested_thread(virq, 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(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static struct irq_domain_ops wm831x_irq_domain_ops = { + .map = wm831x_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +int wm831x_irq_init(struct wm831x *wm831x, int irq) +{ + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct irq_domain *domain; + int i, ret, irq_base; + + mutex_init(&wm831x->irq_lock); + + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { + wm831x->irq_masks_cur[i] = 0xffff; + wm831x->irq_masks_cache[i] = 0xffff; + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + + /* Try to dynamically allocate IRQs if no base is specified */ + if (pdata && pdata->irq_base) { + irq_base = irq_alloc_descs(pdata->irq_base, 0, + WM831X_NUM_IRQS, 0); + if (irq_base < 0) { + dev_warn(wm831x->dev, "Failed to allocate IRQs: %d\n", + irq_base); + irq_base = 0; + } + } else { + irq_base = 0; + } + + if (irq_base) + domain = irq_domain_add_legacy(wm831x->dev->of_node, + ARRAY_SIZE(wm831x_irqs), + irq_base, 0, + &wm831x_irq_domain_ops, + wm831x); + else + domain = irq_domain_add_linear(wm831x->dev->of_node, + ARRAY_SIZE(wm831x_irqs), + &wm831x_irq_domain_ops, + wm831x); + + if (!domain) { + dev_warn(wm831x->dev, "Failed to allocate IRQ domain\n"); + return -EINVAL; + } + + if (pdata && pdata->irq_cmos) + i = 0; + else + i = WM831X_IRQ_OD; + + wm831x_set_bits(wm831x, WM831X_IRQ_CONFIG, + WM831X_IRQ_OD, i); + + wm831x->irq = irq; + wm831x->irq_domain = domain; + + if (irq) { + /* Try to flag /IRQ as a wake source; there are a number of + * unconditional wake sources in the PMIC so this isn't + * conditional but we don't actually care *too* much if it + * fails. + */ + ret = enable_irq_wake(irq); + if (ret != 0) { + dev_warn(wm831x->dev, + "Can't enable IRQ as wake source: %d\n", + ret); + } + + ret = request_threaded_irq(irq, NULL, wm831x_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "wm831x", wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + } else { + dev_warn(wm831x->dev, + "No interrupt specified - functionality limited\n"); + } + + /* Enable top level interrupts, we mask at secondary level */ + wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0); + + return 0; +} + +void wm831x_irq_exit(struct wm831x *wm831x) +{ + if (wm831x->irq) + free_irq(wm831x->irq, wm831x); +} diff --git a/kernel/drivers/mfd/wm831x-otp.c b/kernel/drivers/mfd/wm831x-otp.c new file mode 100644 index 000000000..b90f3e06b --- /dev/null +++ b/kernel/drivers/mfd/wm831x-otp.c @@ -0,0 +1,91 @@ +/* + * wm831x-otp.c -- OTP for Wolfson WM831x PMICs + * + * Copyright 2009 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/i2c.h> +#include <linux/bcd.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/random.h> + +#include <linux/mfd/wm831x/core.h> +#include <linux/mfd/wm831x/otp.h> + +/* In bytes */ +#define WM831X_UNIQUE_ID_LEN 16 + +/* Read the unique ID from the chip into id */ +static int wm831x_unique_id_read(struct wm831x *wm831x, char *id) +{ + int i, val; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN / 2; i++) { + val = wm831x_reg_read(wm831x, WM831X_UNIQUE_ID_1 + i); + if (val < 0) + return val; + + id[i * 2] = (val >> 8) & 0xff; + id[(i * 2) + 1] = val & 0xff; + } + + return 0; +} + +static ssize_t wm831x_unique_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + int i, rval; + char id[WM831X_UNIQUE_ID_LEN]; + ssize_t ret = 0; + + rval = wm831x_unique_id_read(wm831x, id); + if (rval < 0) + return 0; + + for (i = 0; i < WM831X_UNIQUE_ID_LEN; i++) + ret += sprintf(&buf[ret], "%02x", buf[i]); + + ret += sprintf(&buf[ret], "\n"); + + return ret; +} + +static DEVICE_ATTR(unique_id, 0444, wm831x_unique_id_show, NULL); + +int wm831x_otp_init(struct wm831x *wm831x) +{ + char uuid[WM831X_UNIQUE_ID_LEN]; + int ret; + + ret = device_create_file(wm831x->dev, &dev_attr_unique_id); + if (ret != 0) + dev_err(wm831x->dev, "Unique ID attribute not created: %d\n", + ret); + + ret = wm831x_unique_id_read(wm831x, uuid); + if (ret == 0) + add_device_randomness(uuid, sizeof(uuid)); + else + dev_err(wm831x->dev, "Failed to read UUID: %d\n", ret); + + return ret; +} + +void wm831x_otp_exit(struct wm831x *wm831x) +{ + device_remove_file(wm831x->dev, &dev_attr_unique_id); +} + diff --git a/kernel/drivers/mfd/wm831x-spi.c b/kernel/drivers/mfd/wm831x-spi.c new file mode 100644 index 000000000..b8a5e3b34 --- /dev/null +++ b/kernel/drivers/mfd/wm831x-spi.c @@ -0,0 +1,127 @@ +/* + * wm831x-spi.c -- SPI access for Wolfson WM831x PMICs + * + * Copyright 2009,2010 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/pm.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> +#include <linux/err.h> + +#include <linux/mfd/wm831x/core.h> + +static int wm831x_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct wm831x *wm831x; + enum wm831x_parent type; + int ret; + + type = (enum wm831x_parent)id->driver_data; + + wm831x = devm_kzalloc(&spi->dev, sizeof(struct wm831x), GFP_KERNEL); + if (wm831x == NULL) + return -ENOMEM; + + spi->mode = SPI_MODE_0; + + spi_set_drvdata(spi, wm831x); + wm831x->dev = &spi->dev; + + wm831x->regmap = devm_regmap_init_spi(spi, &wm831x_regmap_config); + if (IS_ERR(wm831x->regmap)) { + ret = PTR_ERR(wm831x->regmap); + dev_err(wm831x->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + return wm831x_device_init(wm831x, type, spi->irq); +} + +static int wm831x_spi_remove(struct spi_device *spi) +{ + struct wm831x *wm831x = spi_get_drvdata(spi); + + wm831x_device_exit(wm831x); + + return 0; +} + +static int wm831x_spi_suspend(struct device *dev) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + + return wm831x_device_suspend(wm831x); +} + +static int wm831x_spi_poweroff(struct device *dev) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + + wm831x_device_shutdown(wm831x); + + return 0; +} + +static const struct dev_pm_ops wm831x_spi_pm = { + .freeze = wm831x_spi_suspend, + .suspend = wm831x_spi_suspend, + .poweroff = wm831x_spi_poweroff, +}; + +static const struct spi_device_id wm831x_spi_ids[] = { + { "wm8310", WM8310 }, + { "wm8311", WM8311 }, + { "wm8312", WM8312 }, + { "wm8320", WM8320 }, + { "wm8321", WM8321 }, + { "wm8325", WM8325 }, + { "wm8326", WM8326 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, wm831x_spi_ids); + +static struct spi_driver wm831x_spi_driver = { + .driver = { + .name = "wm831x", + .owner = THIS_MODULE, + .pm = &wm831x_spi_pm, + }, + .id_table = wm831x_spi_ids, + .probe = wm831x_spi_probe, + .remove = wm831x_spi_remove, +}; + +static int __init wm831x_spi_init(void) +{ + int ret; + + ret = spi_register_driver(&wm831x_spi_driver); + if (ret != 0) + pr_err("Failed to register WM831x SPI driver: %d\n", ret); + + return 0; +} +subsys_initcall(wm831x_spi_init); + +static void __exit wm831x_spi_exit(void) +{ + spi_unregister_driver(&wm831x_spi_driver); +} +module_exit(wm831x_spi_exit); + +MODULE_DESCRIPTION("SPI support for WM831x/2x AudioPlus PMICs"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown"); diff --git a/kernel/drivers/mfd/wm8350-core.c b/kernel/drivers/mfd/wm8350-core.c new file mode 100644 index 000000000..f5124a8ac --- /dev/null +++ b/kernel/drivers/mfd/wm8350-core.c @@ -0,0 +1,470 @@ +/* + * wm8350-core.c -- Device access for Wolfson WM8350 + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood, Mark Brown + * + * 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/slab.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/audio.h> +#include <linux/mfd/wm8350/comparator.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/mfd/wm8350/rtc.h> +#include <linux/mfd/wm8350/supply.h> +#include <linux/mfd/wm8350/wdt.h> + +#define WM8350_CLOCK_CONTROL_1 0x28 +#define WM8350_AIF_TEST 0x74 + +/* debug */ +#define WM8350_BUS_DEBUG 0 +#if WM8350_BUS_DEBUG +#define dump(regs, src) do { \ + int i_; \ + u16 *src_ = src; \ + printk(KERN_DEBUG); \ + for (i_ = 0; i_ < regs; i_++) \ + printk(" 0x%4.4x", *src_++); \ + printk("\n"); \ +} while (0); +#else +#define dump(bytes, src) +#endif + +#define WM8350_LOCK_DEBUG 0 +#if WM8350_LOCK_DEBUG +#define ldbg(format, arg...) printk(format, ## arg) +#else +#define ldbg(format, arg...) +#endif + +/* + * WM8350 Device IO + */ +static DEFINE_MUTEX(reg_lock_mutex); + +/* + * Safe read, modify, write methods + */ +int wm8350_clear_bits(struct wm8350 *wm8350, u16 reg, u16 mask) +{ + return regmap_update_bits(wm8350->regmap, reg, mask, 0); +} +EXPORT_SYMBOL_GPL(wm8350_clear_bits); + +int wm8350_set_bits(struct wm8350 *wm8350, u16 reg, u16 mask) +{ + return regmap_update_bits(wm8350->regmap, reg, mask, mask); +} +EXPORT_SYMBOL_GPL(wm8350_set_bits); + +u16 wm8350_reg_read(struct wm8350 *wm8350, int reg) +{ + unsigned int data; + int err; + + err = regmap_read(wm8350->regmap, reg, &data); + if (err) + dev_err(wm8350->dev, "read from reg R%d failed\n", reg); + + return data; +} +EXPORT_SYMBOL_GPL(wm8350_reg_read); + +int wm8350_reg_write(struct wm8350 *wm8350, int reg, u16 val) +{ + int ret; + + ret = regmap_write(wm8350->regmap, reg, val); + + if (ret) + dev_err(wm8350->dev, "write to reg R%d failed\n", reg); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_write); + +int wm8350_block_read(struct wm8350 *wm8350, int start_reg, int regs, + u16 *dest) +{ + int err = 0; + + err = regmap_bulk_read(wm8350->regmap, start_reg, dest, regs); + if (err) + dev_err(wm8350->dev, "block read starting from R%d failed\n", + start_reg); + + return err; +} +EXPORT_SYMBOL_GPL(wm8350_block_read); + +int wm8350_block_write(struct wm8350 *wm8350, int start_reg, int regs, + u16 *src) +{ + int ret = 0; + + ret = regmap_bulk_write(wm8350->regmap, start_reg, src, regs); + if (ret) + dev_err(wm8350->dev, "block write starting at R%d failed\n", + start_reg); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_block_write); + +/** + * wm8350_reg_lock() + * + * The WM8350 has a hardware lock which can be used to prevent writes to + * some registers (generally those which can cause particularly serious + * problems if misused). This function enables that lock. + */ +int wm8350_reg_lock(struct wm8350 *wm8350) +{ + int ret; + + mutex_lock(®_lock_mutex); + + ldbg(__func__); + + ret = wm8350_reg_write(wm8350, WM8350_SECURITY, WM8350_LOCK_KEY); + if (ret) + dev_err(wm8350->dev, "lock failed\n"); + + wm8350->unlocked = false; + + mutex_unlock(®_lock_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_lock); + +/** + * wm8350_reg_unlock() + * + * The WM8350 has a hardware lock which can be used to prevent writes to + * some registers (generally those which can cause particularly serious + * problems if misused). This function disables that lock so updates + * can be performed. For maximum safety this should be done only when + * required. + */ +int wm8350_reg_unlock(struct wm8350 *wm8350) +{ + int ret; + + mutex_lock(®_lock_mutex); + + ldbg(__func__); + + ret = wm8350_reg_write(wm8350, WM8350_SECURITY, WM8350_UNLOCK_KEY); + if (ret) + dev_err(wm8350->dev, "unlock failed\n"); + + wm8350->unlocked = true; + + mutex_unlock(®_lock_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_unlock); + +int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref) +{ + u16 reg, result = 0; + + if (channel < WM8350_AUXADC_AUX1 || channel > WM8350_AUXADC_TEMP) + return -EINVAL; + if (channel >= WM8350_AUXADC_USB && channel <= WM8350_AUXADC_TEMP + && (scale != 0 || vref != 0)) + return -EINVAL; + + mutex_lock(&wm8350->auxadc_mutex); + + /* Turn on the ADC */ + reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, reg | WM8350_AUXADC_ENA); + + if (scale || vref) { + reg = scale << 13; + reg |= vref << 12; + wm8350_reg_write(wm8350, WM8350_AUX1_READBACK + channel, reg); + } + + reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1); + reg |= 1 << channel | WM8350_AUXADC_POLL; + wm8350_reg_write(wm8350, WM8350_DIGITISER_CONTROL_1, reg); + + /* If a late IRQ left the completion signalled then consume + * the completion. */ + try_wait_for_completion(&wm8350->auxadc_done); + + /* We ignore the result of the completion and just check for a + * conversion result, allowing us to soldier on if the IRQ + * infrastructure is not set up for the chip. */ + wait_for_completion_timeout(&wm8350->auxadc_done, msecs_to_jiffies(5)); + + reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1); + if (reg & WM8350_AUXADC_POLL) + dev_err(wm8350->dev, "adc chn %d read timeout\n", channel); + else + result = wm8350_reg_read(wm8350, + WM8350_AUX1_READBACK + channel); + + /* Turn off the ADC */ + reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, + reg & ~WM8350_AUXADC_ENA); + + mutex_unlock(&wm8350->auxadc_mutex); + + return result & WM8350_AUXADC_DATA1_MASK; +} +EXPORT_SYMBOL_GPL(wm8350_read_auxadc); + +static irqreturn_t wm8350_auxadc_irq(int irq, void *irq_data) +{ + struct wm8350 *wm8350 = irq_data; + + complete(&wm8350->auxadc_done); + + return IRQ_HANDLED; +} + +/* + * Register a client device. This is non-fatal since there is no need to + * fail the entire device init due to a single platform device failing. + */ +static void wm8350_client_dev_register(struct wm8350 *wm8350, + const char *name, + struct platform_device **pdev) +{ + int ret; + + *pdev = platform_device_alloc(name, -1); + if (*pdev == NULL) { + dev_err(wm8350->dev, "Failed to allocate %s\n", name); + return; + } + + (*pdev)->dev.parent = wm8350->dev; + platform_set_drvdata(*pdev, wm8350); + ret = platform_device_add(*pdev); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to register %s: %d\n", name, ret); + platform_device_put(*pdev); + *pdev = NULL; + } +} + +int wm8350_device_init(struct wm8350 *wm8350, int irq, + struct wm8350_platform_data *pdata) +{ + int ret; + unsigned int id1, id2, mask_rev; + unsigned int cust_id, mode, chip_rev; + + dev_set_drvdata(wm8350->dev, wm8350); + + /* get WM8350 revision and config mode */ + ret = regmap_read(wm8350->regmap, WM8350_RESET_ID, &id1); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to read ID: %d\n", ret); + goto err; + } + + ret = regmap_read(wm8350->regmap, WM8350_ID, &id2); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to read ID: %d\n", ret); + goto err; + } + + ret = regmap_read(wm8350->regmap, WM8350_REVISION, &mask_rev); + if (ret != 0) { + dev_err(wm8350->dev, "Failed to read revision: %d\n", ret); + goto err; + } + + if (id1 != 0x6143) { + dev_err(wm8350->dev, + "Device with ID %x is not a WM8350\n", id1); + ret = -ENODEV; + goto err; + } + + mode = (id2 & WM8350_CONF_STS_MASK) >> 10; + cust_id = id2 & WM8350_CUST_ID_MASK; + chip_rev = (id2 & WM8350_CHIP_REV_MASK) >> 12; + dev_info(wm8350->dev, + "CONF_STS %d, CUST_ID %d, MASK_REV %d, CHIP_REV %d\n", + mode, cust_id, mask_rev, chip_rev); + + if (cust_id != 0) { + dev_err(wm8350->dev, "Unsupported CUST_ID\n"); + ret = -ENODEV; + goto err; + } + + switch (mask_rev) { + case 0: + wm8350->pmic.max_dcdc = WM8350_DCDC_6; + wm8350->pmic.max_isink = WM8350_ISINK_B; + + switch (chip_rev) { + case WM8350_REV_E: + dev_info(wm8350->dev, "WM8350 Rev E\n"); + break; + case WM8350_REV_F: + dev_info(wm8350->dev, "WM8350 Rev F\n"); + break; + case WM8350_REV_G: + dev_info(wm8350->dev, "WM8350 Rev G\n"); + wm8350->power.rev_g_coeff = 1; + break; + case WM8350_REV_H: + dev_info(wm8350->dev, "WM8350 Rev H\n"); + wm8350->power.rev_g_coeff = 1; + break; + default: + /* For safety we refuse to run on unknown hardware */ + dev_err(wm8350->dev, "Unknown WM8350 CHIP_REV\n"); + ret = -ENODEV; + goto err; + } + break; + + case 1: + wm8350->pmic.max_dcdc = WM8350_DCDC_4; + wm8350->pmic.max_isink = WM8350_ISINK_A; + + switch (chip_rev) { + case 0: + dev_info(wm8350->dev, "WM8351 Rev A\n"); + wm8350->power.rev_g_coeff = 1; + break; + + case 1: + dev_info(wm8350->dev, "WM8351 Rev B\n"); + wm8350->power.rev_g_coeff = 1; + break; + + default: + dev_err(wm8350->dev, "Unknown WM8351 CHIP_REV\n"); + ret = -ENODEV; + goto err; + } + break; + + case 2: + wm8350->pmic.max_dcdc = WM8350_DCDC_6; + wm8350->pmic.max_isink = WM8350_ISINK_B; + + switch (chip_rev) { + case 0: + dev_info(wm8350->dev, "WM8352 Rev A\n"); + wm8350->power.rev_g_coeff = 1; + break; + + default: + dev_err(wm8350->dev, "Unknown WM8352 CHIP_REV\n"); + ret = -ENODEV; + goto err; + } + break; + + default: + dev_err(wm8350->dev, "Unknown MASK_REV\n"); + ret = -ENODEV; + goto err; + } + + mutex_init(&wm8350->auxadc_mutex); + init_completion(&wm8350->auxadc_done); + + ret = wm8350_irq_init(wm8350, irq, pdata); + if (ret < 0) + goto err; + + if (wm8350->irq_base) { + ret = request_threaded_irq(wm8350->irq_base + + WM8350_IRQ_AUXADC_DATARDY, + NULL, wm8350_auxadc_irq, 0, + "auxadc", wm8350); + if (ret < 0) + dev_warn(wm8350->dev, + "Failed to request AUXADC IRQ: %d\n", ret); + } + + if (pdata && pdata->init) { + ret = pdata->init(wm8350); + if (ret != 0) { + dev_err(wm8350->dev, "Platform init() failed: %d\n", + ret); + goto err_irq; + } + } + + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0x0); + + wm8350_client_dev_register(wm8350, "wm8350-codec", + &(wm8350->codec.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-gpio", + &(wm8350->gpio.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-hwmon", + &(wm8350->hwmon.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-power", + &(wm8350->power.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-rtc", &(wm8350->rtc.pdev)); + wm8350_client_dev_register(wm8350, "wm8350-wdt", &(wm8350->wdt.pdev)); + + return 0; + +err_irq: + wm8350_irq_exit(wm8350); +err: + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_device_init); + +void wm8350_device_exit(struct wm8350 *wm8350) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8350->pmic.led); i++) + platform_device_unregister(wm8350->pmic.led[i].pdev); + + for (i = 0; i < ARRAY_SIZE(wm8350->pmic.pdev); i++) + platform_device_unregister(wm8350->pmic.pdev[i]); + + platform_device_unregister(wm8350->wdt.pdev); + platform_device_unregister(wm8350->rtc.pdev); + platform_device_unregister(wm8350->power.pdev); + platform_device_unregister(wm8350->hwmon.pdev); + platform_device_unregister(wm8350->gpio.pdev); + platform_device_unregister(wm8350->codec.pdev); + + if (wm8350->irq_base) + free_irq(wm8350->irq_base + WM8350_IRQ_AUXADC_DATARDY, wm8350); + + wm8350_irq_exit(wm8350); +} +EXPORT_SYMBOL_GPL(wm8350_device_exit); + +MODULE_DESCRIPTION("WM8350 AudioPlus PMIC core driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/wm8350-gpio.c b/kernel/drivers/mfd/wm8350-gpio.c new file mode 100644 index 000000000..d584f6b4d --- /dev/null +++ b/kernel/drivers/mfd/wm8350-gpio.c @@ -0,0 +1,222 @@ +/* + * wm8350-core.c -- Device access for Wolfson WM8350 + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * 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/errno.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/pmic.h> + +static int gpio_set_dir(struct wm8350 *wm8350, int gpio, int dir) +{ + int ret; + + wm8350_reg_unlock(wm8350); + if (dir == WM8350_GPIO_DIR_OUT) + ret = wm8350_clear_bits(wm8350, + WM8350_GPIO_CONFIGURATION_I_O, + 1 << gpio); + else + ret = wm8350_set_bits(wm8350, + WM8350_GPIO_CONFIGURATION_I_O, + 1 << gpio); + wm8350_reg_lock(wm8350); + return ret; +} + +static int wm8350_gpio_set_debounce(struct wm8350 *wm8350, int gpio, int db) +{ + if (db == WM8350_GPIO_DEBOUNCE_ON) + return wm8350_set_bits(wm8350, WM8350_GPIO_DEBOUNCE, + 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_DEBOUNCE, 1 << gpio); +} + +static int gpio_set_func(struct wm8350 *wm8350, int gpio, int func) +{ + u16 reg; + + wm8350_reg_unlock(wm8350); + switch (gpio) { + case 0: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP0_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 0)); + break; + case 1: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP1_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 4)); + break; + case 2: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP2_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 8)); + break; + case 3: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP3_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 12)); + break; + case 4: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP4_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 0)); + break; + case 5: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP5_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 4)); + break; + case 6: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP6_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 8)); + break; + case 7: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP7_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 12)); + break; + case 8: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP8_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 0)); + break; + case 9: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP9_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 4)); + break; + case 10: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP10_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 8)); + break; + case 11: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP11_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 12)); + break; + case 12: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_4) + & ~WM8350_GP12_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_4, + reg | ((func & 0xf) << 0)); + break; + default: + wm8350_reg_lock(wm8350); + return -EINVAL; + } + + wm8350_reg_lock(wm8350); + return 0; +} + +static int gpio_set_pull_up(struct wm8350 *wm8350, int gpio, int up) +{ + if (up) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PIN_PULL_UP_CONTROL, + 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PIN_PULL_UP_CONTROL, + 1 << gpio); +} + +static int gpio_set_pull_down(struct wm8350 *wm8350, int gpio, int down) +{ + if (down) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PULL_DOWN_CONTROL, + 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PULL_DOWN_CONTROL, + 1 << gpio); +} + +static int gpio_set_polarity(struct wm8350 *wm8350, int gpio, int pol) +{ + if (pol == WM8350_GPIO_ACTIVE_HIGH) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PIN_POLARITY_TYPE, + 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PIN_POLARITY_TYPE, + 1 << gpio); +} + +static int gpio_set_invert(struct wm8350 *wm8350, int gpio, int invert) +{ + if (invert == WM8350_GPIO_INVERT_ON) + return wm8350_set_bits(wm8350, WM8350_GPIO_INT_MODE, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_INT_MODE, 1 << gpio); +} + +int wm8350_gpio_config(struct wm8350 *wm8350, int gpio, int dir, int func, + int pol, int pull, int invert, int debounce) +{ + /* make sure we never pull up and down at the same time */ + if (pull == WM8350_GPIO_PULL_NONE) { + if (gpio_set_pull_up(wm8350, gpio, 0)) + goto err; + if (gpio_set_pull_down(wm8350, gpio, 0)) + goto err; + } else if (pull == WM8350_GPIO_PULL_UP) { + if (gpio_set_pull_down(wm8350, gpio, 0)) + goto err; + if (gpio_set_pull_up(wm8350, gpio, 1)) + goto err; + } else if (pull == WM8350_GPIO_PULL_DOWN) { + if (gpio_set_pull_up(wm8350, gpio, 0)) + goto err; + if (gpio_set_pull_down(wm8350, gpio, 1)) + goto err; + } + + if (gpio_set_invert(wm8350, gpio, invert)) + goto err; + if (gpio_set_polarity(wm8350, gpio, pol)) + goto err; + if (wm8350_gpio_set_debounce(wm8350, gpio, debounce)) + goto err; + if (gpio_set_dir(wm8350, gpio, dir)) + goto err; + return gpio_set_func(wm8350, gpio, func); + +err: + return -EIO; +} +EXPORT_SYMBOL_GPL(wm8350_gpio_config); diff --git a/kernel/drivers/mfd/wm8350-i2c.c b/kernel/drivers/mfd/wm8350-i2c.c new file mode 100644 index 000000000..6a16a8a6f --- /dev/null +++ b/kernel/drivers/mfd/wm8350-i2c.c @@ -0,0 +1,93 @@ +/* + * wm8350-i2c.c -- Generic I2C driver for Wolfson WM8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * linux@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/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +static int wm8350_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8350 *wm8350; + struct wm8350_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret = 0; + + wm8350 = devm_kzalloc(&i2c->dev, sizeof(struct wm8350), GFP_KERNEL); + if (wm8350 == NULL) + return -ENOMEM; + + wm8350->regmap = devm_regmap_init_i2c(i2c, &wm8350_regmap); + if (IS_ERR(wm8350->regmap)) { + ret = PTR_ERR(wm8350->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8350); + wm8350->dev = &i2c->dev; + + return wm8350_device_init(wm8350, i2c->irq, pdata); +} + +static int wm8350_i2c_remove(struct i2c_client *i2c) +{ + struct wm8350 *wm8350 = i2c_get_clientdata(i2c); + + wm8350_device_exit(wm8350); + + return 0; +} + +static const struct i2c_device_id wm8350_i2c_id[] = { + { "wm8350", 0 }, + { "wm8351", 0 }, + { "wm8352", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8350_i2c_id); + + +static struct i2c_driver wm8350_i2c_driver = { + .driver = { + .name = "wm8350", + .owner = THIS_MODULE, + }, + .probe = wm8350_i2c_probe, + .remove = wm8350_i2c_remove, + .id_table = wm8350_i2c_id, +}; + +static int __init wm8350_i2c_init(void) +{ + return i2c_add_driver(&wm8350_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(wm8350_i2c_init); + +static void __exit wm8350_i2c_exit(void) +{ + i2c_del_driver(&wm8350_i2c_driver); +} +module_exit(wm8350_i2c_exit); + +MODULE_DESCRIPTION("I2C support for the WM8350 AudioPlus PMIC"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/mfd/wm8350-irq.c b/kernel/drivers/mfd/wm8350-irq.c new file mode 100644 index 000000000..813ff50f9 --- /dev/null +++ b/kernel/drivers/mfd/wm8350-irq.c @@ -0,0 +1,553 @@ +/* + * wm8350-irq.c -- IRQ support for Wolfson WM8350 + * + * Copyright 2007, 2008, 2009 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood, Mark Brown + * + * 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/bug.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/audio.h> +#include <linux/mfd/wm8350/comparator.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/mfd/wm8350/rtc.h> +#include <linux/mfd/wm8350/supply.h> +#include <linux/mfd/wm8350/wdt.h> + +#define WM8350_INT_OFFSET_1 0 +#define WM8350_INT_OFFSET_2 1 +#define WM8350_POWER_UP_INT_OFFSET 2 +#define WM8350_UNDER_VOLTAGE_INT_OFFSET 3 +#define WM8350_OVER_CURRENT_INT_OFFSET 4 +#define WM8350_GPIO_INT_OFFSET 5 +#define WM8350_COMPARATOR_INT_OFFSET 6 + +struct wm8350_irq_data { + int primary; + int reg; + int mask; + int primary_only; +}; + +static struct wm8350_irq_data wm8350_irqs[] = { + [WM8350_IRQ_OC_LS] = { + .primary = WM8350_OC_INT, + .reg = WM8350_OVER_CURRENT_INT_OFFSET, + .mask = WM8350_OC_LS_EINT, + .primary_only = 1, + }, + [WM8350_IRQ_UV_DC1] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC1_EINT, + }, + [WM8350_IRQ_UV_DC2] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC2_EINT, + }, + [WM8350_IRQ_UV_DC3] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC3_EINT, + }, + [WM8350_IRQ_UV_DC4] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC4_EINT, + }, + [WM8350_IRQ_UV_DC5] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC5_EINT, + }, + [WM8350_IRQ_UV_DC6] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_DC6_EINT, + }, + [WM8350_IRQ_UV_LDO1] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO1_EINT, + }, + [WM8350_IRQ_UV_LDO2] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO2_EINT, + }, + [WM8350_IRQ_UV_LDO3] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO3_EINT, + }, + [WM8350_IRQ_UV_LDO4] = { + .primary = WM8350_UV_INT, + .reg = WM8350_UNDER_VOLTAGE_INT_OFFSET, + .mask = WM8350_UV_LDO4_EINT, + }, + [WM8350_IRQ_CHG_BAT_HOT] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_BAT_HOT_EINT, + }, + [WM8350_IRQ_CHG_BAT_COLD] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_BAT_COLD_EINT, + }, + [WM8350_IRQ_CHG_BAT_FAIL] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_BAT_FAIL_EINT, + }, + [WM8350_IRQ_CHG_TO] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_TO_EINT, + }, + [WM8350_IRQ_CHG_END] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_END_EINT, + }, + [WM8350_IRQ_CHG_START] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_START_EINT, + }, + [WM8350_IRQ_CHG_FAST_RDY] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_FAST_RDY_EINT, + }, + [WM8350_IRQ_CHG_VBATT_LT_3P9] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_VBATT_LT_3P9_EINT, + }, + [WM8350_IRQ_CHG_VBATT_LT_3P1] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_VBATT_LT_3P1_EINT, + }, + [WM8350_IRQ_CHG_VBATT_LT_2P85] = { + .primary = WM8350_CHG_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_CHG_VBATT_LT_2P85_EINT, + }, + [WM8350_IRQ_RTC_ALM] = { + .primary = WM8350_RTC_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_RTC_ALM_EINT, + }, + [WM8350_IRQ_RTC_SEC] = { + .primary = WM8350_RTC_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_RTC_SEC_EINT, + }, + [WM8350_IRQ_RTC_PER] = { + .primary = WM8350_RTC_INT, + .reg = WM8350_INT_OFFSET_1, + .mask = WM8350_RTC_PER_EINT, + }, + [WM8350_IRQ_CS1] = { + .primary = WM8350_CS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_CS1_EINT, + }, + [WM8350_IRQ_CS2] = { + .primary = WM8350_CS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_CS2_EINT, + }, + [WM8350_IRQ_SYS_HYST_COMP_FAIL] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_HYST_COMP_FAIL_EINT, + }, + [WM8350_IRQ_SYS_CHIP_GT115] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_CHIP_GT115_EINT, + }, + [WM8350_IRQ_SYS_CHIP_GT140] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_CHIP_GT140_EINT, + }, + [WM8350_IRQ_SYS_WDOG_TO] = { + .primary = WM8350_SYS_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_SYS_WDOG_TO_EINT, + }, + [WM8350_IRQ_AUXADC_DATARDY] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DATARDY_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP4] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP4_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP3] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP3_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP2] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP2_EINT, + }, + [WM8350_IRQ_AUXADC_DCOMP1] = { + .primary = WM8350_AUXADC_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_AUXADC_DCOMP1_EINT, + }, + [WM8350_IRQ_USB_LIMIT] = { + .primary = WM8350_USB_INT, + .reg = WM8350_INT_OFFSET_2, + .mask = WM8350_USB_LIMIT_EINT, + .primary_only = 1, + }, + [WM8350_IRQ_WKUP_OFF_STATE] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_OFF_STATE_EINT, + }, + [WM8350_IRQ_WKUP_HIB_STATE] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_HIB_STATE_EINT, + }, + [WM8350_IRQ_WKUP_CONV_FAULT] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_CONV_FAULT_EINT, + }, + [WM8350_IRQ_WKUP_WDOG_RST] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_WDOG_RST_EINT, + }, + [WM8350_IRQ_WKUP_GP_PWR_ON] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_GP_PWR_ON_EINT, + }, + [WM8350_IRQ_WKUP_ONKEY] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_ONKEY_EINT, + }, + [WM8350_IRQ_WKUP_GP_WAKEUP] = { + .primary = WM8350_WKUP_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_WKUP_GP_WAKEUP_EINT, + }, + [WM8350_IRQ_CODEC_JCK_DET_L] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_JCK_DET_L_EINT, + }, + [WM8350_IRQ_CODEC_JCK_DET_R] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_JCK_DET_R_EINT, + }, + [WM8350_IRQ_CODEC_MICSCD] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_MICSCD_EINT, + }, + [WM8350_IRQ_CODEC_MICD] = { + .primary = WM8350_CODEC_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_CODEC_MICD_EINT, + }, + [WM8350_IRQ_EXT_USB_FB] = { + .primary = WM8350_EXT_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_EXT_USB_FB_EINT, + }, + [WM8350_IRQ_EXT_WALL_FB] = { + .primary = WM8350_EXT_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_EXT_WALL_FB_EINT, + }, + [WM8350_IRQ_EXT_BAT_FB] = { + .primary = WM8350_EXT_INT, + .reg = WM8350_COMPARATOR_INT_OFFSET, + .mask = WM8350_EXT_BAT_FB_EINT, + }, + [WM8350_IRQ_GPIO(0)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP0_EINT, + }, + [WM8350_IRQ_GPIO(1)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP1_EINT, + }, + [WM8350_IRQ_GPIO(2)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP2_EINT, + }, + [WM8350_IRQ_GPIO(3)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP3_EINT, + }, + [WM8350_IRQ_GPIO(4)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP4_EINT, + }, + [WM8350_IRQ_GPIO(5)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP5_EINT, + }, + [WM8350_IRQ_GPIO(6)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP6_EINT, + }, + [WM8350_IRQ_GPIO(7)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP7_EINT, + }, + [WM8350_IRQ_GPIO(8)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP8_EINT, + }, + [WM8350_IRQ_GPIO(9)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP9_EINT, + }, + [WM8350_IRQ_GPIO(10)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP10_EINT, + }, + [WM8350_IRQ_GPIO(11)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP11_EINT, + }, + [WM8350_IRQ_GPIO(12)] = { + .primary = WM8350_GP_INT, + .reg = WM8350_GPIO_INT_OFFSET, + .mask = WM8350_GP12_EINT, + }, +}; + +static inline struct wm8350_irq_data *irq_to_wm8350_irq(struct wm8350 *wm8350, + int irq) +{ + return &wm8350_irqs[irq - wm8350->irq_base]; +} + +/* + * This is a threaded IRQ handler so can access I2C/SPI. Since all + * interrupts are clear on read 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 wm8350_irq(int irq, void *irq_data) +{ + struct wm8350 *wm8350 = irq_data; + u16 level_one; + u16 sub_reg[WM8350_NUM_IRQ_REGS]; + int read_done[WM8350_NUM_IRQ_REGS]; + struct wm8350_irq_data *data; + int i; + + level_one = wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS) + & ~wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK); + + if (!level_one) + return IRQ_NONE; + + memset(&read_done, 0, sizeof(read_done)); + + for (i = 0; i < ARRAY_SIZE(wm8350_irqs); i++) { + data = &wm8350_irqs[i]; + + if (!(level_one & data->primary)) + continue; + + if (!read_done[data->reg]) { + sub_reg[data->reg] = + wm8350_reg_read(wm8350, WM8350_INT_STATUS_1 + + data->reg); + sub_reg[data->reg] &= ~wm8350->irq_masks[data->reg]; + read_done[data->reg] = 1; + } + + if (sub_reg[data->reg] & data->mask) + handle_nested_irq(wm8350->irq_base + i); + } + + return IRQ_HANDLED; +} + +static void wm8350_irq_lock(struct irq_data *data) +{ + struct wm8350 *wm8350 = irq_data_get_irq_chip_data(data); + + mutex_lock(&wm8350->irq_lock); +} + +static void wm8350_irq_sync_unlock(struct irq_data *data) +{ + struct wm8350 *wm8350 = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) { + /* If there's been a change in the mask write it back + * to the hardware. */ + WARN_ON(regmap_update_bits(wm8350->regmap, + WM8350_INT_STATUS_1_MASK + i, + 0xffff, wm8350->irq_masks[i])); + } + + mutex_unlock(&wm8350->irq_lock); +} + +static void wm8350_irq_enable(struct irq_data *data) +{ + struct wm8350 *wm8350 = irq_data_get_irq_chip_data(data); + struct wm8350_irq_data *irq_data = irq_to_wm8350_irq(wm8350, + data->irq); + + wm8350->irq_masks[irq_data->reg] &= ~irq_data->mask; +} + +static void wm8350_irq_disable(struct irq_data *data) +{ + struct wm8350 *wm8350 = irq_data_get_irq_chip_data(data); + struct wm8350_irq_data *irq_data = irq_to_wm8350_irq(wm8350, + data->irq); + + wm8350->irq_masks[irq_data->reg] |= irq_data->mask; +} + +static struct irq_chip wm8350_irq_chip = { + .name = "wm8350", + .irq_bus_lock = wm8350_irq_lock, + .irq_bus_sync_unlock = wm8350_irq_sync_unlock, + .irq_disable = wm8350_irq_disable, + .irq_enable = wm8350_irq_enable, +}; + +int wm8350_irq_init(struct wm8350 *wm8350, int irq, + struct wm8350_platform_data *pdata) +{ + 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; + } + + /* Mask top level interrupts */ + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0xFFFF); + + /* Mask all individual interrupts by default and cache the + * masks. We read the masks back since there are unwritable + * bits in the mask registers. */ + for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) { + wm8350_reg_write(wm8350, WM8350_INT_STATUS_1_MASK + i, + 0xFFFF); + wm8350->irq_masks[i] = + wm8350_reg_read(wm8350, + WM8350_INT_STATUS_1_MASK + i); + } + + mutex_init(&wm8350->irq_lock); + wm8350->chip_irq = irq; + + 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, + WM8350_IRQ_POL); + } else { + flags |= IRQF_TRIGGER_LOW; + + wm8350_clear_bits(wm8350, WM8350_SYSTEM_CONTROL_1, + WM8350_IRQ_POL); + } + + /* Register with genirq */ + for (cur_irq = wm8350->irq_base; + cur_irq < ARRAY_SIZE(wm8350_irqs) + wm8350->irq_base; + cur_irq++) { + irq_set_chip_data(cur_irq, wm8350); + irq_set_chip_and_handler(cur_irq, &wm8350_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, wm8350_irq, flags, + "wm8350", wm8350); + if (ret != 0) + dev_err(wm8350->dev, "Failed to request IRQ: %d\n", ret); + + /* Allow interrupts to fire */ + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0); + + return ret; +} + +int wm8350_irq_exit(struct wm8350 *wm8350) +{ + free_irq(wm8350->chip_irq, wm8350); + return 0; +} diff --git a/kernel/drivers/mfd/wm8350-regmap.c b/kernel/drivers/mfd/wm8350-regmap.c new file mode 100644 index 000000000..9efc64750 --- /dev/null +++ b/kernel/drivers/mfd/wm8350-regmap.c @@ -0,0 +1,339 @@ +/* + * wm8350-regmap.c -- Wolfson Microelectronics WM8350 register map + * + * This file splits out the tables describing the defaults and access + * status of the WM8350 registers since they are rather large. + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * 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/mfd/wm8350/core.h> + +/* + * Access masks. + */ + +static const struct wm8350_reg_access { + u16 readable; /* Mask of readable bits */ + u16 writable; /* Mask of writable bits */ + u16 vol; /* Mask of volatile bits */ +} wm8350_reg_io_map[] = { + /* read write volatile */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R0 - Reset/ID */ + { 0x7CFF, 0x0C00, 0x0000 }, /* R1 - ID */ + { 0x007F, 0x0000, 0x0000 }, /* R2 - ROM Mask ID */ + { 0xBE3B, 0xBE3B, 0x8000 }, /* R3 - System Control 1 */ + { 0xFEF7, 0xFEF7, 0xF800 }, /* R4 - System Control 2 */ + { 0x80FF, 0x80FF, 0x8000 }, /* R5 - System Hibernate */ + { 0xFB0E, 0xFB0E, 0x0000 }, /* R6 - Interface Control */ + { 0x0000, 0x0000, 0x0000 }, /* R7 */ + { 0xE537, 0xE537, 0xFFFF }, /* R8 - Power mgmt (1) */ + { 0x0FF3, 0x0FF3, 0xFFFF }, /* R9 - Power mgmt (2) */ + { 0x008F, 0x008F, 0xFFFF }, /* R10 - Power mgmt (3) */ + { 0x6D3C, 0x6D3C, 0xFFFF }, /* R11 - Power mgmt (4) */ + { 0x1F8F, 0x1F8F, 0xFFFF }, /* R12 - Power mgmt (5) */ + { 0x8F3F, 0x8F3F, 0xFFFF }, /* R13 - Power mgmt (6) */ + { 0x0003, 0x0003, 0xFFFF }, /* R14 - Power mgmt (7) */ + { 0x0000, 0x0000, 0x0000 }, /* R15 */ + { 0x7F7F, 0x7F7F, 0xFFFF }, /* R16 - RTC Seconds/Minutes */ + { 0x073F, 0x073F, 0xFFFF }, /* R17 - RTC Hours/Day */ + { 0x1F3F, 0x1F3F, 0xFFFF }, /* R18 - RTC Date/Month */ + { 0x3FFF, 0x00FF, 0xFFFF }, /* R19 - RTC Year */ + { 0x7F7F, 0x7F7F, 0x0000 }, /* R20 - Alarm Seconds/Minutes */ + { 0x0F3F, 0x0F3F, 0x0000 }, /* R21 - Alarm Hours/Day */ + { 0x1F3F, 0x1F3F, 0x0000 }, /* R22 - Alarm Date/Month */ + { 0xEF7F, 0xEA7F, 0xFFFF }, /* R23 - RTC Time Control */ + { 0x3BFF, 0x0000, 0xFFFF }, /* R24 - System Interrupts */ + { 0xFEE7, 0x0000, 0xFFFF }, /* R25 - Interrupt Status 1 */ + { 0x35FF, 0x0000, 0xFFFF }, /* R26 - Interrupt Status 2 */ + { 0x0F3F, 0x0000, 0xFFFF }, /* R27 - Power Up Interrupt Status */ + { 0x0F3F, 0x0000, 0xFFFF }, /* R28 - Under Voltage Interrupt status */ + { 0x8000, 0x0000, 0xFFFF }, /* R29 - Over Current Interrupt status */ + { 0x1FFF, 0x0000, 0xFFFF }, /* R30 - GPIO Interrupt Status */ + { 0xEF7F, 0x0000, 0xFFFF }, /* R31 - Comparator Interrupt Status */ + { 0x3FFF, 0x3FFF, 0x0000 }, /* R32 - System Interrupts Mask */ + { 0xFEE7, 0xFEE7, 0x0000 }, /* R33 - Interrupt Status 1 Mask */ + { 0xF5FF, 0xF5FF, 0x0000 }, /* R34 - Interrupt Status 2 Mask */ + { 0x0F3F, 0x0F3F, 0x0000 }, /* R35 - Power Up Interrupt Status Mask */ + { 0x0F3F, 0x0F3F, 0x0000 }, /* R36 - Under Voltage Int status Mask */ + { 0x8000, 0x8000, 0x0000 }, /* R37 - Over Current Int status Mask */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R38 - GPIO Interrupt Status Mask */ + { 0xEF7F, 0xEF7F, 0x0000 }, /* R39 - Comparator IntStatus Mask */ + { 0xC9F7, 0xC9F7, 0xFFFF }, /* R40 - Clock Control 1 */ + { 0x8001, 0x8001, 0x0000 }, /* R41 - Clock Control 2 */ + { 0xFFF7, 0xFFF7, 0xFFFF }, /* R42 - FLL Control 1 */ + { 0xFBFF, 0xFBFF, 0x0000 }, /* R43 - FLL Control 2 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R44 - FLL Control 3 */ + { 0x0033, 0x0033, 0x0000 }, /* R45 - FLL Control 4 */ + { 0x0000, 0x0000, 0x0000 }, /* R46 */ + { 0x0000, 0x0000, 0x0000 }, /* R47 */ + { 0x3033, 0x3033, 0x0000 }, /* R48 - DAC Control */ + { 0x0000, 0x0000, 0x0000 }, /* R49 */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R50 - DAC Digital Volume L */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R51 - DAC Digital Volume R */ + { 0x0000, 0x0000, 0x0000 }, /* R52 */ + { 0x0FFF, 0x0FFF, 0xFFFF }, /* R53 - DAC LR Rate */ + { 0x0017, 0x0017, 0x0000 }, /* R54 - DAC Clock Control */ + { 0x0000, 0x0000, 0x0000 }, /* R55 */ + { 0x0000, 0x0000, 0x0000 }, /* R56 */ + { 0x0000, 0x0000, 0x0000 }, /* R57 */ + { 0x4000, 0x4000, 0x0000 }, /* R58 - DAC Mute */ + { 0x7000, 0x7000, 0x0000 }, /* R59 - DAC Mute Volume */ + { 0x3C00, 0x3C00, 0x0000 }, /* R60 - DAC Side */ + { 0x0000, 0x0000, 0x0000 }, /* R61 */ + { 0x0000, 0x0000, 0x0000 }, /* R62 */ + { 0x0000, 0x0000, 0x0000 }, /* R63 */ + { 0x8303, 0x8303, 0xFFFF }, /* R64 - ADC Control */ + { 0x0000, 0x0000, 0x0000 }, /* R65 */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R66 - ADC Digital Volume L */ + { 0x81FF, 0x81FF, 0xFFFF }, /* R67 - ADC Digital Volume R */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R68 - ADC Divider */ + { 0x0000, 0x0000, 0x0000 }, /* R69 */ + { 0x0FFF, 0x0FFF, 0xFFFF }, /* R70 - ADC LR Rate */ + { 0x0000, 0x0000, 0x0000 }, /* R71 */ + { 0x0707, 0x0707, 0xFFFF }, /* R72 - Input Control */ + { 0xC0C0, 0xC0C0, 0xFFFF }, /* R73 - IN3 Input Control */ + { 0xC09F, 0xC09F, 0xFFFF }, /* R74 - Mic Bias Control */ + { 0x0000, 0x0000, 0x0000 }, /* R75 */ + { 0x0F15, 0x0F15, 0xFFFF }, /* R76 - Output Control */ + { 0xC000, 0xC000, 0xFFFF }, /* R77 - Jack Detect */ + { 0x03FF, 0x03FF, 0x0000 }, /* R78 - Anti Pop Control */ + { 0x0000, 0x0000, 0x0000 }, /* R79 */ + { 0xE1FC, 0xE1FC, 0x8000 }, /* R80 - Left Input Volume */ + { 0xE1FC, 0xE1FC, 0x8000 }, /* R81 - Right Input Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R82 */ + { 0x0000, 0x0000, 0x0000 }, /* R83 */ + { 0x0000, 0x0000, 0x0000 }, /* R84 */ + { 0x0000, 0x0000, 0x0000 }, /* R85 */ + { 0x0000, 0x0000, 0x0000 }, /* R86 */ + { 0x0000, 0x0000, 0x0000 }, /* R87 */ + { 0x9807, 0x9807, 0xFFFF }, /* R88 - Left Mixer Control */ + { 0x980B, 0x980B, 0xFFFF }, /* R89 - Right Mixer Control */ + { 0x0000, 0x0000, 0x0000 }, /* R90 */ + { 0x0000, 0x0000, 0x0000 }, /* R91 */ + { 0x8909, 0x8909, 0xFFFF }, /* R92 - OUT3 Mixer Control */ + { 0x9E07, 0x9E07, 0xFFFF }, /* R93 - OUT4 Mixer Control */ + { 0x0000, 0x0000, 0x0000 }, /* R94 */ + { 0x0000, 0x0000, 0x0000 }, /* R95 */ + { 0x0EEE, 0x0EEE, 0x0000 }, /* R96 - Output Left Mixer Volume */ + { 0xE0EE, 0xE0EE, 0x0000 }, /* R97 - Output Right Mixer Volume */ + { 0x0E0F, 0x0E0F, 0x0000 }, /* R98 - Input Mixer Volume L */ + { 0xE0E1, 0xE0E1, 0x0000 }, /* R99 - Input Mixer Volume R */ + { 0x800E, 0x800E, 0x0000 }, /* R100 - Input Mixer Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R101 */ + { 0x0000, 0x0000, 0x0000 }, /* R102 */ + { 0x0000, 0x0000, 0x0000 }, /* R103 */ + { 0xE1FC, 0xE1FC, 0xFFFF }, /* R104 - LOUT1 Volume */ + { 0xE1FC, 0xE1FC, 0xFFFF }, /* R105 - ROUT1 Volume */ + { 0xE1FC, 0xE1FC, 0xFFFF }, /* R106 - LOUT2 Volume */ + { 0xE7FC, 0xE7FC, 0xFFFF }, /* R107 - ROUT2 Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R108 */ + { 0x0000, 0x0000, 0x0000 }, /* R109 */ + { 0x0000, 0x0000, 0x0000 }, /* R110 */ + { 0x80E0, 0x80E0, 0xFFFF }, /* R111 - BEEP Volume */ + { 0xBF00, 0xBF00, 0x0000 }, /* R112 - AI Formating */ + { 0x00F1, 0x00F1, 0x0000 }, /* R113 - ADC DAC COMP */ + { 0x00F8, 0x00F8, 0x0000 }, /* R114 - AI ADC Control */ + { 0x40FB, 0x40FB, 0x0000 }, /* R115 - AI DAC Control */ + { 0x7C30, 0x7C30, 0x0000 }, /* R116 - AIF Test */ + { 0x0000, 0x0000, 0x0000 }, /* R117 */ + { 0x0000, 0x0000, 0x0000 }, /* R118 */ + { 0x0000, 0x0000, 0x0000 }, /* R119 */ + { 0x0000, 0x0000, 0x0000 }, /* R120 */ + { 0x0000, 0x0000, 0x0000 }, /* R121 */ + { 0x0000, 0x0000, 0x0000 }, /* R122 */ + { 0x0000, 0x0000, 0x0000 }, /* R123 */ + { 0x0000, 0x0000, 0x0000 }, /* R124 */ + { 0x0000, 0x0000, 0x0000 }, /* R125 */ + { 0x0000, 0x0000, 0x0000 }, /* R126 */ + { 0x0000, 0x0000, 0x0000 }, /* R127 */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R128 - GPIO Debounce */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R129 - GPIO Pin pull up Control */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R130 - GPIO Pull down Control */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R131 - GPIO Interrupt Mode */ + { 0x0000, 0x0000, 0x0000 }, /* R132 */ + { 0x00C0, 0x00C0, 0x0000 }, /* R133 - GPIO Control */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R134 - GPIO Configuration (i/o) */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R135 - GPIO Pin Polarity / Type */ + { 0x0000, 0x0000, 0x0000 }, /* R136 */ + { 0x0000, 0x0000, 0x0000 }, /* R137 */ + { 0x0000, 0x0000, 0x0000 }, /* R138 */ + { 0x0000, 0x0000, 0x0000 }, /* R139 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R140 - GPIO Function Select 1 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R141 - GPIO Function Select 2 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R142 - GPIO Function Select 3 */ + { 0x000F, 0x000F, 0x0000 }, /* R143 - GPIO Function Select 4 */ + { 0xF0FF, 0xF0FF, 0xA000 }, /* R144 - Digitiser Control (1) */ + { 0x3707, 0x3707, 0x0000 }, /* R145 - Digitiser Control (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R146 */ + { 0x0000, 0x0000, 0x0000 }, /* R147 */ + { 0x0000, 0x0000, 0x0000 }, /* R148 */ + { 0x0000, 0x0000, 0x0000 }, /* R149 */ + { 0x0000, 0x0000, 0x0000 }, /* R150 */ + { 0x0000, 0x0000, 0x0000 }, /* R151 */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R152 - AUX1 Readback */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R153 - AUX2 Readback */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R154 - AUX3 Readback */ + { 0x7FFF, 0x7000, 0xFFFF }, /* R155 - AUX4 Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R156 - USB Voltage Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R157 - LINE Voltage Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R158 - BATT Voltage Readback */ + { 0x0FFF, 0x0000, 0xFFFF }, /* R159 - Chip Temp Readback */ + { 0x0000, 0x0000, 0x0000 }, /* R160 */ + { 0x0000, 0x0000, 0x0000 }, /* R161 */ + { 0x0000, 0x0000, 0x0000 }, /* R162 */ + { 0x000F, 0x000F, 0x0000 }, /* R163 - Generic Comparator Control */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R164 - Generic comparator 1 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R165 - Generic comparator 2 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R166 - Generic comparator 3 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R167 - Generic comparator 4 */ + { 0xBFFF, 0xBFFF, 0x8000 }, /* R168 - Battery Charger Control 1 */ + { 0xFFFF, 0x4FFF, 0xB000 }, /* R169 - Battery Charger Control 2 */ + { 0x007F, 0x007F, 0x0000 }, /* R170 - Battery Charger Control 3 */ + { 0x0000, 0x0000, 0x0000 }, /* R171 */ + { 0x903F, 0x903F, 0xFFFF }, /* R172 - Current Sink Driver A */ + { 0xE333, 0xE333, 0xFFFF }, /* R173 - CSA Flash control */ + { 0x903F, 0x903F, 0xFFFF }, /* R174 - Current Sink Driver B */ + { 0xE333, 0xE333, 0xFFFF }, /* R175 - CSB Flash control */ + { 0x8F3F, 0x8F3F, 0xFFFF }, /* R176 - DCDC/LDO requested */ + { 0x332D, 0x332D, 0x0000 }, /* R177 - DCDC Active options */ + { 0x002D, 0x002D, 0x0000 }, /* R178 - DCDC Sleep options */ + { 0x5177, 0x5177, 0x8000 }, /* R179 - Power-check comparator */ + { 0x047F, 0x047F, 0x0000 }, /* R180 - DCDC1 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R181 - DCDC1 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R182 - DCDC1 Low Power */ + { 0x535B, 0x535B, 0x0000 }, /* R183 - DCDC2 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R184 - DCDC2 Timeouts */ + { 0x0000, 0x0000, 0x0000 }, /* R185 */ + { 0x047F, 0x047F, 0x0000 }, /* R186 - DCDC3 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R187 - DCDC3 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R188 - DCDC3 Low Power */ + { 0x047F, 0x047F, 0x0000 }, /* R189 - DCDC4 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R190 - DCDC4 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R191 - DCDC4 Low Power */ + { 0x535B, 0x535B, 0x0000 }, /* R192 - DCDC5 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R193 - DCDC5 Timeouts */ + { 0x0000, 0x0000, 0x0000 }, /* R194 */ + { 0x047F, 0x047F, 0x0000 }, /* R195 - DCDC6 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R196 - DCDC6 Timeouts */ + { 0x737F, 0x737F, 0x0000 }, /* R197 - DCDC6 Low Power */ + { 0x0000, 0x0000, 0x0000 }, /* R198 */ + { 0xFFD3, 0xFFD3, 0x0000 }, /* R199 - Limit Switch Control */ + { 0x441F, 0x441F, 0x0000 }, /* R200 - LDO1 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R201 - LDO1 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R202 - LDO1 Low Power */ + { 0x441F, 0x441F, 0x0000 }, /* R203 - LDO2 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R204 - LDO2 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R205 - LDO2 Low Power */ + { 0x441F, 0x441F, 0x0000 }, /* R206 - LDO3 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R207 - LDO3 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R208 - LDO3 Low Power */ + { 0x441F, 0x441F, 0x0000 }, /* R209 - LDO4 Control */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R210 - LDO4 Timeouts */ + { 0x331F, 0x331F, 0x0000 }, /* R211 - LDO4 Low Power */ + { 0x0000, 0x0000, 0x0000 }, /* R212 */ + { 0x0000, 0x0000, 0x0000 }, /* R213 */ + { 0x0000, 0x0000, 0x0000 }, /* R214 */ + { 0x8F3F, 0x8F3F, 0x0000 }, /* R215 - VCC_FAULT Masks */ + { 0xFF3F, 0xE03F, 0x0000 }, /* R216 - Main Bandgap Control */ + { 0xEF2F, 0xE02F, 0x0000 }, /* R217 - OSC Control */ + { 0xF3FF, 0xB3FF, 0xc000 }, /* R218 - RTC Tick Control */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R219 - Security */ + { 0x09FF, 0x01FF, 0x0000 }, /* R220 - RAM BIST 1 */ + { 0x0000, 0x0000, 0x0000 }, /* R221 */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* R222 */ + { 0xFFFF, 0xFFFF, 0xFFFF }, /* R223 */ + { 0x0000, 0x0000, 0x0000 }, /* R224 */ + { 0x8F3F, 0x0000, 0xFFFF }, /* R225 - DCDC/LDO status */ + { 0x0000, 0x0000, 0xFFFF }, /* R226 - Charger status */ + { 0x34FE, 0x0000, 0xFFFF }, /* R227 */ + { 0x0000, 0x0000, 0x0000 }, /* R228 */ + { 0x0000, 0x0000, 0x0000 }, /* R229 */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R230 - GPIO Pin Status */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R231 */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R232 */ + { 0xFFFF, 0x1FFF, 0xFFFF }, /* R233 */ + { 0x0000, 0x0000, 0x0000 }, /* R234 */ + { 0x0000, 0x0000, 0x0000 }, /* R235 */ + { 0x0000, 0x0000, 0x0000 }, /* R236 */ + { 0x0000, 0x0000, 0x0000 }, /* R237 */ + { 0x0000, 0x0000, 0x0000 }, /* R238 */ + { 0x0000, 0x0000, 0x0000 }, /* R239 */ + { 0x0000, 0x0000, 0x0000 }, /* R240 */ + { 0x0000, 0x0000, 0x0000 }, /* R241 */ + { 0x0000, 0x0000, 0x0000 }, /* R242 */ + { 0x0000, 0x0000, 0x0000 }, /* R243 */ + { 0x0000, 0x0000, 0x0000 }, /* R244 */ + { 0x0000, 0x0000, 0x0000 }, /* R245 */ + { 0x0000, 0x0000, 0x0000 }, /* R246 */ + { 0x0000, 0x0000, 0x0000 }, /* R247 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R248 */ + { 0x0000, 0x0000, 0x0000 }, /* R249 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R250 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R251 */ + { 0x0000, 0x0000, 0x0000 }, /* R252 */ + { 0xFFFF, 0x0010, 0xFFFF }, /* R253 */ + { 0x0000, 0x0000, 0x0000 }, /* R254 */ + { 0x0000, 0x0000, 0x0000 }, /* R255 */ +}; + +static bool wm8350_readable(struct device *dev, unsigned int reg) +{ + return wm8350_reg_io_map[reg].readable; +} + +static bool wm8350_writeable(struct device *dev, unsigned int reg) +{ + struct wm8350 *wm8350 = dev_get_drvdata(dev); + + if (!wm8350->unlocked) { + if ((reg >= WM8350_GPIO_FUNCTION_SELECT_1 && + reg <= WM8350_GPIO_FUNCTION_SELECT_4) || + (reg >= WM8350_BATTERY_CHARGER_CONTROL_1 && + reg <= WM8350_BATTERY_CHARGER_CONTROL_3)) + return false; + } + + return wm8350_reg_io_map[reg].writable; +} + +static bool wm8350_volatile(struct device *dev, unsigned int reg) +{ + return wm8350_reg_io_map[reg].vol; +} + +static bool wm8350_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8350_SYSTEM_INTERRUPTS: + case WM8350_INT_STATUS_1: + case WM8350_INT_STATUS_2: + case WM8350_POWER_UP_INT_STATUS: + case WM8350_UNDER_VOLTAGE_INT_STATUS: + case WM8350_OVER_CURRENT_INT_STATUS: + case WM8350_GPIO_INT_STATUS: + case WM8350_COMPARATOR_INT_STATUS: + return true; + + default: + return false; + } +} + +const struct regmap_config wm8350_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + + .max_register = WM8350_MAX_REGISTER, + .readable_reg = wm8350_readable, + .writeable_reg = wm8350_writeable, + .volatile_reg = wm8350_volatile, + .precious_reg = wm8350_precious, +}; diff --git a/kernel/drivers/mfd/wm8400-core.c b/kernel/drivers/mfd/wm8400-core.c new file mode 100644 index 000000000..c6fb5d16c --- /dev/null +++ b/kernel/drivers/mfd/wm8400-core.c @@ -0,0 +1,228 @@ +/* + * Core driver for WM8400. + * + * Copyright 2008 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/module.h> +#include <linux/bug.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/mfd/core.h> +#include <linux/mfd/wm8400-private.h> +#include <linux/mfd/wm8400-audio.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +static bool wm8400_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8400_INTERRUPT_STATUS_1: + case WM8400_INTERRUPT_LEVELS: + case WM8400_SHUTDOWN_REASON: + return true; + default: + return false; + } +} + +/** + * wm8400_reg_read - Single register read + * + * @wm8400: Pointer to wm8400 control structure + * @reg: Register to read + * + * @return Read value + */ +u16 wm8400_reg_read(struct wm8400 *wm8400, u8 reg) +{ + unsigned int val; + int ret; + + ret = regmap_read(wm8400->regmap, reg, &val); + if (ret < 0) + return ret; + + return val; +} +EXPORT_SYMBOL_GPL(wm8400_reg_read); + +int wm8400_block_read(struct wm8400 *wm8400, u8 reg, int count, u16 *data) +{ + return regmap_bulk_read(wm8400->regmap, reg, data, count); +} +EXPORT_SYMBOL_GPL(wm8400_block_read); + +static int wm8400_register_codec(struct wm8400 *wm8400) +{ + const struct mfd_cell cell = { + .name = "wm8400-codec", + .platform_data = wm8400, + .pdata_size = sizeof(*wm8400), + }; + + return mfd_add_devices(wm8400->dev, -1, &cell, 1, NULL, 0, NULL); +} + +/* + * wm8400_init - Generic initialisation + * + * The WM8400 can be configured as either an I2C or SPI device. Probe + * functions for each bus set up the accessors then call into this to + * set up the device itself. + */ +static int wm8400_init(struct wm8400 *wm8400, + struct wm8400_platform_data *pdata) +{ + unsigned int reg; + int ret; + + dev_set_drvdata(wm8400->dev, wm8400); + + /* Check that this is actually a WM8400 */ + ret = regmap_read(wm8400->regmap, WM8400_RESET_ID, ®); + if (ret != 0) { + dev_err(wm8400->dev, "Chip ID register read failed\n"); + return -EIO; + } + if (reg != 0x6172) { + dev_err(wm8400->dev, "Device is not a WM8400, ID is %x\n", + reg); + return -ENODEV; + } + + ret = regmap_read(wm8400->regmap, WM8400_ID, ®); + if (ret != 0) { + dev_err(wm8400->dev, "ID register read failed: %d\n", ret); + return ret; + } + reg = (reg & WM8400_CHIP_REV_MASK) >> WM8400_CHIP_REV_SHIFT; + dev_info(wm8400->dev, "WM8400 revision %x\n", reg); + + ret = wm8400_register_codec(wm8400); + if (ret != 0) { + dev_err(wm8400->dev, "Failed to register codec\n"); + goto err_children; + } + + if (pdata && pdata->platform_init) { + ret = pdata->platform_init(wm8400->dev); + if (ret != 0) { + dev_err(wm8400->dev, "Platform init failed: %d\n", + ret); + goto err_children; + } + } else + dev_warn(wm8400->dev, "No platform initialisation supplied\n"); + + return 0; + +err_children: + mfd_remove_devices(wm8400->dev); + return ret; +} + +static void wm8400_release(struct wm8400 *wm8400) +{ + mfd_remove_devices(wm8400->dev); +} + +static const struct regmap_config wm8400_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = WM8400_REGISTER_COUNT - 1, + + .volatile_reg = wm8400_volatile, + + .cache_type = REGCACHE_RBTREE, +}; + +/** + * wm8400_reset_codec_reg_cache - Reset cached codec registers to + * their default values. + */ +void wm8400_reset_codec_reg_cache(struct wm8400 *wm8400) +{ + regmap_reinit_cache(wm8400->regmap, &wm8400_regmap_config); +} +EXPORT_SYMBOL_GPL(wm8400_reset_codec_reg_cache); + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int wm8400_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8400 *wm8400; + + wm8400 = devm_kzalloc(&i2c->dev, sizeof(struct wm8400), GFP_KERNEL); + if (!wm8400) + return -ENOMEM; + + wm8400->regmap = devm_regmap_init_i2c(i2c, &wm8400_regmap_config); + if (IS_ERR(wm8400->regmap)) + return PTR_ERR(wm8400->regmap); + + wm8400->dev = &i2c->dev; + i2c_set_clientdata(i2c, wm8400); + + return wm8400_init(wm8400, dev_get_platdata(&i2c->dev)); +} + +static int wm8400_i2c_remove(struct i2c_client *i2c) +{ + struct wm8400 *wm8400 = i2c_get_clientdata(i2c); + + wm8400_release(wm8400); + + return 0; +} + +static const struct i2c_device_id wm8400_i2c_id[] = { + { "wm8400", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8400_i2c_id); + +static struct i2c_driver wm8400_i2c_driver = { + .driver = { + .name = "WM8400", + .owner = THIS_MODULE, + }, + .probe = wm8400_i2c_probe, + .remove = wm8400_i2c_remove, + .id_table = wm8400_i2c_id, +}; +#endif + +static int __init wm8400_module_init(void) +{ + int ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8400_i2c_driver); + if (ret != 0) + pr_err("Failed to register I2C driver: %d\n", ret); +#endif + + return ret; +} +subsys_initcall(wm8400_module_init); + +static void __exit wm8400_module_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8400_i2c_driver); +#endif +} +module_exit(wm8400_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); diff --git a/kernel/drivers/mfd/wm8994-core.c b/kernel/drivers/mfd/wm8994-core.c new file mode 100644 index 000000000..53ae5af5d --- /dev/null +++ b/kernel/drivers/mfd/wm8994-core.c @@ -0,0 +1,693 @@ +/* + * wm8994-core.c -- Device access for Wolfson WM8994 + * + * Copyright 2009 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/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/machine.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/registers.h> + +#include "wm8994.h" + +static const struct mfd_cell wm8994_regulator_devs[] = { + { + .name = "wm8994-ldo", + .id = 0, + .pm_runtime_no_callbacks = true, + }, + { + .name = "wm8994-ldo", + .id = 1, + .pm_runtime_no_callbacks = true, + }, +}; + +static struct resource wm8994_codec_resources[] = { + { + .start = WM8994_IRQ_TEMP_SHUT, + .end = WM8994_IRQ_TEMP_WARN, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm8994_gpio_resources[] = { + { + .start = WM8994_IRQ_GPIO(1), + .end = WM8994_IRQ_GPIO(11), + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct mfd_cell wm8994_devs[] = { + { + .name = "wm8994-codec", + .num_resources = ARRAY_SIZE(wm8994_codec_resources), + .resources = wm8994_codec_resources, + }, + + { + .name = "wm8994-gpio", + .num_resources = ARRAY_SIZE(wm8994_gpio_resources), + .resources = wm8994_gpio_resources, + .pm_runtime_no_callbacks = true, + }, +}; + +/* + * Supplies for the main bulk of CODEC; the LDO supplies are ignored + * and should be handled via the standard regulator API supply + * management. + */ +static const char *wm1811_main_supplies[] = { + "DBVDD1", + "DBVDD2", + "DBVDD3", + "DCVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD1", + "SPKVDD2", +}; + +static const char *wm8994_main_supplies[] = { + "DBVDD", + "DCVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD1", + "SPKVDD2", +}; + +static const char *wm8958_main_supplies[] = { + "DBVDD1", + "DBVDD2", + "DBVDD3", + "DCVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD1", + "SPKVDD2", +}; + +#ifdef CONFIG_PM +static int wm8994_suspend(struct device *dev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(dev); + int ret; + + /* Don't actually go through with the suspend if the CODEC is + * still active for accessory detect. */ + switch (wm8994->type) { + case WM8958: + case WM1811: + ret = wm8994_reg_read(wm8994, WM8958_MIC_DETECT_1); + if (ret < 0) { + dev_err(dev, "Failed to read power status: %d\n", ret); + } else if (ret & WM8958_MICD_ENA) { + dev_dbg(dev, "CODEC still active, ignoring suspend\n"); + return 0; + } + break; + default: + break; + } + + /* Disable LDO pulldowns while the device is suspended if we + * don't know that something will be driving them. */ + if (!wm8994->ldo_ena_always_driven) + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD); + + /* Explicitly put the device into reset in case regulators + * don't get disabled in order to ensure consistent restart. + */ + wm8994_reg_write(wm8994, WM8994_SOFTWARE_RESET, + wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET)); + + regcache_mark_dirty(wm8994->regmap); + + /* Restore GPIO registers to prevent problems with mismatched + * pin configurations. + */ + ret = regcache_sync_region(wm8994->regmap, WM8994_GPIO_1, + WM8994_GPIO_11); + if (ret != 0) + dev_err(dev, "Failed to restore GPIO registers: %d\n", ret); + + /* In case one of the GPIOs is used as a wake input. */ + ret = regcache_sync_region(wm8994->regmap, + WM8994_INTERRUPT_STATUS_1_MASK, + WM8994_INTERRUPT_STATUS_1_MASK); + if (ret != 0) + dev_err(dev, "Failed to restore interrupt mask: %d\n", ret); + + regcache_cache_only(wm8994->regmap, true); + wm8994->suspended = true; + + ret = regulator_bulk_disable(wm8994->num_supplies, + wm8994->supplies); + if (ret != 0) { + dev_err(dev, "Failed to disable supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int wm8994_resume(struct device *dev) +{ + struct wm8994 *wm8994 = dev_get_drvdata(dev); + int ret; + + /* We may have lied to the PM core about suspending */ + if (!wm8994->suspended) + return 0; + + ret = regulator_bulk_enable(wm8994->num_supplies, + wm8994->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(wm8994->regmap, false); + ret = regcache_sync(wm8994->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register map: %d\n", ret); + goto err_enable; + } + + /* Disable LDO pulldowns while the device is active */ + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, + 0); + + wm8994->suspended = false; + + return 0; + +err_enable: + regulator_bulk_disable(wm8994->num_supplies, wm8994->supplies); + + return ret; +} +#endif + +#ifdef CONFIG_REGULATOR +static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo) +{ + struct wm8994_ldo_pdata *ldo_pdata; + + if (!pdata) + return 0; + + ldo_pdata = &pdata->ldo[ldo]; + + if (!ldo_pdata->init_data) + return 0; + + return ldo_pdata->init_data->num_consumer_supplies != 0; +} +#else +static int wm8994_ldo_in_use(struct wm8994_pdata *pdata, int ldo) +{ + return 0; +} +#endif + +static const struct reg_default wm8994_revc_patch[] = { + { 0x102, 0x3 }, + { 0x56, 0x3 }, + { 0x817, 0x0 }, + { 0x102, 0x0 }, +}; + +static const struct reg_default wm8958_reva_patch[] = { + { 0x102, 0x3 }, + { 0xcb, 0x81 }, + { 0x817, 0x0 }, + { 0x102, 0x0 }, +}; + +static const struct reg_default wm1811_reva_patch[] = { + { 0x102, 0x3 }, + { 0x56, 0xc07 }, + { 0x5d, 0x7e }, + { 0x5e, 0x0 }, + { 0x102, 0x0 }, +}; + +#ifdef CONFIG_OF +static int wm8994_set_pdata_from_of(struct wm8994 *wm8994) +{ + struct device_node *np = wm8994->dev->of_node; + struct wm8994_pdata *pdata = &wm8994->pdata; + int i; + + if (!np) + return 0; + + if (of_property_read_u32_array(np, "wlf,gpio-cfg", pdata->gpio_defaults, + ARRAY_SIZE(pdata->gpio_defaults)) >= 0) { + for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) { + if (wm8994->pdata.gpio_defaults[i] == 0) + pdata->gpio_defaults[i] + = WM8994_CONFIGURE_GPIO; + } + } + + of_property_read_u32_array(np, "wlf,micbias-cfg", pdata->micbias, + ARRAY_SIZE(pdata->micbias)); + + pdata->lineout1_diff = true; + pdata->lineout2_diff = true; + if (of_find_property(np, "wlf,lineout1-se", NULL)) + pdata->lineout1_diff = false; + if (of_find_property(np, "wlf,lineout2-se", NULL)) + pdata->lineout2_diff = false; + + if (of_find_property(np, "wlf,lineout1-feedback", NULL)) + pdata->lineout1fb = true; + if (of_find_property(np, "wlf,lineout2-feedback", NULL)) + pdata->lineout2fb = true; + + if (of_find_property(np, "wlf,ldoena-always-driven", NULL)) + pdata->lineout2fb = true; + + pdata->ldo[0].enable = of_get_named_gpio(np, "wlf,ldo1ena", 0); + if (pdata->ldo[0].enable < 0) + pdata->ldo[0].enable = 0; + + pdata->ldo[1].enable = of_get_named_gpio(np, "wlf,ldo2ena", 0); + if (pdata->ldo[1].enable < 0) + pdata->ldo[1].enable = 0; + + return 0; +} +#else +static int wm8994_set_pdata_from_of(struct wm8994 *wm8994) +{ + return 0; +} +#endif + +/* + * Instantiate the generic non-control parts of the device. + */ +static int wm8994_device_init(struct wm8994 *wm8994, int irq) +{ + struct wm8994_pdata *pdata; + struct regmap_config *regmap_config; + const struct reg_default *regmap_patch = NULL; + const char *devname; + int ret, i, patch_regs = 0; + int pulls = 0; + + if (dev_get_platdata(wm8994->dev)) { + pdata = dev_get_platdata(wm8994->dev); + wm8994->pdata = *pdata; + } + pdata = &wm8994->pdata; + + ret = wm8994_set_pdata_from_of(wm8994); + if (ret != 0) + return ret; + + dev_set_drvdata(wm8994->dev, wm8994); + + /* Add the on-chip regulators first for bootstrapping */ + ret = mfd_add_devices(wm8994->dev, 0, + wm8994_regulator_devs, + ARRAY_SIZE(wm8994_regulator_devs), + NULL, 0, NULL); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to add children: %d\n", ret); + goto err; + } + + switch (wm8994->type) { + case WM1811: + wm8994->num_supplies = ARRAY_SIZE(wm1811_main_supplies); + break; + case WM8994: + wm8994->num_supplies = ARRAY_SIZE(wm8994_main_supplies); + break; + case WM8958: + wm8994->num_supplies = ARRAY_SIZE(wm8958_main_supplies); + break; + default: + BUG(); + goto err; + } + + wm8994->supplies = devm_kzalloc(wm8994->dev, + sizeof(struct regulator_bulk_data) * + wm8994->num_supplies, GFP_KERNEL); + if (!wm8994->supplies) { + ret = -ENOMEM; + goto err; + } + + switch (wm8994->type) { + case WM1811: + for (i = 0; i < ARRAY_SIZE(wm1811_main_supplies); i++) + wm8994->supplies[i].supply = wm1811_main_supplies[i]; + break; + case WM8994: + for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++) + wm8994->supplies[i].supply = wm8994_main_supplies[i]; + break; + case WM8958: + for (i = 0; i < ARRAY_SIZE(wm8958_main_supplies); i++) + wm8994->supplies[i].supply = wm8958_main_supplies[i]; + break; + default: + BUG(); + goto err; + } + + ret = devm_regulator_bulk_get(wm8994->dev, wm8994->num_supplies, + wm8994->supplies); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to get supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(wm8994->num_supplies, + wm8994->supplies); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to enable supplies: %d\n", ret); + goto err; + } + + ret = wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET); + if (ret < 0) { + dev_err(wm8994->dev, "Failed to read ID register\n"); + goto err_enable; + } + switch (ret) { + case 0x1811: + devname = "WM1811"; + if (wm8994->type != WM1811) + dev_warn(wm8994->dev, "Device registered as type %d\n", + wm8994->type); + wm8994->type = WM1811; + break; + case 0x8994: + devname = "WM8994"; + if (wm8994->type != WM8994) + dev_warn(wm8994->dev, "Device registered as type %d\n", + wm8994->type); + wm8994->type = WM8994; + break; + case 0x8958: + devname = "WM8958"; + if (wm8994->type != WM8958) + dev_warn(wm8994->dev, "Device registered as type %d\n", + wm8994->type); + wm8994->type = WM8958; + break; + default: + dev_err(wm8994->dev, "Device is not a WM8994, ID is %x\n", + ret); + ret = -EINVAL; + goto err_enable; + } + + ret = wm8994_reg_read(wm8994, WM8994_CHIP_REVISION); + if (ret < 0) { + dev_err(wm8994->dev, "Failed to read revision register: %d\n", + ret); + goto err_enable; + } + wm8994->revision = ret & WM8994_CHIP_REV_MASK; + wm8994->cust_id = (ret & WM8994_CUST_ID_MASK) >> WM8994_CUST_ID_SHIFT; + + switch (wm8994->type) { + case WM8994: + switch (wm8994->revision) { + case 0: + case 1: + dev_warn(wm8994->dev, + "revision %c not fully supported\n", + 'A' + wm8994->revision); + break; + case 2: + case 3: + default: + regmap_patch = wm8994_revc_patch; + patch_regs = ARRAY_SIZE(wm8994_revc_patch); + break; + } + break; + + case WM8958: + switch (wm8994->revision) { + case 0: + regmap_patch = wm8958_reva_patch; + patch_regs = ARRAY_SIZE(wm8958_reva_patch); + break; + default: + break; + } + break; + + case WM1811: + /* Revision C did not change the relevant layer */ + if (wm8994->revision > 1) + wm8994->revision++; + + regmap_patch = wm1811_reva_patch; + patch_regs = ARRAY_SIZE(wm1811_reva_patch); + break; + + default: + break; + } + + dev_info(wm8994->dev, "%s revision %c CUST_ID %02x\n", devname, + 'A' + wm8994->revision, wm8994->cust_id); + + switch (wm8994->type) { + case WM1811: + regmap_config = &wm1811_regmap_config; + break; + case WM8994: + regmap_config = &wm8994_regmap_config; + break; + case WM8958: + regmap_config = &wm8958_regmap_config; + break; + default: + dev_err(wm8994->dev, "Unknown device type %d\n", wm8994->type); + return -EINVAL; + } + + ret = regmap_reinit_cache(wm8994->regmap, regmap_config); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to reinit register cache: %d\n", + ret); + return ret; + } + + /* Explicitly put the device into reset in case regulators + * don't get disabled in order to ensure we know the device + * state. + */ + ret = wm8994_reg_write(wm8994, WM8994_SOFTWARE_RESET, + wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET)); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to reset device: %d\n", ret); + return ret; + } + + if (regmap_patch) { + ret = regmap_register_patch(wm8994->regmap, regmap_patch, + patch_regs); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to register patch: %d\n", + ret); + goto err; + } + } + + wm8994->irq_base = pdata->irq_base; + wm8994->gpio_base = pdata->gpio_base; + + /* GPIO configuration is only applied if it's non-zero */ + for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) { + if (pdata->gpio_defaults[i]) { + wm8994_set_bits(wm8994, WM8994_GPIO_1 + i, + 0xffff, pdata->gpio_defaults[i]); + } + } + + wm8994->ldo_ena_always_driven = pdata->ldo_ena_always_driven; + + if (pdata->spkmode_pu) + pulls |= WM8994_SPKMODE_PU; + + /* Disable unneeded pulls */ + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD | + WM8994_SPKMODE_PU | WM8994_CSNADDR_PD, + pulls); + + /* In some system designs where the regulators are not in use, + * we can achieve a small reduction in leakage currents by + * floating LDO outputs. This bit makes no difference if the + * LDOs are enabled, it only affects cases where the LDOs were + * in operation and are then disabled. + */ + for (i = 0; i < WM8994_NUM_LDO_REGS; i++) { + if (wm8994_ldo_in_use(pdata, i)) + wm8994_set_bits(wm8994, WM8994_LDO_1 + i, + WM8994_LDO1_DISCH, WM8994_LDO1_DISCH); + else + wm8994_set_bits(wm8994, WM8994_LDO_1 + i, + WM8994_LDO1_DISCH, 0); + } + + wm8994_irq_init(wm8994); + + ret = mfd_add_devices(wm8994->dev, -1, + wm8994_devs, ARRAY_SIZE(wm8994_devs), + NULL, 0, NULL); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to add children: %d\n", ret); + goto err_irq; + } + + pm_runtime_enable(wm8994->dev); + pm_runtime_idle(wm8994->dev); + + return 0; + +err_irq: + wm8994_irq_exit(wm8994); +err_enable: + regulator_bulk_disable(wm8994->num_supplies, + wm8994->supplies); +err: + mfd_remove_devices(wm8994->dev); + return ret; +} + +static void wm8994_device_exit(struct wm8994 *wm8994) +{ + pm_runtime_disable(wm8994->dev); + mfd_remove_devices(wm8994->dev); + wm8994_irq_exit(wm8994); + regulator_bulk_disable(wm8994->num_supplies, + wm8994->supplies); +} + +static const struct of_device_id wm8994_of_match[] = { + { .compatible = "wlf,wm1811", .data = (void *)WM1811 }, + { .compatible = "wlf,wm8994", .data = (void *)WM8994 }, + { .compatible = "wlf,wm8958", .data = (void *)WM8958 }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8994_of_match); + +static int wm8994_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + const struct of_device_id *of_id; + struct wm8994 *wm8994; + int ret; + + wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL); + if (wm8994 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8994); + wm8994->dev = &i2c->dev; + wm8994->irq = i2c->irq; + + if (i2c->dev.of_node) { + of_id = of_match_device(wm8994_of_match, &i2c->dev); + if (of_id) + wm8994->type = (enum wm8994_type)of_id->data; + } else { + wm8994->type = id->driver_data; + } + + wm8994->regmap = devm_regmap_init_i2c(i2c, &wm8994_base_regmap_config); + if (IS_ERR(wm8994->regmap)) { + ret = PTR_ERR(wm8994->regmap); + dev_err(wm8994->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + return wm8994_device_init(wm8994, i2c->irq); +} + +static int wm8994_i2c_remove(struct i2c_client *i2c) +{ + struct wm8994 *wm8994 = i2c_get_clientdata(i2c); + + wm8994_device_exit(wm8994); + + return 0; +} + +static const struct i2c_device_id wm8994_i2c_id[] = { + { "wm1811", WM1811 }, + { "wm1811a", WM1811 }, + { "wm8994", WM8994 }, + { "wm8958", WM8958 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8994_i2c_id); + +static const struct dev_pm_ops wm8994_pm_ops = { + SET_RUNTIME_PM_OPS(wm8994_suspend, wm8994_resume, NULL) +}; + +static struct i2c_driver wm8994_i2c_driver = { + .driver = { + .name = "wm8994", + .owner = THIS_MODULE, + .pm = &wm8994_pm_ops, + .of_match_table = of_match_ptr(wm8994_of_match), + }, + .probe = wm8994_i2c_probe, + .remove = wm8994_i2c_remove, + .id_table = wm8994_i2c_id, +}; + +module_i2c_driver(wm8994_i2c_driver); + +MODULE_DESCRIPTION("Core support for the WM8994 audio CODEC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); diff --git a/kernel/drivers/mfd/wm8994-irq.c b/kernel/drivers/mfd/wm8994-irq.c new file mode 100644 index 000000000..a14407edb --- /dev/null +++ b/kernel/drivers/mfd/wm8994-irq.c @@ -0,0 +1,271 @@ +/* + * wm8994-irq.c -- Interrupt controller support for Wolfson WM8994 + * + * Copyright 2010 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/gpio.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/regmap.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/registers.h> + +#include <linux/delay.h> + +static struct regmap_irq wm8994_irqs[] = { + [WM8994_IRQ_TEMP_SHUT] = { + .reg_offset = 1, + .mask = WM8994_TEMP_SHUT_EINT, + }, + [WM8994_IRQ_MIC1_DET] = { + .reg_offset = 1, + .mask = WM8994_MIC1_DET_EINT, + }, + [WM8994_IRQ_MIC1_SHRT] = { + .reg_offset = 1, + .mask = WM8994_MIC1_SHRT_EINT, + }, + [WM8994_IRQ_MIC2_DET] = { + .reg_offset = 1, + .mask = WM8994_MIC2_DET_EINT, + }, + [WM8994_IRQ_MIC2_SHRT] = { + .reg_offset = 1, + .mask = WM8994_MIC2_SHRT_EINT, + }, + [WM8994_IRQ_FLL1_LOCK] = { + .reg_offset = 1, + .mask = WM8994_FLL1_LOCK_EINT, + }, + [WM8994_IRQ_FLL2_LOCK] = { + .reg_offset = 1, + .mask = WM8994_FLL2_LOCK_EINT, + }, + [WM8994_IRQ_SRC1_LOCK] = { + .reg_offset = 1, + .mask = WM8994_SRC1_LOCK_EINT, + }, + [WM8994_IRQ_SRC2_LOCK] = { + .reg_offset = 1, + .mask = WM8994_SRC2_LOCK_EINT, + }, + [WM8994_IRQ_AIF1DRC1_SIG_DET] = { + .reg_offset = 1, + .mask = WM8994_AIF1DRC1_SIG_DET, + }, + [WM8994_IRQ_AIF1DRC2_SIG_DET] = { + .reg_offset = 1, + .mask = WM8994_AIF1DRC2_SIG_DET_EINT, + }, + [WM8994_IRQ_AIF2DRC_SIG_DET] = { + .reg_offset = 1, + .mask = WM8994_AIF2DRC_SIG_DET_EINT, + }, + [WM8994_IRQ_FIFOS_ERR] = { + .reg_offset = 1, + .mask = WM8994_FIFOS_ERR_EINT, + }, + [WM8994_IRQ_WSEQ_DONE] = { + .reg_offset = 1, + .mask = WM8994_WSEQ_DONE_EINT, + }, + [WM8994_IRQ_DCS_DONE] = { + .reg_offset = 1, + .mask = WM8994_DCS_DONE_EINT, + }, + [WM8994_IRQ_TEMP_WARN] = { + .reg_offset = 1, + .mask = WM8994_TEMP_WARN_EINT, + }, + [WM8994_IRQ_GPIO(1)] = { + .mask = WM8994_GP1_EINT, + }, + [WM8994_IRQ_GPIO(2)] = { + .mask = WM8994_GP2_EINT, + }, + [WM8994_IRQ_GPIO(3)] = { + .mask = WM8994_GP3_EINT, + }, + [WM8994_IRQ_GPIO(4)] = { + .mask = WM8994_GP4_EINT, + }, + [WM8994_IRQ_GPIO(5)] = { + .mask = WM8994_GP5_EINT, + }, + [WM8994_IRQ_GPIO(6)] = { + .mask = WM8994_GP6_EINT, + }, + [WM8994_IRQ_GPIO(7)] = { + .mask = WM8994_GP7_EINT, + }, + [WM8994_IRQ_GPIO(8)] = { + .mask = WM8994_GP8_EINT, + }, + [WM8994_IRQ_GPIO(9)] = { + .mask = WM8994_GP8_EINT, + }, + [WM8994_IRQ_GPIO(10)] = { + .mask = WM8994_GP10_EINT, + }, + [WM8994_IRQ_GPIO(11)] = { + .mask = WM8994_GP11_EINT, + }, +}; + +static struct regmap_irq_chip wm8994_irq_chip = { + .name = "wm8994", + .irqs = wm8994_irqs, + .num_irqs = ARRAY_SIZE(wm8994_irqs), + + .num_regs = 2, + .status_base = WM8994_INTERRUPT_STATUS_1, + .mask_base = WM8994_INTERRUPT_STATUS_1_MASK, + .ack_base = WM8994_INTERRUPT_STATUS_1, + .runtime_pm = true, +}; + +static void wm8994_edge_irq_enable(struct irq_data *data) +{ +} + +static void wm8994_edge_irq_disable(struct irq_data *data) +{ +} + +static struct irq_chip wm8994_edge_irq_chip = { + .name = "wm8994_edge", + .irq_disable = wm8994_edge_irq_disable, + .irq_enable = wm8994_edge_irq_enable, +}; + +static irqreturn_t wm8994_edge_irq(int irq, void *data) +{ + struct wm8994 *wm8994 = data; + + while (gpio_get_value_cansleep(wm8994->pdata.irq_gpio)) + handle_nested_irq(irq_create_mapping(wm8994->edge_irq, 0)); + + return IRQ_HANDLED; +} + +static int wm8994_edge_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct wm8994 *wm8994 = h->host_data; + + irq_set_chip_data(virq, wm8994); + irq_set_chip_and_handler(virq, &wm8994_edge_irq_chip, handle_edge_irq); + irq_set_nested_thread(virq, 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(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif + + return 0; +} + +static struct irq_domain_ops wm8994_edge_irq_ops = { + .map = wm8994_edge_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +int wm8994_irq_init(struct wm8994 *wm8994) +{ + int ret; + unsigned long irqflags; + struct wm8994_pdata *pdata = dev_get_platdata(wm8994->dev); + + if (!wm8994->irq) { + dev_warn(wm8994->dev, + "No interrupt specified, no interrupts\n"); + wm8994->irq_base = 0; + return 0; + } + + /* select user or default irq flags */ + irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; + if (pdata->irq_flags) + irqflags = pdata->irq_flags; + + /* use a GPIO for edge triggered controllers */ + if (irqflags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + if (gpio_to_irq(pdata->irq_gpio) != wm8994->irq) { + dev_warn(wm8994->dev, "IRQ %d is not GPIO %d (%d)\n", + wm8994->irq, pdata->irq_gpio, + gpio_to_irq(pdata->irq_gpio)); + wm8994->irq = gpio_to_irq(pdata->irq_gpio); + } + + ret = devm_gpio_request_one(wm8994->dev, pdata->irq_gpio, + GPIOF_IN, "WM8994 IRQ"); + + if (ret != 0) { + dev_err(wm8994->dev, "Failed to get IRQ GPIO: %d\n", + ret); + return ret; + } + + wm8994->edge_irq = irq_domain_add_linear(NULL, 1, + &wm8994_edge_irq_ops, + wm8994); + + ret = regmap_add_irq_chip(wm8994->regmap, + irq_create_mapping(wm8994->edge_irq, + 0), + IRQF_ONESHOT, + wm8994->irq_base, &wm8994_irq_chip, + &wm8994->irq_data); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to get IRQ: %d\n", + ret); + return ret; + } + + ret = request_threaded_irq(wm8994->irq, + NULL, wm8994_edge_irq, + irqflags, + "WM8994 edge", wm8994); + } else { + ret = regmap_add_irq_chip(wm8994->regmap, wm8994->irq, + irqflags, + wm8994->irq_base, &wm8994_irq_chip, + &wm8994->irq_data); + } + + if (ret != 0) { + dev_err(wm8994->dev, "Failed to register IRQ chip: %d\n", ret); + return ret; + } + + /* Enable top level interrupt if it was masked */ + wm8994_reg_write(wm8994, WM8994_INTERRUPT_CONTROL, 0); + + return 0; +} +EXPORT_SYMBOL(wm8994_irq_init); + +void wm8994_irq_exit(struct wm8994 *wm8994) +{ + regmap_del_irq_chip(wm8994->irq, wm8994->irq_data); +} +EXPORT_SYMBOL(wm8994_irq_exit); diff --git a/kernel/drivers/mfd/wm8994-regmap.c b/kernel/drivers/mfd/wm8994-regmap.c new file mode 100644 index 000000000..300e9b6a2 --- /dev/null +++ b/kernel/drivers/mfd/wm8994-regmap.c @@ -0,0 +1,1291 @@ +/* + * wm8994-regmap.c -- Register map data for WM8994 series devices + * + * Copyright 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/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/regmap.h> +#include <linux/device.h> + +#include "wm8994.h" + +static struct reg_default wm1811_defaults[] = { + { 0x0001, 0x0000 }, /* R1 - Power Management (1) */ + { 0x0002, 0x6000 }, /* R2 - Power Management (2) */ + { 0x0003, 0x0000 }, /* R3 - Power Management (3) */ + { 0x0004, 0x0000 }, /* R4 - Power Management (4) */ + { 0x0005, 0x0000 }, /* R5 - Power Management (5) */ + { 0x0006, 0x0000 }, /* R6 - Power Management (6) */ + { 0x0015, 0x0000 }, /* R21 - Input Mixer (1) */ + { 0x0018, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ + { 0x0019, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ + { 0x001A, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ + { 0x001B, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ + { 0x001C, 0x006D }, /* R28 - Left Output Volume */ + { 0x001D, 0x006D }, /* R29 - Right Output Volume */ + { 0x001E, 0x0066 }, /* R30 - Line Outputs Volume */ + { 0x001F, 0x0020 }, /* R31 - HPOUT2 Volume */ + { 0x0020, 0x0079 }, /* R32 - Left OPGA Volume */ + { 0x0021, 0x0079 }, /* R33 - Right OPGA Volume */ + { 0x0022, 0x0003 }, /* R34 - SPKMIXL Attenuation */ + { 0x0023, 0x0003 }, /* R35 - SPKMIXR Attenuation */ + { 0x0024, 0x0011 }, /* R36 - SPKOUT Mixers */ + { 0x0025, 0x0140 }, /* R37 - ClassD */ + { 0x0026, 0x0079 }, /* R38 - Speaker Volume Left */ + { 0x0027, 0x0079 }, /* R39 - Speaker Volume Right */ + { 0x0028, 0x0000 }, /* R40 - Input Mixer (2) */ + { 0x0029, 0x0000 }, /* R41 - Input Mixer (3) */ + { 0x002A, 0x0000 }, /* R42 - Input Mixer (4) */ + { 0x002B, 0x0000 }, /* R43 - Input Mixer (5) */ + { 0x002C, 0x0000 }, /* R44 - Input Mixer (6) */ + { 0x002D, 0x0000 }, /* R45 - Output Mixer (1) */ + { 0x002E, 0x0000 }, /* R46 - Output Mixer (2) */ + { 0x002F, 0x0000 }, /* R47 - Output Mixer (3) */ + { 0x0030, 0x0000 }, /* R48 - Output Mixer (4) */ + { 0x0031, 0x0000 }, /* R49 - Output Mixer (5) */ + { 0x0032, 0x0000 }, /* R50 - Output Mixer (6) */ + { 0x0033, 0x0000 }, /* R51 - HPOUT2 Mixer */ + { 0x0034, 0x0000 }, /* R52 - Line Mixer (1) */ + { 0x0035, 0x0000 }, /* R53 - Line Mixer (2) */ + { 0x0036, 0x0000 }, /* R54 - Speaker Mixer */ + { 0x0037, 0x0000 }, /* R55 - Additional Control */ + { 0x0038, 0x0000 }, /* R56 - AntiPOP (1) */ + { 0x0039, 0x0000 }, /* R57 - AntiPOP (2) */ + { 0x003B, 0x000D }, /* R59 - LDO 1 */ + { 0x003C, 0x0003 }, /* R60 - LDO 2 */ + { 0x003D, 0x0039 }, /* R61 - MICBIAS1 */ + { 0x003E, 0x0039 }, /* R62 - MICBIAS2 */ + { 0x004C, 0x1F25 }, /* R76 - Charge Pump (1) */ + { 0x004D, 0xAB19 }, /* R77 - Charge Pump (2) */ + { 0x0051, 0x0004 }, /* R81 - Class W (1) */ + { 0x0055, 0x054A }, /* R85 - DC Servo (2) */ + { 0x0059, 0x0000 }, /* R89 - DC Servo (4) */ + { 0x0060, 0x0000 }, /* R96 - Analogue HP (1) */ + { 0x00C5, 0x0000 }, /* R197 - Class D Test (5) */ + { 0x00D0, 0x7600 }, /* R208 - Mic Detect 1 */ + { 0x00D1, 0x007F }, /* R209 - Mic Detect 2 */ + { 0x0101, 0x8004 }, /* R257 - Control Interface */ + { 0x0200, 0x0000 }, /* R512 - AIF1 Clocking (1) */ + { 0x0201, 0x0000 }, /* R513 - AIF1 Clocking (2) */ + { 0x0204, 0x0000 }, /* R516 - AIF2 Clocking (1) */ + { 0x0205, 0x0000 }, /* R517 - AIF2 Clocking (2) */ + { 0x0208, 0x0000 }, /* R520 - Clocking (1) */ + { 0x0209, 0x0000 }, /* R521 - Clocking (2) */ + { 0x0210, 0x0083 }, /* R528 - AIF1 Rate */ + { 0x0211, 0x0083 }, /* R529 - AIF2 Rate */ + { 0x0220, 0x0000 }, /* R544 - FLL1 Control (1) */ + { 0x0221, 0x0000 }, /* R545 - FLL1 Control (2) */ + { 0x0222, 0x0000 }, /* R546 - FLL1 Control (3) */ + { 0x0223, 0x0000 }, /* R547 - FLL1 Control (4) */ + { 0x0224, 0x0C80 }, /* R548 - FLL1 Control (5) */ + { 0x0226, 0x0000 }, /* R550 - FLL1 EFS 1 */ + { 0x0227, 0x0006 }, /* R551 - FLL1 EFS 2 */ + { 0x0240, 0x0000 }, /* R576 - FLL2Control (1) */ + { 0x0241, 0x0000 }, /* R577 - FLL2Control (2) */ + { 0x0242, 0x0000 }, /* R578 - FLL2Control (3) */ + { 0x0243, 0x0000 }, /* R579 - FLL2 Control (4) */ + { 0x0244, 0x0C80 }, /* R580 - FLL2Control (5) */ + { 0x0246, 0x0000 }, /* R582 - FLL2 EFS 1 */ + { 0x0247, 0x0006 }, /* R583 - FLL2 EFS 2 */ + { 0x0300, 0x4050 }, /* R768 - AIF1 Control (1) */ + { 0x0301, 0x4000 }, /* R769 - AIF1 Control (2) */ + { 0x0302, 0x0000 }, /* R770 - AIF1 Master/Slave */ + { 0x0303, 0x0040 }, /* R771 - AIF1 BCLK */ + { 0x0304, 0x0040 }, /* R772 - AIF1ADC LRCLK */ + { 0x0305, 0x0040 }, /* R773 - AIF1DAC LRCLK */ + { 0x0306, 0x0004 }, /* R774 - AIF1DAC Data */ + { 0x0307, 0x0100 }, /* R775 - AIF1ADC Data */ + { 0x0310, 0x4050 }, /* R784 - AIF2 Control (1) */ + { 0x0311, 0x4000 }, /* R785 - AIF2 Control (2) */ + { 0x0312, 0x0000 }, /* R786 - AIF2 Master/Slave */ + { 0x0313, 0x0040 }, /* R787 - AIF2 BCLK */ + { 0x0314, 0x0040 }, /* R788 - AIF2ADC LRCLK */ + { 0x0315, 0x0040 }, /* R789 - AIF2DAC LRCLK */ + { 0x0316, 0x0000 }, /* R790 - AIF2DAC Data */ + { 0x0317, 0x0000 }, /* R791 - AIF2ADC Data */ + { 0x0318, 0x0003 }, /* R792 - AIF2TX Control */ + { 0x0320, 0x0040 }, /* R800 - AIF3 Control (1) */ + { 0x0321, 0x0000 }, /* R801 - AIF3 Control (2) */ + { 0x0322, 0x0000 }, /* R802 - AIF3DAC Data */ + { 0x0323, 0x0000 }, /* R803 - AIF3ADC Data */ + { 0x0400, 0x00C0 }, /* R1024 - AIF1 ADC1 Left Volume */ + { 0x0401, 0x00C0 }, /* R1025 - AIF1 ADC1 Right Volume */ + { 0x0402, 0x00C0 }, /* R1026 - AIF1 DAC1 Left Volume */ + { 0x0403, 0x00C0 }, /* R1027 - AIF1 DAC1 Right Volume */ + { 0x0410, 0x0000 }, /* R1040 - AIF1 ADC1 Filters */ + { 0x0411, 0x0000 }, /* R1041 - AIF1 ADC2 Filters */ + { 0x0420, 0x0200 }, /* R1056 - AIF1 DAC1 Filters (1) */ + { 0x0421, 0x0010 }, /* R1057 - AIF1 DAC1 Filters (2) */ + { 0x0422, 0x0200 }, /* R1058 - AIF1 DAC2 Filters (1) */ + { 0x0423, 0x0010 }, /* R1059 - AIF1 DAC2 Filters (2) */ + { 0x0430, 0x0068 }, /* R1072 - AIF1 DAC1 Noise Gate */ + { 0x0431, 0x0068 }, /* R1073 - AIF1 DAC2 Noise Gate */ + { 0x0440, 0x0098 }, /* R1088 - AIF1 DRC1 (1) */ + { 0x0441, 0x0845 }, /* R1089 - AIF1 DRC1 (2) */ + { 0x0442, 0x0000 }, /* R1090 - AIF1 DRC1 (3) */ + { 0x0443, 0x0000 }, /* R1091 - AIF1 DRC1 (4) */ + { 0x0444, 0x0000 }, /* R1092 - AIF1 DRC1 (5) */ + { 0x0450, 0x0098 }, /* R1104 - AIF1 DRC2 (1) */ + { 0x0451, 0x0845 }, /* R1105 - AIF1 DRC2 (2) */ + { 0x0452, 0x0000 }, /* R1106 - AIF1 DRC2 (3) */ + { 0x0453, 0x0000 }, /* R1107 - AIF1 DRC2 (4) */ + { 0x0454, 0x0000 }, /* R1108 - AIF1 DRC2 (5) */ + { 0x0480, 0x6318 }, /* R1152 - AIF1 DAC1 EQ Gains (1) */ + { 0x0481, 0x6300 }, /* R1153 - AIF1 DAC1 EQ Gains (2) */ + { 0x0482, 0x0FCA }, /* R1154 - AIF1 DAC1 EQ Band 1 A */ + { 0x0483, 0x0400 }, /* R1155 - AIF1 DAC1 EQ Band 1 B */ + { 0x0484, 0x00D8 }, /* R1156 - AIF1 DAC1 EQ Band 1 PG */ + { 0x0485, 0x1EB5 }, /* R1157 - AIF1 DAC1 EQ Band 2 A */ + { 0x0486, 0xF145 }, /* R1158 - AIF1 DAC1 EQ Band 2 B */ + { 0x0487, 0x0B75 }, /* R1159 - AIF1 DAC1 EQ Band 2 C */ + { 0x0488, 0x01C5 }, /* R1160 - AIF1 DAC1 EQ Band 2 PG */ + { 0x0489, 0x1C58 }, /* R1161 - AIF1 DAC1 EQ Band 3 A */ + { 0x048A, 0xF373 }, /* R1162 - AIF1 DAC1 EQ Band 3 B */ + { 0x048B, 0x0A54 }, /* R1163 - AIF1 DAC1 EQ Band 3 C */ + { 0x048C, 0x0558 }, /* R1164 - AIF1 DAC1 EQ Band 3 PG */ + { 0x048D, 0x168E }, /* R1165 - AIF1 DAC1 EQ Band 4 A */ + { 0x048E, 0xF829 }, /* R1166 - AIF1 DAC1 EQ Band 4 B */ + { 0x048F, 0x07AD }, /* R1167 - AIF1 DAC1 EQ Band 4 C */ + { 0x0490, 0x1103 }, /* R1168 - AIF1 DAC1 EQ Band 4 PG */ + { 0x0491, 0x0564 }, /* R1169 - AIF1 DAC1 EQ Band 5 A */ + { 0x0492, 0x0559 }, /* R1170 - AIF1 DAC1 EQ Band 5 B */ + { 0x0493, 0x4000 }, /* R1171 - AIF1 DAC1 EQ Band 5 PG */ + { 0x0494, 0x0000 }, /* R1172 - AIF1 DAC1 EQ Band 1 C */ + { 0x04A0, 0x6318 }, /* R1184 - AIF1 DAC2 EQ Gains (1) */ + { 0x04A1, 0x6300 }, /* R1185 - AIF1 DAC2 EQ Gains (2) */ + { 0x04A2, 0x0FCA }, /* R1186 - AIF1 DAC2 EQ Band 1 A */ + { 0x04A3, 0x0400 }, /* R1187 - AIF1 DAC2 EQ Band 1 B */ + { 0x04A4, 0x00D8 }, /* R1188 - AIF1 DAC2 EQ Band 1 PG */ + { 0x04A5, 0x1EB5 }, /* R1189 - AIF1 DAC2 EQ Band 2 A */ + { 0x04A6, 0xF145 }, /* R1190 - AIF1 DAC2 EQ Band 2 B */ + { 0x04A7, 0x0B75 }, /* R1191 - AIF1 DAC2 EQ Band 2 C */ + { 0x04A8, 0x01C5 }, /* R1192 - AIF1 DAC2 EQ Band 2 PG */ + { 0x04A9, 0x1C58 }, /* R1193 - AIF1 DAC2 EQ Band 3 A */ + { 0x04AA, 0xF373 }, /* R1194 - AIF1 DAC2 EQ Band 3 B */ + { 0x04AB, 0x0A54 }, /* R1195 - AIF1 DAC2 EQ Band 3 C */ + { 0x04AC, 0x0558 }, /* R1196 - AIF1 DAC2 EQ Band 3 PG */ + { 0x04AD, 0x168E }, /* R1197 - AIF1 DAC2 EQ Band 4 A */ + { 0x04AE, 0xF829 }, /* R1198 - AIF1 DAC2 EQ Band 4 B */ + { 0x04AF, 0x07AD }, /* R1199 - AIF1 DAC2 EQ Band 4 C */ + { 0x04B0, 0x1103 }, /* R1200 - AIF1 DAC2 EQ Band 4 PG */ + { 0x04B1, 0x0564 }, /* R1201 - AIF1 DAC2 EQ Band 5 A */ + { 0x04B2, 0x0559 }, /* R1202 - AIF1 DAC2 EQ Band 5 B */ + { 0x04B3, 0x4000 }, /* R1203 - AIF1 DAC2 EQ Band 5 PG */ + { 0x04B4, 0x0000 }, /* R1204 - AIF1 DAC2 EQ Band 1 C */ + { 0x0500, 0x00C0 }, /* R1280 - AIF2 ADC Left Volume */ + { 0x0501, 0x00C0 }, /* R1281 - AIF2 ADC Right Volume */ + { 0x0502, 0x00C0 }, /* R1282 - AIF2 DAC Left Volume */ + { 0x0503, 0x00C0 }, /* R1283 - AIF2 DAC Right Volume */ + { 0x0510, 0x0000 }, /* R1296 - AIF2 ADC Filters */ + { 0x0520, 0x0200 }, /* R1312 - AIF2 DAC Filters (1) */ + { 0x0521, 0x0010 }, /* R1313 - AIF2 DAC Filters (2) */ + { 0x0530, 0x0068 }, /* R1328 - AIF2 DAC Noise Gate */ + { 0x0540, 0x0098 }, /* R1344 - AIF2 DRC (1) */ + { 0x0541, 0x0845 }, /* R1345 - AIF2 DRC (2) */ + { 0x0542, 0x0000 }, /* R1346 - AIF2 DRC (3) */ + { 0x0543, 0x0000 }, /* R1347 - AIF2 DRC (4) */ + { 0x0544, 0x0000 }, /* R1348 - AIF2 DRC (5) */ + { 0x0580, 0x6318 }, /* R1408 - AIF2 EQ Gains (1) */ + { 0x0581, 0x6300 }, /* R1409 - AIF2 EQ Gains (2) */ + { 0x0582, 0x0FCA }, /* R1410 - AIF2 EQ Band 1 A */ + { 0x0583, 0x0400 }, /* R1411 - AIF2 EQ Band 1 B */ + { 0x0584, 0x00D8 }, /* R1412 - AIF2 EQ Band 1 PG */ + { 0x0585, 0x1EB5 }, /* R1413 - AIF2 EQ Band 2 A */ + { 0x0586, 0xF145 }, /* R1414 - AIF2 EQ Band 2 B */ + { 0x0587, 0x0B75 }, /* R1415 - AIF2 EQ Band 2 C */ + { 0x0588, 0x01C5 }, /* R1416 - AIF2 EQ Band 2 PG */ + { 0x0589, 0x1C58 }, /* R1417 - AIF2 EQ Band 3 A */ + { 0x058A, 0xF373 }, /* R1418 - AIF2 EQ Band 3 B */ + { 0x058B, 0x0A54 }, /* R1419 - AIF2 EQ Band 3 C */ + { 0x058C, 0x0558 }, /* R1420 - AIF2 EQ Band 3 PG */ + { 0x058D, 0x168E }, /* R1421 - AIF2 EQ Band 4 A */ + { 0x058E, 0xF829 }, /* R1422 - AIF2 EQ Band 4 B */ + { 0x058F, 0x07AD }, /* R1423 - AIF2 EQ Band 4 C */ + { 0x0590, 0x1103 }, /* R1424 - AIF2 EQ Band 4 PG */ + { 0x0591, 0x0564 }, /* R1425 - AIF2 EQ Band 5 A */ + { 0x0592, 0x0559 }, /* R1426 - AIF2 EQ Band 5 B */ + { 0x0593, 0x4000 }, /* R1427 - AIF2 EQ Band 5 PG */ + { 0x0594, 0x0000 }, /* R1428 - AIF2 EQ Band 1 C */ + { 0x0600, 0x0000 }, /* R1536 - DAC1 Mixer Volumes */ + { 0x0601, 0x0000 }, /* R1537 - DAC1 Left Mixer Routing */ + { 0x0602, 0x0000 }, /* R1538 - DAC1 Right Mixer Routing */ + { 0x0603, 0x0000 }, /* R1539 - AIF2ADC Mixer Volumes */ + { 0x0604, 0x0000 }, /* R1540 - AIF2ADC Left Mixer Routing */ + { 0x0605, 0x0000 }, /* R1541 - AIF2ADC Right Mixer Routing */ + { 0x0606, 0x0000 }, /* R1542 - AIF1 ADC1 Left Mixer Routing */ + { 0x0607, 0x0000 }, /* R1543 - AIF1 ADC1 Right Mixer Routing */ + { 0x0608, 0x0000 }, /* R1544 - AIF1 ADC2 Left Mixer Routing */ + { 0x0609, 0x0000 }, /* R1545 - AIF1 ADC2 Right Mixer Routing */ + { 0x0610, 0x02C0 }, /* R1552 - DAC1 Left Volume */ + { 0x0611, 0x02C0 }, /* R1553 - DAC1 Right Volume */ + { 0x0612, 0x02C0 }, /* R1554 - AIF2TX Left Volume */ + { 0x0613, 0x02C0 }, /* R1555 - AIF2TX Right Volume */ + { 0x0614, 0x0000 }, /* R1556 - DAC Softmute */ + { 0x0620, 0x0002 }, /* R1568 - Oversampling */ + { 0x0621, 0x0000 }, /* R1569 - Sidetone */ + { 0x0700, 0x8100 }, /* R1792 - GPIO 1 */ + { 0x0701, 0xA101 }, /* R1793 - Pull Control (MCLK2) */ + { 0x0702, 0xA101 }, /* R1794 - Pull Control (BCLK2) */ + { 0x0703, 0xA101 }, /* R1795 - Pull Control (DACLRCLK2) */ + { 0x0704, 0xA101 }, /* R1796 - Pull Control (DACDAT2) */ + { 0x0707, 0xA101 }, /* R1799 - GPIO 8 */ + { 0x0708, 0xA101 }, /* R1800 - GPIO 9 */ + { 0x0709, 0xA101 }, /* R1801 - GPIO 10 */ + { 0x070A, 0xA101 }, /* R1802 - GPIO 11 */ + { 0x0720, 0x0000 }, /* R1824 - Pull Control (1) */ + { 0x0721, 0x0156 }, /* R1825 - Pull Control (2) */ + { 0x0732, 0x0000 }, /* R1842 - Interrupt Raw Status 2 */ + { 0x0738, 0x07FF }, /* R1848 - Interrupt Status 1 Mask */ + { 0x0739, 0xDFEF }, /* R1849 - Interrupt Status 2 Mask */ + { 0x0740, 0x0000 }, /* R1856 - Interrupt Control */ + { 0x0748, 0x003F }, /* R1864 - IRQ Debounce */ +}; + +static struct reg_default wm8994_defaults[] = { + { 0x0001, 0x0000 }, /* R1 - Power Management (1) */ + { 0x0002, 0x6000 }, /* R2 - Power Management (2) */ + { 0x0003, 0x0000 }, /* R3 - Power Management (3) */ + { 0x0004, 0x0000 }, /* R4 - Power Management (4) */ + { 0x0005, 0x0000 }, /* R5 - Power Management (5) */ + { 0x0006, 0x0000 }, /* R6 - Power Management (6) */ + { 0x0015, 0x0000 }, /* R21 - Input Mixer (1) */ + { 0x0018, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ + { 0x0019, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ + { 0x001A, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ + { 0x001B, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ + { 0x001C, 0x006D }, /* R28 - Left Output Volume */ + { 0x001D, 0x006D }, /* R29 - Right Output Volume */ + { 0x001E, 0x0066 }, /* R30 - Line Outputs Volume */ + { 0x001F, 0x0020 }, /* R31 - HPOUT2 Volume */ + { 0x0020, 0x0079 }, /* R32 - Left OPGA Volume */ + { 0x0021, 0x0079 }, /* R33 - Right OPGA Volume */ + { 0x0022, 0x0003 }, /* R34 - SPKMIXL Attenuation */ + { 0x0023, 0x0003 }, /* R35 - SPKMIXR Attenuation */ + { 0x0024, 0x0011 }, /* R36 - SPKOUT Mixers */ + { 0x0025, 0x0140 }, /* R37 - ClassD */ + { 0x0026, 0x0079 }, /* R38 - Speaker Volume Left */ + { 0x0027, 0x0079 }, /* R39 - Speaker Volume Right */ + { 0x0028, 0x0000 }, /* R40 - Input Mixer (2) */ + { 0x0029, 0x0000 }, /* R41 - Input Mixer (3) */ + { 0x002A, 0x0000 }, /* R42 - Input Mixer (4) */ + { 0x002B, 0x0000 }, /* R43 - Input Mixer (5) */ + { 0x002C, 0x0000 }, /* R44 - Input Mixer (6) */ + { 0x002D, 0x0000 }, /* R45 - Output Mixer (1) */ + { 0x002E, 0x0000 }, /* R46 - Output Mixer (2) */ + { 0x002F, 0x0000 }, /* R47 - Output Mixer (3) */ + { 0x0030, 0x0000 }, /* R48 - Output Mixer (4) */ + { 0x0031, 0x0000 }, /* R49 - Output Mixer (5) */ + { 0x0032, 0x0000 }, /* R50 - Output Mixer (6) */ + { 0x0033, 0x0000 }, /* R51 - HPOUT2 Mixer */ + { 0x0034, 0x0000 }, /* R52 - Line Mixer (1) */ + { 0x0035, 0x0000 }, /* R53 - Line Mixer (2) */ + { 0x0036, 0x0000 }, /* R54 - Speaker Mixer */ + { 0x0037, 0x0000 }, /* R55 - Additional Control */ + { 0x0038, 0x0000 }, /* R56 - AntiPOP (1) */ + { 0x0039, 0x0000 }, /* R57 - AntiPOP (2) */ + { 0x003A, 0x0000 }, /* R58 - MICBIAS */ + { 0x003B, 0x000D }, /* R59 - LDO 1 */ + { 0x003C, 0x0003 }, /* R60 - LDO 2 */ + { 0x004C, 0x1F25 }, /* R76 - Charge Pump (1) */ + { 0x0051, 0x0004 }, /* R81 - Class W (1) */ + { 0x0055, 0x054A }, /* R85 - DC Servo (2) */ + { 0x0057, 0x0000 }, /* R87 - DC Servo (4) */ + { 0x0060, 0x0000 }, /* R96 - Analogue HP (1) */ + { 0x0101, 0x8004 }, /* R257 - Control Interface */ + { 0x0110, 0x0000 }, /* R272 - Write Sequencer Ctrl (1) */ + { 0x0111, 0x0000 }, /* R273 - Write Sequencer Ctrl (2) */ + { 0x0200, 0x0000 }, /* R512 - AIF1 Clocking (1) */ + { 0x0201, 0x0000 }, /* R513 - AIF1 Clocking (2) */ + { 0x0204, 0x0000 }, /* R516 - AIF2 Clocking (1) */ + { 0x0205, 0x0000 }, /* R517 - AIF2 Clocking (2) */ + { 0x0208, 0x0000 }, /* R520 - Clocking (1) */ + { 0x0209, 0x0000 }, /* R521 - Clocking (2) */ + { 0x0210, 0x0083 }, /* R528 - AIF1 Rate */ + { 0x0211, 0x0083 }, /* R529 - AIF2 Rate */ + { 0x0220, 0x0000 }, /* R544 - FLL1 Control (1) */ + { 0x0221, 0x0000 }, /* R545 - FLL1 Control (2) */ + { 0x0222, 0x0000 }, /* R546 - FLL1 Control (3) */ + { 0x0223, 0x0000 }, /* R547 - FLL1 Control (4) */ + { 0x0224, 0x0C80 }, /* R548 - FLL1 Control (5) */ + { 0x0240, 0x0000 }, /* R576 - FLL2 Control (1) */ + { 0x0241, 0x0000 }, /* R577 - FLL2 Control (2) */ + { 0x0242, 0x0000 }, /* R578 - FLL2 Control (3) */ + { 0x0243, 0x0000 }, /* R579 - FLL2 Control (4) */ + { 0x0244, 0x0C80 }, /* R580 - FLL2 Control (5) */ + { 0x0300, 0x4050 }, /* R768 - AIF1 Control (1) */ + { 0x0301, 0x4000 }, /* R769 - AIF1 Control (2) */ + { 0x0302, 0x0000 }, /* R770 - AIF1 Master/Slave */ + { 0x0303, 0x0040 }, /* R771 - AIF1 BCLK */ + { 0x0304, 0x0040 }, /* R772 - AIF1ADC LRCLK */ + { 0x0305, 0x0040 }, /* R773 - AIF1DAC LRCLK */ + { 0x0306, 0x0004 }, /* R774 - AIF1DAC Data */ + { 0x0307, 0x0100 }, /* R775 - AIF1ADC Data */ + { 0x0310, 0x4050 }, /* R784 - AIF2 Control (1) */ + { 0x0311, 0x4000 }, /* R785 - AIF2 Control (2) */ + { 0x0312, 0x0000 }, /* R786 - AIF2 Master/Slave */ + { 0x0313, 0x0040 }, /* R787 - AIF2 BCLK */ + { 0x0314, 0x0040 }, /* R788 - AIF2ADC LRCLK */ + { 0x0315, 0x0040 }, /* R789 - AIF2DAC LRCLK */ + { 0x0316, 0x0000 }, /* R790 - AIF2DAC Data */ + { 0x0317, 0x0000 }, /* R791 - AIF2ADC Data */ + { 0x0400, 0x00C0 }, /* R1024 - AIF1 ADC1 Left Volume */ + { 0x0401, 0x00C0 }, /* R1025 - AIF1 ADC1 Right Volume */ + { 0x0402, 0x00C0 }, /* R1026 - AIF1 DAC1 Left Volume */ + { 0x0403, 0x00C0 }, /* R1027 - AIF1 DAC1 Right Volume */ + { 0x0404, 0x00C0 }, /* R1028 - AIF1 ADC2 Left Volume */ + { 0x0405, 0x00C0 }, /* R1029 - AIF1 ADC2 Right Volume */ + { 0x0406, 0x00C0 }, /* R1030 - AIF1 DAC2 Left Volume */ + { 0x0407, 0x00C0 }, /* R1031 - AIF1 DAC2 Right Volume */ + { 0x0410, 0x0000 }, /* R1040 - AIF1 ADC1 Filters */ + { 0x0411, 0x0000 }, /* R1041 - AIF1 ADC2 Filters */ + { 0x0420, 0x0200 }, /* R1056 - AIF1 DAC1 Filters (1) */ + { 0x0421, 0x0010 }, /* R1057 - AIF1 DAC1 Filters (2) */ + { 0x0422, 0x0200 }, /* R1058 - AIF1 DAC2 Filters (1) */ + { 0x0423, 0x0010 }, /* R1059 - AIF1 DAC2 Filters (2) */ + { 0x0440, 0x0098 }, /* R1088 - AIF1 DRC1 (1) */ + { 0x0441, 0x0845 }, /* R1089 - AIF1 DRC1 (2) */ + { 0x0442, 0x0000 }, /* R1090 - AIF1 DRC1 (3) */ + { 0x0443, 0x0000 }, /* R1091 - AIF1 DRC1 (4) */ + { 0x0444, 0x0000 }, /* R1092 - AIF1 DRC1 (5) */ + { 0x0450, 0x0098 }, /* R1104 - AIF1 DRC2 (1) */ + { 0x0451, 0x0845 }, /* R1105 - AIF1 DRC2 (2) */ + { 0x0452, 0x0000 }, /* R1106 - AIF1 DRC2 (3) */ + { 0x0453, 0x0000 }, /* R1107 - AIF1 DRC2 (4) */ + { 0x0454, 0x0000 }, /* R1108 - AIF1 DRC2 (5) */ + { 0x0480, 0x6318 }, /* R1152 - AIF1 DAC1 EQ Gains (1) */ + { 0x0481, 0x6300 }, /* R1153 - AIF1 DAC1 EQ Gains (2) */ + { 0x0482, 0x0FCA }, /* R1154 - AIF1 DAC1 EQ Band 1 A */ + { 0x0483, 0x0400 }, /* R1155 - AIF1 DAC1 EQ Band 1 B */ + { 0x0484, 0x00D8 }, /* R1156 - AIF1 DAC1 EQ Band 1 PG */ + { 0x0485, 0x1EB5 }, /* R1157 - AIF1 DAC1 EQ Band 2 A */ + { 0x0486, 0xF145 }, /* R1158 - AIF1 DAC1 EQ Band 2 B */ + { 0x0487, 0x0B75 }, /* R1159 - AIF1 DAC1 EQ Band 2 C */ + { 0x0488, 0x01C5 }, /* R1160 - AIF1 DAC1 EQ Band 2 PG */ + { 0x0489, 0x1C58 }, /* R1161 - AIF1 DAC1 EQ Band 3 A */ + { 0x048A, 0xF373 }, /* R1162 - AIF1 DAC1 EQ Band 3 B */ + { 0x048B, 0x0A54 }, /* R1163 - AIF1 DAC1 EQ Band 3 C */ + { 0x048C, 0x0558 }, /* R1164 - AIF1 DAC1 EQ Band 3 PG */ + { 0x048D, 0x168E }, /* R1165 - AIF1 DAC1 EQ Band 4 A */ + { 0x048E, 0xF829 }, /* R1166 - AIF1 DAC1 EQ Band 4 B */ + { 0x048F, 0x07AD }, /* R1167 - AIF1 DAC1 EQ Band 4 C */ + { 0x0490, 0x1103 }, /* R1168 - AIF1 DAC1 EQ Band 4 PG */ + { 0x0491, 0x0564 }, /* R1169 - AIF1 DAC1 EQ Band 5 A */ + { 0x0492, 0x0559 }, /* R1170 - AIF1 DAC1 EQ Band 5 B */ + { 0x0493, 0x4000 }, /* R1171 - AIF1 DAC1 EQ Band 5 PG */ + { 0x04A0, 0x6318 }, /* R1184 - AIF1 DAC2 EQ Gains (1) */ + { 0x04A1, 0x6300 }, /* R1185 - AIF1 DAC2 EQ Gains (2) */ + { 0x04A2, 0x0FCA }, /* R1186 - AIF1 DAC2 EQ Band 1 A */ + { 0x04A3, 0x0400 }, /* R1187 - AIF1 DAC2 EQ Band 1 B */ + { 0x04A4, 0x00D8 }, /* R1188 - AIF1 DAC2 EQ Band 1 PG */ + { 0x04A5, 0x1EB5 }, /* R1189 - AIF1 DAC2 EQ Band 2 A */ + { 0x04A6, 0xF145 }, /* R1190 - AIF1 DAC2 EQ Band 2 B */ + { 0x04A7, 0x0B75 }, /* R1191 - AIF1 DAC2 EQ Band 2 C */ + { 0x04A8, 0x01C5 }, /* R1192 - AIF1 DAC2 EQ Band 2 PG */ + { 0x04A9, 0x1C58 }, /* R1193 - AIF1 DAC2 EQ Band 3 A */ + { 0x04AA, 0xF373 }, /* R1194 - AIF1 DAC2 EQ Band 3 B */ + { 0x04AB, 0x0A54 }, /* R1195 - AIF1 DAC2 EQ Band 3 C */ + { 0x04AC, 0x0558 }, /* R1196 - AIF1 DAC2 EQ Band 3 PG */ + { 0x04AD, 0x168E }, /* R1197 - AIF1 DAC2 EQ Band 4 A */ + { 0x04AE, 0xF829 }, /* R1198 - AIF1 DAC2 EQ Band 4 B */ + { 0x04AF, 0x07AD }, /* R1199 - AIF1 DAC2 EQ Band 4 C */ + { 0x04B0, 0x1103 }, /* R1200 - AIF1 DAC2 EQ Band 4 PG */ + { 0x04B1, 0x0564 }, /* R1201 - AIF1 DAC2 EQ Band 5 A */ + { 0x04B2, 0x0559 }, /* R1202 - AIF1 DAC2 EQ Band 5 B */ + { 0x04B3, 0x4000 }, /* R1203 - AIF1 DAC2 EQ Band 5 PG */ + { 0x0500, 0x00C0 }, /* R1280 - AIF2 ADC Left Volume */ + { 0x0501, 0x00C0 }, /* R1281 - AIF2 ADC Right Volume */ + { 0x0502, 0x00C0 }, /* R1282 - AIF2 DAC Left Volume */ + { 0x0503, 0x00C0 }, /* R1283 - AIF2 DAC Right Volume */ + { 0x0510, 0x0000 }, /* R1296 - AIF2 ADC Filters */ + { 0x0520, 0x0200 }, /* R1312 - AIF2 DAC Filters (1) */ + { 0x0521, 0x0010 }, /* R1313 - AIF2 DAC Filters (2) */ + { 0x0540, 0x0098 }, /* R1344 - AIF2 DRC (1) */ + { 0x0541, 0x0845 }, /* R1345 - AIF2 DRC (2) */ + { 0x0542, 0x0000 }, /* R1346 - AIF2 DRC (3) */ + { 0x0543, 0x0000 }, /* R1347 - AIF2 DRC (4) */ + { 0x0544, 0x0000 }, /* R1348 - AIF2 DRC (5) */ + { 0x0580, 0x6318 }, /* R1408 - AIF2 EQ Gains (1) */ + { 0x0581, 0x6300 }, /* R1409 - AIF2 EQ Gains (2) */ + { 0x0582, 0x0FCA }, /* R1410 - AIF2 EQ Band 1 A */ + { 0x0583, 0x0400 }, /* R1411 - AIF2 EQ Band 1 B */ + { 0x0584, 0x00D8 }, /* R1412 - AIF2 EQ Band 1 PG */ + { 0x0585, 0x1EB5 }, /* R1413 - AIF2 EQ Band 2 A */ + { 0x0586, 0xF145 }, /* R1414 - AIF2 EQ Band 2 B */ + { 0x0587, 0x0B75 }, /* R1415 - AIF2 EQ Band 2 C */ + { 0x0588, 0x01C5 }, /* R1416 - AIF2 EQ Band 2 PG */ + { 0x0589, 0x1C58 }, /* R1417 - AIF2 EQ Band 3 A */ + { 0x058A, 0xF373 }, /* R1418 - AIF2 EQ Band 3 B */ + { 0x058B, 0x0A54 }, /* R1419 - AIF2 EQ Band 3 C */ + { 0x058C, 0x0558 }, /* R1420 - AIF2 EQ Band 3 PG */ + { 0x058D, 0x168E }, /* R1421 - AIF2 EQ Band 4 A */ + { 0x058E, 0xF829 }, /* R1422 - AIF2 EQ Band 4 B */ + { 0x058F, 0x07AD }, /* R1423 - AIF2 EQ Band 4 C */ + { 0x0590, 0x1103 }, /* R1424 - AIF2 EQ Band 4 PG */ + { 0x0591, 0x0564 }, /* R1425 - AIF2 EQ Band 5 A */ + { 0x0592, 0x0559 }, /* R1426 - AIF2 EQ Band 5 B */ + { 0x0593, 0x4000 }, /* R1427 - AIF2 EQ Band 5 PG */ + { 0x0600, 0x0000 }, /* R1536 - DAC1 Mixer Volumes */ + { 0x0601, 0x0000 }, /* R1537 - DAC1 Left Mixer Routing */ + { 0x0602, 0x0000 }, /* R1538 - DAC1 Right Mixer Routing */ + { 0x0603, 0x0000 }, /* R1539 - DAC2 Mixer Volumes */ + { 0x0604, 0x0000 }, /* R1540 - DAC2 Left Mixer Routing */ + { 0x0605, 0x0000 }, /* R1541 - DAC2 Right Mixer Routing */ + { 0x0606, 0x0000 }, /* R1542 - AIF1 ADC1 Left Mixer Routing */ + { 0x0607, 0x0000 }, /* R1543 - AIF1 ADC1 Right Mixer Routing */ + { 0x0608, 0x0000 }, /* R1544 - AIF1 ADC2 Left Mixer Routing */ + { 0x0609, 0x0000 }, /* R1545 - AIF1 ADC2 Right mixer Routing */ + { 0x0610, 0x02C0 }, /* R1552 - DAC1 Left Volume */ + { 0x0611, 0x02C0 }, /* R1553 - DAC1 Right Volume */ + { 0x0612, 0x02C0 }, /* R1554 - DAC2 Left Volume */ + { 0x0613, 0x02C0 }, /* R1555 - DAC2 Right Volume */ + { 0x0614, 0x0000 }, /* R1556 - DAC Softmute */ + { 0x0620, 0x0002 }, /* R1568 - Oversampling */ + { 0x0621, 0x0000 }, /* R1569 - Sidetone */ + { 0x0700, 0x8100 }, /* R1792 - GPIO 1 */ + { 0x0701, 0xA101 }, /* R1793 - GPIO 2 */ + { 0x0702, 0xA101 }, /* R1794 - GPIO 3 */ + { 0x0703, 0xA101 }, /* R1795 - GPIO 4 */ + { 0x0704, 0xA101 }, /* R1796 - GPIO 5 */ + { 0x0705, 0xA101 }, /* R1797 - GPIO 6 */ + { 0x0706, 0xA101 }, /* R1798 - GPIO 7 */ + { 0x0707, 0xA101 }, /* R1799 - GPIO 8 */ + { 0x0708, 0xA101 }, /* R1800 - GPIO 9 */ + { 0x0709, 0xA101 }, /* R1801 - GPIO 10 */ + { 0x070A, 0xA101 }, /* R1802 - GPIO 11 */ + { 0x0720, 0x0000 }, /* R1824 - Pull Control (1) */ + { 0x0721, 0x0156 }, /* R1825 - Pull Control (2) */ + { 0x0738, 0x07FF }, /* R1848 - Interrupt Status 1 Mask */ + { 0x0739, 0xFFFF }, /* R1849 - Interrupt Status 2 Mask */ + { 0x0740, 0x0000 }, /* R1856 - Interrupt Control */ + { 0x0748, 0x003F }, /* R1864 - IRQ Debounce */ +}; + +static struct reg_default wm8958_defaults[] = { + { 0x0001, 0x0000 }, /* R1 - Power Management (1) */ + { 0x0002, 0x6000 }, /* R2 - Power Management (2) */ + { 0x0003, 0x0000 }, /* R3 - Power Management (3) */ + { 0x0004, 0x0000 }, /* R4 - Power Management (4) */ + { 0x0005, 0x0000 }, /* R5 - Power Management (5) */ + { 0x0006, 0x0000 }, /* R6 - Power Management (6) */ + { 0x0015, 0x0000 }, /* R21 - Input Mixer (1) */ + { 0x0018, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ + { 0x0019, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ + { 0x001A, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ + { 0x001B, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ + { 0x001C, 0x006D }, /* R28 - Left Output Volume */ + { 0x001D, 0x006D }, /* R29 - Right Output Volume */ + { 0x001E, 0x0066 }, /* R30 - Line Outputs Volume */ + { 0x001F, 0x0020 }, /* R31 - HPOUT2 Volume */ + { 0x0020, 0x0079 }, /* R32 - Left OPGA Volume */ + { 0x0021, 0x0079 }, /* R33 - Right OPGA Volume */ + { 0x0022, 0x0003 }, /* R34 - SPKMIXL Attenuation */ + { 0x0023, 0x0003 }, /* R35 - SPKMIXR Attenuation */ + { 0x0024, 0x0011 }, /* R36 - SPKOUT Mixers */ + { 0x0025, 0x0140 }, /* R37 - ClassD */ + { 0x0026, 0x0079 }, /* R38 - Speaker Volume Left */ + { 0x0027, 0x0079 }, /* R39 - Speaker Volume Right */ + { 0x0028, 0x0000 }, /* R40 - Input Mixer (2) */ + { 0x0029, 0x0000 }, /* R41 - Input Mixer (3) */ + { 0x002A, 0x0000 }, /* R42 - Input Mixer (4) */ + { 0x002B, 0x0000 }, /* R43 - Input Mixer (5) */ + { 0x002C, 0x0000 }, /* R44 - Input Mixer (6) */ + { 0x002D, 0x0000 }, /* R45 - Output Mixer (1) */ + { 0x002E, 0x0000 }, /* R46 - Output Mixer (2) */ + { 0x002F, 0x0000 }, /* R47 - Output Mixer (3) */ + { 0x0030, 0x0000 }, /* R48 - Output Mixer (4) */ + { 0x0031, 0x0000 }, /* R49 - Output Mixer (5) */ + { 0x0032, 0x0000 }, /* R50 - Output Mixer (6) */ + { 0x0033, 0x0000 }, /* R51 - HPOUT2 Mixer */ + { 0x0034, 0x0000 }, /* R52 - Line Mixer (1) */ + { 0x0035, 0x0000 }, /* R53 - Line Mixer (2) */ + { 0x0036, 0x0000 }, /* R54 - Speaker Mixer */ + { 0x0037, 0x0000 }, /* R55 - Additional Control */ + { 0x0038, 0x0000 }, /* R56 - AntiPOP (1) */ + { 0x0039, 0x0180 }, /* R57 - AntiPOP (2) */ + { 0x003B, 0x000D }, /* R59 - LDO 1 */ + { 0x003C, 0x0005 }, /* R60 - LDO 2 */ + { 0x003D, 0x0039 }, /* R61 - MICBIAS1 */ + { 0x003E, 0x0039 }, /* R62 - MICBIAS2 */ + { 0x004C, 0x1F25 }, /* R76 - Charge Pump (1) */ + { 0x004D, 0xAB19 }, /* R77 - Charge Pump (2) */ + { 0x0051, 0x0004 }, /* R81 - Class W (1) */ + { 0x0055, 0x054A }, /* R85 - DC Servo (2) */ + { 0x0057, 0x0000 }, /* R87 - DC Servo (4) */ + { 0x0060, 0x0000 }, /* R96 - Analogue HP (1) */ + { 0x00C5, 0x0000 }, /* R197 - Class D Test (5) */ + { 0x00D0, 0x5600 }, /* R208 - Mic Detect 1 */ + { 0x00D1, 0x007F }, /* R209 - Mic Detect 2 */ + { 0x0101, 0x8004 }, /* R257 - Control Interface */ + { 0x0110, 0x0000 }, /* R272 - Write Sequencer Ctrl (1) */ + { 0x0111, 0x0000 }, /* R273 - Write Sequencer Ctrl (2) */ + { 0x0200, 0x0000 }, /* R512 - AIF1 Clocking (1) */ + { 0x0201, 0x0000 }, /* R513 - AIF1 Clocking (2) */ + { 0x0204, 0x0000 }, /* R516 - AIF2 Clocking (1) */ + { 0x0205, 0x0000 }, /* R517 - AIF2 Clocking (2) */ + { 0x0208, 0x0000 }, /* R520 - Clocking (1) */ + { 0x0209, 0x0000 }, /* R521 - Clocking (2) */ + { 0x0210, 0x0083 }, /* R528 - AIF1 Rate */ + { 0x0211, 0x0083 }, /* R529 - AIF2 Rate */ + { 0x0220, 0x0000 }, /* R544 - FLL1 Control (1) */ + { 0x0221, 0x0000 }, /* R545 - FLL1 Control (2) */ + { 0x0222, 0x0000 }, /* R546 - FLL1 Control (3) */ + { 0x0223, 0x0000 }, /* R547 - FLL1 Control (4) */ + { 0x0224, 0x0C80 }, /* R548 - FLL1 Control (5) */ + { 0x0226, 0x0000 }, /* R550 - FLL1 EFS 1 */ + { 0x0227, 0x0006 }, /* R551 - FLL1 EFS 2 */ + { 0x0240, 0x0000 }, /* R576 - FLL2Control (1) */ + { 0x0241, 0x0000 }, /* R577 - FLL2Control (2) */ + { 0x0242, 0x0000 }, /* R578 - FLL2Control (3) */ + { 0x0243, 0x0000 }, /* R579 - FLL2 Control (4) */ + { 0x0244, 0x0C80 }, /* R580 - FLL2Control (5) */ + { 0x0246, 0x0000 }, /* R582 - FLL2 EFS 1 */ + { 0x0247, 0x0006 }, /* R583 - FLL2 EFS 2 */ + { 0x0300, 0x4050 }, /* R768 - AIF1 Control (1) */ + { 0x0301, 0x4000 }, /* R769 - AIF1 Control (2) */ + { 0x0302, 0x0000 }, /* R770 - AIF1 Master/Slave */ + { 0x0303, 0x0040 }, /* R771 - AIF1 BCLK */ + { 0x0304, 0x0040 }, /* R772 - AIF1ADC LRCLK */ + { 0x0305, 0x0040 }, /* R773 - AIF1DAC LRCLK */ + { 0x0306, 0x0004 }, /* R774 - AIF1DAC Data */ + { 0x0307, 0x0100 }, /* R775 - AIF1ADC Data */ + { 0x0310, 0x4053 }, /* R784 - AIF2 Control (1) */ + { 0x0311, 0x4000 }, /* R785 - AIF2 Control (2) */ + { 0x0312, 0x0000 }, /* R786 - AIF2 Master/Slave */ + { 0x0313, 0x0040 }, /* R787 - AIF2 BCLK */ + { 0x0314, 0x0040 }, /* R788 - AIF2ADC LRCLK */ + { 0x0315, 0x0040 }, /* R789 - AIF2DAC LRCLK */ + { 0x0316, 0x0000 }, /* R790 - AIF2DAC Data */ + { 0x0317, 0x0000 }, /* R791 - AIF2ADC Data */ + { 0x0320, 0x0040 }, /* R800 - AIF3 Control (1) */ + { 0x0321, 0x0000 }, /* R801 - AIF3 Control (2) */ + { 0x0322, 0x0000 }, /* R802 - AIF3DAC Data */ + { 0x0323, 0x0000 }, /* R803 - AIF3ADC Data */ + { 0x0400, 0x00C0 }, /* R1024 - AIF1 ADC1 Left Volume */ + { 0x0401, 0x00C0 }, /* R1025 - AIF1 ADC1 Right Volume */ + { 0x0402, 0x00C0 }, /* R1026 - AIF1 DAC1 Left Volume */ + { 0x0403, 0x00C0 }, /* R1027 - AIF1 DAC1 Right Volume */ + { 0x0404, 0x00C0 }, /* R1028 - AIF1 ADC2 Left Volume */ + { 0x0405, 0x00C0 }, /* R1029 - AIF1 ADC2 Right Volume */ + { 0x0406, 0x00C0 }, /* R1030 - AIF1 DAC2 Left Volume */ + { 0x0407, 0x00C0 }, /* R1031 - AIF1 DAC2 Right Volume */ + { 0x0410, 0x0000 }, /* R1040 - AIF1 ADC1 Filters */ + { 0x0411, 0x0000 }, /* R1041 - AIF1 ADC2 Filters */ + { 0x0420, 0x0200 }, /* R1056 - AIF1 DAC1 Filters (1) */ + { 0x0421, 0x0010 }, /* R1057 - AIF1 DAC1 Filters (2) */ + { 0x0422, 0x0200 }, /* R1058 - AIF1 DAC2 Filters (1) */ + { 0x0423, 0x0010 }, /* R1059 - AIF1 DAC2 Filters (2) */ + { 0x0430, 0x0068 }, /* R1072 - AIF1 DAC1 Noise Gate */ + { 0x0431, 0x0068 }, /* R1073 - AIF1 DAC2 Noise Gate */ + { 0x0440, 0x0098 }, /* R1088 - AIF1 DRC1 (1) */ + { 0x0441, 0x0845 }, /* R1089 - AIF1 DRC1 (2) */ + { 0x0442, 0x0000 }, /* R1090 - AIF1 DRC1 (3) */ + { 0x0443, 0x0000 }, /* R1091 - AIF1 DRC1 (4) */ + { 0x0444, 0x0000 }, /* R1092 - AIF1 DRC1 (5) */ + { 0x0450, 0x0098 }, /* R1104 - AIF1 DRC2 (1) */ + { 0x0451, 0x0845 }, /* R1105 - AIF1 DRC2 (2) */ + { 0x0452, 0x0000 }, /* R1106 - AIF1 DRC2 (3) */ + { 0x0453, 0x0000 }, /* R1107 - AIF1 DRC2 (4) */ + { 0x0454, 0x0000 }, /* R1108 - AIF1 DRC2 (5) */ + { 0x0480, 0x6318 }, /* R1152 - AIF1 DAC1 EQ Gains (1) */ + { 0x0481, 0x6300 }, /* R1153 - AIF1 DAC1 EQ Gains (2) */ + { 0x0482, 0x0FCA }, /* R1154 - AIF1 DAC1 EQ Band 1 A */ + { 0x0483, 0x0400 }, /* R1155 - AIF1 DAC1 EQ Band 1 B */ + { 0x0484, 0x00D8 }, /* R1156 - AIF1 DAC1 EQ Band 1 PG */ + { 0x0485, 0x1EB5 }, /* R1157 - AIF1 DAC1 EQ Band 2 A */ + { 0x0486, 0xF145 }, /* R1158 - AIF1 DAC1 EQ Band 2 B */ + { 0x0487, 0x0B75 }, /* R1159 - AIF1 DAC1 EQ Band 2 C */ + { 0x0488, 0x01C5 }, /* R1160 - AIF1 DAC1 EQ Band 2 PG */ + { 0x0489, 0x1C58 }, /* R1161 - AIF1 DAC1 EQ Band 3 A */ + { 0x048A, 0xF373 }, /* R1162 - AIF1 DAC1 EQ Band 3 B */ + { 0x048B, 0x0A54 }, /* R1163 - AIF1 DAC1 EQ Band 3 C */ + { 0x048C, 0x0558 }, /* R1164 - AIF1 DAC1 EQ Band 3 PG */ + { 0x048D, 0x168E }, /* R1165 - AIF1 DAC1 EQ Band 4 A */ + { 0x048E, 0xF829 }, /* R1166 - AIF1 DAC1 EQ Band 4 B */ + { 0x048F, 0x07AD }, /* R1167 - AIF1 DAC1 EQ Band 4 C */ + { 0x0490, 0x1103 }, /* R1168 - AIF1 DAC1 EQ Band 4 PG */ + { 0x0491, 0x0564 }, /* R1169 - AIF1 DAC1 EQ Band 5 A */ + { 0x0492, 0x0559 }, /* R1170 - AIF1 DAC1 EQ Band 5 B */ + { 0x0493, 0x4000 }, /* R1171 - AIF1 DAC1 EQ Band 5 PG */ + { 0x0494, 0x0000 }, /* R1172 - AIF1 DAC1 EQ Band 1 C */ + { 0x04A0, 0x6318 }, /* R1184 - AIF1 DAC2 EQ Gains (1) */ + { 0x04A1, 0x6300 }, /* R1185 - AIF1 DAC2 EQ Gains (2) */ + { 0x04A2, 0x0FCA }, /* R1186 - AIF1 DAC2 EQ Band 1 A */ + { 0x04A3, 0x0400 }, /* R1187 - AIF1 DAC2 EQ Band 1 B */ + { 0x04A4, 0x00D8 }, /* R1188 - AIF1 DAC2 EQ Band 1 PG */ + { 0x04A5, 0x1EB5 }, /* R1189 - AIF1 DAC2 EQ Band 2 A */ + { 0x04A6, 0xF145 }, /* R1190 - AIF1 DAC2 EQ Band 2 B */ + { 0x04A7, 0x0B75 }, /* R1191 - AIF1 DAC2 EQ Band 2 C */ + { 0x04A8, 0x01C5 }, /* R1192 - AIF1 DAC2 EQ Band 2 PG */ + { 0x04A9, 0x1C58 }, /* R1193 - AIF1 DAC2 EQ Band 3 A */ + { 0x04AA, 0xF373 }, /* R1194 - AIF1 DAC2 EQ Band 3 B */ + { 0x04AB, 0x0A54 }, /* R1195 - AIF1 DAC2 EQ Band 3 C */ + { 0x04AC, 0x0558 }, /* R1196 - AIF1 DAC2 EQ Band 3 PG */ + { 0x04AD, 0x168E }, /* R1197 - AIF1 DAC2 EQ Band 4 A */ + { 0x04AE, 0xF829 }, /* R1198 - AIF1 DAC2 EQ Band 4 B */ + { 0x04AF, 0x07AD }, /* R1199 - AIF1 DAC2 EQ Band 4 C */ + { 0x04B0, 0x1103 }, /* R1200 - AIF1 DAC2 EQ Band 4 PG */ + { 0x04B1, 0x0564 }, /* R1201 - AIF1 DAC2 EQ Band 5 A */ + { 0x04B2, 0x0559 }, /* R1202 - AIF1 DAC2 EQ Band 5 B */ + { 0x04B3, 0x4000 }, /* R1203 - AIF1 DAC2 EQ Band 5 PG */ + { 0x04B4, 0x0000 }, /* R1204 - AIF1 DAC2EQ Band 1 C */ + { 0x0500, 0x00C0 }, /* R1280 - AIF2 ADC Left Volume */ + { 0x0501, 0x00C0 }, /* R1281 - AIF2 ADC Right Volume */ + { 0x0502, 0x00C0 }, /* R1282 - AIF2 DAC Left Volume */ + { 0x0503, 0x00C0 }, /* R1283 - AIF2 DAC Right Volume */ + { 0x0510, 0x0000 }, /* R1296 - AIF2 ADC Filters */ + { 0x0520, 0x0200 }, /* R1312 - AIF2 DAC Filters (1) */ + { 0x0521, 0x0010 }, /* R1313 - AIF2 DAC Filters (2) */ + { 0x0530, 0x0068 }, /* R1328 - AIF2 DAC Noise Gate */ + { 0x0540, 0x0098 }, /* R1344 - AIF2 DRC (1) */ + { 0x0541, 0x0845 }, /* R1345 - AIF2 DRC (2) */ + { 0x0542, 0x0000 }, /* R1346 - AIF2 DRC (3) */ + { 0x0543, 0x0000 }, /* R1347 - AIF2 DRC (4) */ + { 0x0544, 0x0000 }, /* R1348 - AIF2 DRC (5) */ + { 0x0580, 0x6318 }, /* R1408 - AIF2 EQ Gains (1) */ + { 0x0581, 0x6300 }, /* R1409 - AIF2 EQ Gains (2) */ + { 0x0582, 0x0FCA }, /* R1410 - AIF2 EQ Band 1 A */ + { 0x0583, 0x0400 }, /* R1411 - AIF2 EQ Band 1 B */ + { 0x0584, 0x00D8 }, /* R1412 - AIF2 EQ Band 1 PG */ + { 0x0585, 0x1EB5 }, /* R1413 - AIF2 EQ Band 2 A */ + { 0x0586, 0xF145 }, /* R1414 - AIF2 EQ Band 2 B */ + { 0x0587, 0x0B75 }, /* R1415 - AIF2 EQ Band 2 C */ + { 0x0588, 0x01C5 }, /* R1416 - AIF2 EQ Band 2 PG */ + { 0x0589, 0x1C58 }, /* R1417 - AIF2 EQ Band 3 A */ + { 0x058A, 0xF373 }, /* R1418 - AIF2 EQ Band 3 B */ + { 0x058B, 0x0A54 }, /* R1419 - AIF2 EQ Band 3 C */ + { 0x058C, 0x0558 }, /* R1420 - AIF2 EQ Band 3 PG */ + { 0x058D, 0x168E }, /* R1421 - AIF2 EQ Band 4 A */ + { 0x058E, 0xF829 }, /* R1422 - AIF2 EQ Band 4 B */ + { 0x058F, 0x07AD }, /* R1423 - AIF2 EQ Band 4 C */ + { 0x0590, 0x1103 }, /* R1424 - AIF2 EQ Band 4 PG */ + { 0x0591, 0x0564 }, /* R1425 - AIF2 EQ Band 5 A */ + { 0x0592, 0x0559 }, /* R1426 - AIF2 EQ Band 5 B */ + { 0x0593, 0x4000 }, /* R1427 - AIF2 EQ Band 5 PG */ + { 0x0594, 0x0000 }, /* R1428 - AIF2 EQ Band 1 C */ + { 0x0600, 0x0000 }, /* R1536 - DAC1 Mixer Volumes */ + { 0x0601, 0x0000 }, /* R1537 - DAC1 Left Mixer Routing */ + { 0x0602, 0x0000 }, /* R1538 - DAC1 Right Mixer Routing */ + { 0x0603, 0x0000 }, /* R1539 - DAC2 Mixer Volumes */ + { 0x0604, 0x0000 }, /* R1540 - DAC2 Left Mixer Routing */ + { 0x0605, 0x0000 }, /* R1541 - DAC2 Right Mixer Routing */ + { 0x0606, 0x0000 }, /* R1542 - AIF1 ADC1 Left Mixer Routing */ + { 0x0607, 0x0000 }, /* R1543 - AIF1 ADC1 Right Mixer Routing */ + { 0x0608, 0x0000 }, /* R1544 - AIF1 ADC2 Left Mixer Routing */ + { 0x0609, 0x0000 }, /* R1545 - AIF1 ADC2 Right mixer Routing */ + { 0x0610, 0x02C0 }, /* R1552 - DAC1 Left Volume */ + { 0x0611, 0x02C0 }, /* R1553 - DAC1 Right Volume */ + { 0x0612, 0x02C0 }, /* R1554 - DAC2 Left Volume */ + { 0x0613, 0x02C0 }, /* R1555 - DAC2 Right Volume */ + { 0x0614, 0x0000 }, /* R1556 - DAC Softmute */ + { 0x0620, 0x0002 }, /* R1568 - Oversampling */ + { 0x0621, 0x0000 }, /* R1569 - Sidetone */ + { 0x0700, 0x8100 }, /* R1792 - GPIO 1 */ + { 0x0701, 0xA101 }, /* R1793 - Pull Control (MCLK2) */ + { 0x0702, 0xA101 }, /* R1794 - Pull Control (BCLK2) */ + { 0x0703, 0xA101 }, /* R1795 - Pull Control (DACLRCLK2) */ + { 0x0704, 0xA101 }, /* R1796 - Pull Control (DACDAT2) */ + { 0x0705, 0xA101 }, /* R1797 - GPIO 6 */ + { 0x0707, 0xA101 }, /* R1799 - GPIO 8 */ + { 0x0708, 0xA101 }, /* R1800 - GPIO 9 */ + { 0x0709, 0xA101 }, /* R1801 - GPIO 10 */ + { 0x070A, 0xA101 }, /* R1802 - GPIO 11 */ + { 0x0720, 0x0000 }, /* R1824 - Pull Control (1) */ + { 0x0721, 0x0156 }, /* R1825 - Pull Control (2) */ + { 0x0738, 0x07FF }, /* R1848 - Interrupt Status 1 Mask */ + { 0x0739, 0xFFEF }, /* R1849 - Interrupt Status 2 Mask */ + { 0x0740, 0x0000 }, /* R1856 - Interrupt Control */ + { 0x0748, 0x003F }, /* R1864 - IRQ Debounce */ + { 0x0900, 0x1C00 }, /* R2304 - DSP2_Program */ + { 0x0901, 0x0000 }, /* R2305 - DSP2_Config */ + { 0x0A0D, 0x0000 }, /* R2573 - DSP2_ExecControl */ + { 0x2400, 0x003F }, /* R9216 - MBC Band 1 K (1) */ + { 0x2401, 0x8BD8 }, /* R9217 - MBC Band 1 K (2) */ + { 0x2402, 0x0032 }, /* R9218 - MBC Band 1 N1 (1) */ + { 0x2403, 0xF52D }, /* R9219 - MBC Band 1 N1 (2) */ + { 0x2404, 0x0065 }, /* R9220 - MBC Band 1 N2 (1) */ + { 0x2405, 0xAC8C }, /* R9221 - MBC Band 1 N2 (2) */ + { 0x2406, 0x006B }, /* R9222 - MBC Band 1 N3 (1) */ + { 0x2407, 0xE087 }, /* R9223 - MBC Band 1 N3 (2) */ + { 0x2408, 0x0072 }, /* R9224 - MBC Band 1 N4 (1) */ + { 0x2409, 0x1483 }, /* R9225 - MBC Band 1 N4 (2) */ + { 0x240A, 0x0072 }, /* R9226 - MBC Band 1 N5 (1) */ + { 0x240B, 0x1483 }, /* R9227 - MBC Band 1 N5 (2) */ + { 0x240C, 0x0043 }, /* R9228 - MBC Band 1 X1 (1) */ + { 0x240D, 0x3525 }, /* R9229 - MBC Band 1 X1 (2) */ + { 0x240E, 0x0006 }, /* R9230 - MBC Band 1 X2 (1) */ + { 0x240F, 0x6A4A }, /* R9231 - MBC Band 1 X2 (2) */ + { 0x2410, 0x0043 }, /* R9232 - MBC Band 1 X3 (1) */ + { 0x2411, 0x6079 }, /* R9233 - MBC Band 1 X3 (2) */ + { 0x2412, 0x000C }, /* R9234 - MBC Band 1 Attack (1) */ + { 0x2413, 0xCCCD }, /* R9235 - MBC Band 1 Attack (2) */ + { 0x2414, 0x0000 }, /* R9236 - MBC Band 1 Decay (1) */ + { 0x2415, 0x0800 }, /* R9237 - MBC Band 1 Decay (2) */ + { 0x2416, 0x003F }, /* R9238 - MBC Band 2 K (1) */ + { 0x2417, 0x8BD8 }, /* R9239 - MBC Band 2 K (2) */ + { 0x2418, 0x0032 }, /* R9240 - MBC Band 2 N1 (1) */ + { 0x2419, 0xF52D }, /* R9241 - MBC Band 2 N1 (2) */ + { 0x241A, 0x0065 }, /* R9242 - MBC Band 2 N2 (1) */ + { 0x241B, 0xAC8C }, /* R9243 - MBC Band 2 N2 (2) */ + { 0x241C, 0x006B }, /* R9244 - MBC Band 2 N3 (1) */ + { 0x241D, 0xE087 }, /* R9245 - MBC Band 2 N3 (2) */ + { 0x241E, 0x0072 }, /* R9246 - MBC Band 2 N4 (1) */ + { 0x241F, 0x1483 }, /* R9247 - MBC Band 2 N4 (2) */ + { 0x2420, 0x0072 }, /* R9248 - MBC Band 2 N5 (1) */ + { 0x2421, 0x1483 }, /* R9249 - MBC Band 2 N5 (2) */ + { 0x2422, 0x0043 }, /* R9250 - MBC Band 2 X1 (1) */ + { 0x2423, 0x3525 }, /* R9251 - MBC Band 2 X1 (2) */ + { 0x2424, 0x0006 }, /* R9252 - MBC Band 2 X2 (1) */ + { 0x2425, 0x6A4A }, /* R9253 - MBC Band 2 X2 (2) */ + { 0x2426, 0x0043 }, /* R9254 - MBC Band 2 X3 (1) */ + { 0x2427, 0x6079 }, /* R9255 - MBC Band 2 X3 (2) */ + { 0x2428, 0x000C }, /* R9256 - MBC Band 2 Attack (1) */ + { 0x2429, 0xCCCD }, /* R9257 - MBC Band 2 Attack (2) */ + { 0x242A, 0x0000 }, /* R9258 - MBC Band 2 Decay (1) */ + { 0x242B, 0x0800 }, /* R9259 - MBC Band 2 Decay (2) */ + { 0x242C, 0x005A }, /* R9260 - MBC_B2_PG2 (1) */ + { 0x242D, 0x7EFA }, /* R9261 - MBC_B2_PG2 (2) */ + { 0x242E, 0x005A }, /* R9262 - MBC_B1_PG2 (1) */ + { 0x242F, 0x7EFA }, /* R9263 - MBC_B1_PG2 (2) */ + { 0x2600, 0x00A7 }, /* R9728 - MBC Crossover (1) */ + { 0x2601, 0x0D1C }, /* R9729 - MBC Crossover (2) */ + { 0x2602, 0x0083 }, /* R9730 - MBC HPF (1) */ + { 0x2603, 0x98AD }, /* R9731 - MBC HPF (2) */ + { 0x2606, 0x0008 }, /* R9734 - MBC LPF (1) */ + { 0x2607, 0xE7A2 }, /* R9735 - MBC LPF (2) */ + { 0x260A, 0x0055 }, /* R9738 - MBC RMS Limit (1) */ + { 0x260B, 0x8C4B }, /* R9739 - MBC RMS Limit (2) */ +}; + +static bool wm1811_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8994_SOFTWARE_RESET: + case WM8994_POWER_MANAGEMENT_1: + case WM8994_POWER_MANAGEMENT_2: + case WM8994_POWER_MANAGEMENT_3: + case WM8994_POWER_MANAGEMENT_4: + case WM8994_POWER_MANAGEMENT_5: + case WM8994_POWER_MANAGEMENT_6: + case WM8994_INPUT_MIXER_1: + case WM8994_LEFT_LINE_INPUT_1_2_VOLUME: + case WM8994_LEFT_LINE_INPUT_3_4_VOLUME: + case WM8994_RIGHT_LINE_INPUT_1_2_VOLUME: + case WM8994_RIGHT_LINE_INPUT_3_4_VOLUME: + case WM8994_LEFT_OUTPUT_VOLUME: + case WM8994_RIGHT_OUTPUT_VOLUME: + case WM8994_LINE_OUTPUTS_VOLUME: + case WM8994_HPOUT2_VOLUME: + case WM8994_LEFT_OPGA_VOLUME: + case WM8994_RIGHT_OPGA_VOLUME: + case WM8994_SPKMIXL_ATTENUATION: + case WM8994_SPKMIXR_ATTENUATION: + case WM8994_SPKOUT_MIXERS: + case WM8994_CLASSD: + case WM8994_SPEAKER_VOLUME_LEFT: + case WM8994_SPEAKER_VOLUME_RIGHT: + case WM8994_INPUT_MIXER_2: + case WM8994_INPUT_MIXER_3: + case WM8994_INPUT_MIXER_4: + case WM8994_INPUT_MIXER_5: + case WM8994_INPUT_MIXER_6: + case WM8994_OUTPUT_MIXER_1: + case WM8994_OUTPUT_MIXER_2: + case WM8994_OUTPUT_MIXER_3: + case WM8994_OUTPUT_MIXER_4: + case WM8994_OUTPUT_MIXER_5: + case WM8994_OUTPUT_MIXER_6: + case WM8994_HPOUT2_MIXER: + case WM8994_LINE_MIXER_1: + case WM8994_LINE_MIXER_2: + case WM8994_SPEAKER_MIXER: + case WM8994_ADDITIONAL_CONTROL: + case WM8994_ANTIPOP_1: + case WM8994_ANTIPOP_2: + case WM8994_LDO_1: + case WM8994_LDO_2: + case WM8958_MICBIAS1: + case WM8958_MICBIAS2: + case WM8994_CHARGE_PUMP_1: + case WM8958_CHARGE_PUMP_2: + case WM8994_CLASS_W_1: + case WM8994_DC_SERVO_1: + case WM8994_DC_SERVO_2: + case WM8994_DC_SERVO_READBACK: + case WM8994_DC_SERVO_4: + case WM8994_DC_SERVO_4E: + case WM8994_ANALOGUE_HP_1: + case WM8958_MIC_DETECT_1: + case WM8958_MIC_DETECT_2: + case WM8958_MIC_DETECT_3: + case WM8994_CHIP_REVISION: + case WM8994_CONTROL_INTERFACE: + case WM8994_AIF1_CLOCKING_1: + case WM8994_AIF1_CLOCKING_2: + case WM8994_AIF2_CLOCKING_1: + case WM8994_AIF2_CLOCKING_2: + case WM8994_CLOCKING_1: + case WM8994_CLOCKING_2: + case WM8994_AIF1_RATE: + case WM8994_AIF2_RATE: + case WM8994_RATE_STATUS: + case WM8994_FLL1_CONTROL_1: + case WM8994_FLL1_CONTROL_2: + case WM8994_FLL1_CONTROL_3: + case WM8994_FLL1_CONTROL_4: + case WM8994_FLL1_CONTROL_5: + case WM8958_FLL1_EFS_1: + case WM8958_FLL1_EFS_2: + case WM8994_FLL2_CONTROL_1: + case WM8994_FLL2_CONTROL_2: + case WM8994_FLL2_CONTROL_3: + case WM8994_FLL2_CONTROL_4: + case WM8994_FLL2_CONTROL_5: + case WM8958_FLL2_EFS_1: + case WM8958_FLL2_EFS_2: + case WM8994_AIF1_CONTROL_1: + case WM8994_AIF1_CONTROL_2: + case WM8994_AIF1_MASTER_SLAVE: + case WM8994_AIF1_BCLK: + case WM8994_AIF1ADC_LRCLK: + case WM8994_AIF1DAC_LRCLK: + case WM8994_AIF1DAC_DATA: + case WM8994_AIF1ADC_DATA: + case WM8994_AIF2_CONTROL_1: + case WM8994_AIF2_CONTROL_2: + case WM8994_AIF2_MASTER_SLAVE: + case WM8994_AIF2_BCLK: + case WM8994_AIF2ADC_LRCLK: + case WM8994_AIF2DAC_LRCLK: + case WM8994_AIF2DAC_DATA: + case WM8994_AIF2ADC_DATA: + case WM1811_AIF2TX_CONTROL: + case WM8958_AIF3_CONTROL_1: + case WM8958_AIF3_CONTROL_2: + case WM8958_AIF3DAC_DATA: + case WM8958_AIF3ADC_DATA: + case WM8994_AIF1_ADC1_LEFT_VOLUME: + case WM8994_AIF1_ADC1_RIGHT_VOLUME: + case WM8994_AIF1_DAC1_LEFT_VOLUME: + case WM8994_AIF1_DAC1_RIGHT_VOLUME: + case WM8994_AIF1_ADC1_FILTERS: + case WM8994_AIF1_ADC2_FILTERS: + case WM8994_AIF1_DAC1_FILTERS_1: + case WM8994_AIF1_DAC1_FILTERS_2: + case WM8994_AIF1_DAC2_FILTERS_1: + case WM8994_AIF1_DAC2_FILTERS_2: + case WM8958_AIF1_DAC1_NOISE_GATE: + case WM8958_AIF1_DAC2_NOISE_GATE: + case WM8994_AIF1_DRC1_1: + case WM8994_AIF1_DRC1_2: + case WM8994_AIF1_DRC1_3: + case WM8994_AIF1_DRC1_4: + case WM8994_AIF1_DRC1_5: + case WM8994_AIF1_DRC2_1: + case WM8994_AIF1_DRC2_2: + case WM8994_AIF1_DRC2_3: + case WM8994_AIF1_DRC2_4: + case WM8994_AIF1_DRC2_5: + case WM8994_AIF1_DAC1_EQ_GAINS_1: + case WM8994_AIF1_DAC1_EQ_GAINS_2: + case WM8994_AIF1_DAC1_EQ_BAND_1_A: + case WM8994_AIF1_DAC1_EQ_BAND_1_B: + case WM8994_AIF1_DAC1_EQ_BAND_1_PG: + case WM8994_AIF1_DAC1_EQ_BAND_2_A: + case WM8994_AIF1_DAC1_EQ_BAND_2_B: + case WM8994_AIF1_DAC1_EQ_BAND_2_C: + case WM8994_AIF1_DAC1_EQ_BAND_2_PG: + case WM8994_AIF1_DAC1_EQ_BAND_3_A: + case WM8994_AIF1_DAC1_EQ_BAND_3_B: + case WM8994_AIF1_DAC1_EQ_BAND_3_C: + case WM8994_AIF1_DAC1_EQ_BAND_3_PG: + case WM8994_AIF1_DAC1_EQ_BAND_4_A: + case WM8994_AIF1_DAC1_EQ_BAND_4_B: + case WM8994_AIF1_DAC1_EQ_BAND_4_C: + case WM8994_AIF1_DAC1_EQ_BAND_4_PG: + case WM8994_AIF1_DAC1_EQ_BAND_5_A: + case WM8994_AIF1_DAC1_EQ_BAND_5_B: + case WM8994_AIF1_DAC1_EQ_BAND_5_PG: + case WM8994_AIF1_DAC1_EQ_BAND_1_C: + case WM8994_AIF1_DAC2_EQ_GAINS_1: + case WM8994_AIF1_DAC2_EQ_GAINS_2: + case WM8994_AIF1_DAC2_EQ_BAND_1_A: + case WM8994_AIF1_DAC2_EQ_BAND_1_B: + case WM8994_AIF1_DAC2_EQ_BAND_1_PG: + case WM8994_AIF1_DAC2_EQ_BAND_2_A: + case WM8994_AIF1_DAC2_EQ_BAND_2_B: + case WM8994_AIF1_DAC2_EQ_BAND_2_C: + case WM8994_AIF1_DAC2_EQ_BAND_2_PG: + case WM8994_AIF1_DAC2_EQ_BAND_3_A: + case WM8994_AIF1_DAC2_EQ_BAND_3_B: + case WM8994_AIF1_DAC2_EQ_BAND_3_C: + case WM8994_AIF1_DAC2_EQ_BAND_3_PG: + case WM8994_AIF1_DAC2_EQ_BAND_4_A: + case WM8994_AIF1_DAC2_EQ_BAND_4_B: + case WM8994_AIF1_DAC2_EQ_BAND_4_C: + case WM8994_AIF1_DAC2_EQ_BAND_4_PG: + case WM8994_AIF1_DAC2_EQ_BAND_5_A: + case WM8994_AIF1_DAC2_EQ_BAND_5_B: + case WM8994_AIF1_DAC2_EQ_BAND_5_PG: + case WM8994_AIF1_DAC2_EQ_BAND_1_C: + case WM8994_AIF2_ADC_LEFT_VOLUME: + case WM8994_AIF2_ADC_RIGHT_VOLUME: + case WM8994_AIF2_DAC_LEFT_VOLUME: + case WM8994_AIF2_DAC_RIGHT_VOLUME: + case WM8994_AIF2_ADC_FILTERS: + case WM8994_AIF2_DAC_FILTERS_1: + case WM8994_AIF2_DAC_FILTERS_2: + case WM8958_AIF2_DAC_NOISE_GATE: + case WM8994_AIF2_DRC_1: + case WM8994_AIF2_DRC_2: + case WM8994_AIF2_DRC_3: + case WM8994_AIF2_DRC_4: + case WM8994_AIF2_DRC_5: + case WM8994_AIF2_EQ_GAINS_1: + case WM8994_AIF2_EQ_GAINS_2: + case WM8994_AIF2_EQ_BAND_1_A: + case WM8994_AIF2_EQ_BAND_1_B: + case WM8994_AIF2_EQ_BAND_1_PG: + case WM8994_AIF2_EQ_BAND_2_A: + case WM8994_AIF2_EQ_BAND_2_B: + case WM8994_AIF2_EQ_BAND_2_C: + case WM8994_AIF2_EQ_BAND_2_PG: + case WM8994_AIF2_EQ_BAND_3_A: + case WM8994_AIF2_EQ_BAND_3_B: + case WM8994_AIF2_EQ_BAND_3_C: + case WM8994_AIF2_EQ_BAND_3_PG: + case WM8994_AIF2_EQ_BAND_4_A: + case WM8994_AIF2_EQ_BAND_4_B: + case WM8994_AIF2_EQ_BAND_4_C: + case WM8994_AIF2_EQ_BAND_4_PG: + case WM8994_AIF2_EQ_BAND_5_A: + case WM8994_AIF2_EQ_BAND_5_B: + case WM8994_AIF2_EQ_BAND_5_PG: + case WM8994_AIF2_EQ_BAND_1_C: + case WM8994_DAC1_MIXER_VOLUMES: + case WM8994_DAC1_LEFT_MIXER_ROUTING: + case WM8994_DAC1_RIGHT_MIXER_ROUTING: + case WM8994_DAC2_MIXER_VOLUMES: + case WM8994_DAC2_LEFT_MIXER_ROUTING: + case WM8994_DAC2_RIGHT_MIXER_ROUTING: + case WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING: + case WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING: + case WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING: + case WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING: + case WM8994_DAC1_LEFT_VOLUME: + case WM8994_DAC1_RIGHT_VOLUME: + case WM8994_DAC2_LEFT_VOLUME: + case WM8994_DAC2_RIGHT_VOLUME: + case WM8994_DAC_SOFTMUTE: + case WM8994_OVERSAMPLING: + case WM8994_SIDETONE: + case WM8994_GPIO_1: + case WM8994_GPIO_2: + case WM8994_GPIO_3: + case WM8994_GPIO_4: + case WM8994_GPIO_5: + case WM8994_GPIO_6: + case WM8994_GPIO_8: + case WM8994_GPIO_9: + case WM8994_GPIO_10: + case WM8994_GPIO_11: + case WM8994_PULL_CONTROL_1: + case WM8994_PULL_CONTROL_2: + case WM8994_INTERRUPT_STATUS_1: + case WM8994_INTERRUPT_STATUS_2: + case WM8994_INTERRUPT_RAW_STATUS_2: + case WM8994_INTERRUPT_STATUS_1_MASK: + case WM8994_INTERRUPT_STATUS_2_MASK: + case WM8994_INTERRUPT_CONTROL: + case WM8994_IRQ_DEBOUNCE: + return true; + default: + return false; + } +} + +static bool wm8994_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8994_DC_SERVO_READBACK: + case WM8994_MICBIAS: + case WM8994_WRITE_SEQUENCER_CTRL_1: + case WM8994_WRITE_SEQUENCER_CTRL_2: + case WM8994_AIF1_ADC2_LEFT_VOLUME: + case WM8994_AIF1_ADC2_RIGHT_VOLUME: + case WM8994_AIF1_DAC2_LEFT_VOLUME: + case WM8994_AIF1_DAC2_RIGHT_VOLUME: + case WM8994_AIF1_ADC2_FILTERS: + case WM8994_AIF1_DAC2_FILTERS_1: + case WM8994_AIF1_DAC2_FILTERS_2: + case WM8958_AIF1_DAC2_NOISE_GATE: + case WM8994_AIF1_DRC2_1: + case WM8994_AIF1_DRC2_2: + case WM8994_AIF1_DRC2_3: + case WM8994_AIF1_DRC2_4: + case WM8994_AIF1_DRC2_5: + case WM8994_AIF1_DAC2_EQ_GAINS_1: + case WM8994_AIF1_DAC2_EQ_GAINS_2: + case WM8994_AIF1_DAC2_EQ_BAND_1_A: + case WM8994_AIF1_DAC2_EQ_BAND_1_B: + case WM8994_AIF1_DAC2_EQ_BAND_1_PG: + case WM8994_AIF1_DAC2_EQ_BAND_2_A: + case WM8994_AIF1_DAC2_EQ_BAND_2_B: + case WM8994_AIF1_DAC2_EQ_BAND_2_C: + case WM8994_AIF1_DAC2_EQ_BAND_2_PG: + case WM8994_AIF1_DAC2_EQ_BAND_3_A: + case WM8994_AIF1_DAC2_EQ_BAND_3_B: + case WM8994_AIF1_DAC2_EQ_BAND_3_C: + case WM8994_AIF1_DAC2_EQ_BAND_3_PG: + case WM8994_AIF1_DAC2_EQ_BAND_4_A: + case WM8994_AIF1_DAC2_EQ_BAND_4_B: + case WM8994_AIF1_DAC2_EQ_BAND_4_C: + case WM8994_AIF1_DAC2_EQ_BAND_4_PG: + case WM8994_AIF1_DAC2_EQ_BAND_5_A: + case WM8994_AIF1_DAC2_EQ_BAND_5_B: + case WM8994_AIF1_DAC2_EQ_BAND_5_PG: + case WM8994_AIF1_DAC2_EQ_BAND_1_C: + case WM8994_DAC2_MIXER_VOLUMES: + case WM8994_DAC2_LEFT_MIXER_ROUTING: + case WM8994_DAC2_RIGHT_MIXER_ROUTING: + case WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING: + case WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING: + case WM8994_DAC2_LEFT_VOLUME: + case WM8994_DAC2_RIGHT_VOLUME: + return true; + default: + return wm1811_readable_register(dev, reg); + } +} + +static bool wm8958_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8958_DSP2_PROGRAM: + case WM8958_DSP2_CONFIG: + case WM8958_DSP2_MAGICNUM: + case WM8958_DSP2_RELEASEYEAR: + case WM8958_DSP2_RELEASEMONTHDAY: + case WM8958_DSP2_RELEASETIME: + case WM8958_DSP2_VERMAJMIN: + case WM8958_DSP2_VERBUILD: + case WM8958_DSP2_TESTREG: + case WM8958_DSP2_XORREG: + case WM8958_DSP2_SHIFTMAXX: + case WM8958_DSP2_SHIFTMAXY: + case WM8958_DSP2_SHIFTMAXZ: + case WM8958_DSP2_SHIFTMAXEXTLO: + case WM8958_DSP2_AESSELECT: + case WM8958_DSP2_EXECCONTROL: + case WM8958_DSP2_SAMPLEBREAK: + case WM8958_DSP2_COUNTBREAK: + case WM8958_DSP2_INTSTATUS: + case WM8958_DSP2_EVENTSTATUS: + case WM8958_DSP2_INTMASK: + case WM8958_DSP2_CONFIGDWIDTH: + case WM8958_DSP2_CONFIGINSTR: + case WM8958_DSP2_CONFIGDMEM: + case WM8958_DSP2_CONFIGDELAYS: + case WM8958_DSP2_CONFIGNUMIO: + case WM8958_DSP2_CONFIGEXTDEPTH: + case WM8958_DSP2_CONFIGMULTIPLIER: + case WM8958_DSP2_CONFIGCTRLDWIDTH: + case WM8958_DSP2_CONFIGPIPELINE: + case WM8958_DSP2_SHIFTMAXEXTHI: + case WM8958_DSP2_SWVERSIONREG: + case WM8958_DSP2_CONFIGXMEM: + case WM8958_DSP2_CONFIGYMEM: + case WM8958_DSP2_CONFIGZMEM: + case WM8958_FW_BUILD_1: + case WM8958_FW_BUILD_0: + case WM8958_FW_ID_1: + case WM8958_FW_ID_0: + case WM8958_FW_MAJOR_1: + case WM8958_FW_MAJOR_0: + case WM8958_FW_MINOR_1: + case WM8958_FW_MINOR_0: + case WM8958_FW_PATCH_1: + case WM8958_FW_PATCH_0: + case WM8958_MBC_BAND_1_K_1: + case WM8958_MBC_BAND_1_K_2: + case WM8958_MBC_BAND_1_N1_1: + case WM8958_MBC_BAND_1_N1_2: + case WM8958_MBC_BAND_1_N2_1: + case WM8958_MBC_BAND_1_N2_2: + case WM8958_MBC_BAND_1_N3_1: + case WM8958_MBC_BAND_1_N3_2: + case WM8958_MBC_BAND_1_N4_1: + case WM8958_MBC_BAND_1_N4_2: + case WM8958_MBC_BAND_1_N5_1: + case WM8958_MBC_BAND_1_N5_2: + case WM8958_MBC_BAND_1_X1_1: + case WM8958_MBC_BAND_1_X1_2: + case WM8958_MBC_BAND_1_X2_1: + case WM8958_MBC_BAND_1_X2_2: + case WM8958_MBC_BAND_1_X3_1: + case WM8958_MBC_BAND_1_X3_2: + case WM8958_MBC_BAND_1_ATTACK_1: + case WM8958_MBC_BAND_1_ATTACK_2: + case WM8958_MBC_BAND_1_DECAY_1: + case WM8958_MBC_BAND_1_DECAY_2: + case WM8958_MBC_BAND_2_K_1: + case WM8958_MBC_BAND_2_K_2: + case WM8958_MBC_BAND_2_N1_1: + case WM8958_MBC_BAND_2_N1_2: + case WM8958_MBC_BAND_2_N2_1: + case WM8958_MBC_BAND_2_N2_2: + case WM8958_MBC_BAND_2_N3_1: + case WM8958_MBC_BAND_2_N3_2: + case WM8958_MBC_BAND_2_N4_1: + case WM8958_MBC_BAND_2_N4_2: + case WM8958_MBC_BAND_2_N5_1: + case WM8958_MBC_BAND_2_N5_2: + case WM8958_MBC_BAND_2_X1_1: + case WM8958_MBC_BAND_2_X1_2: + case WM8958_MBC_BAND_2_X2_1: + case WM8958_MBC_BAND_2_X2_2: + case WM8958_MBC_BAND_2_X3_1: + case WM8958_MBC_BAND_2_X3_2: + case WM8958_MBC_BAND_2_ATTACK_1: + case WM8958_MBC_BAND_2_ATTACK_2: + case WM8958_MBC_BAND_2_DECAY_1: + case WM8958_MBC_BAND_2_DECAY_2: + case WM8958_MBC_B2_PG2_1: + case WM8958_MBC_B2_PG2_2: + case WM8958_MBC_B1_PG2_1: + case WM8958_MBC_B1_PG2_2: + case WM8958_MBC_CROSSOVER_1: + case WM8958_MBC_CROSSOVER_2: + case WM8958_MBC_HPF_1: + case WM8958_MBC_HPF_2: + case WM8958_MBC_LPF_1: + case WM8958_MBC_LPF_2: + case WM8958_MBC_RMS_LIMIT_1: + case WM8958_MBC_RMS_LIMIT_2: + return true; + default: + return wm8994_readable_register(dev, reg); + } +} + +static bool wm8994_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8994_SOFTWARE_RESET: + case WM8994_DC_SERVO_1: + case WM8994_DC_SERVO_READBACK: + case WM8994_RATE_STATUS: + case WM8958_MIC_DETECT_3: + case WM8994_DC_SERVO_4E: + case WM8994_INTERRUPT_STATUS_1: + case WM8994_INTERRUPT_STATUS_2: + return true; + default: + return false; + } +} + +static bool wm1811_volatile_register(struct device *dev, unsigned int reg) +{ + struct wm8994 *wm8994 = dev_get_drvdata(dev); + + switch (reg) { + case WM8994_GPIO_6: + if (wm8994->cust_id > 1 || wm8994->revision > 1) + return true; + else + return false; + default: + return wm8994_volatile_register(dev, reg); + } +} + +static bool wm8958_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8958_DSP2_MAGICNUM: + case WM8958_DSP2_RELEASEYEAR: + case WM8958_DSP2_RELEASEMONTHDAY: + case WM8958_DSP2_RELEASETIME: + case WM8958_DSP2_VERMAJMIN: + case WM8958_DSP2_VERBUILD: + case WM8958_DSP2_EXECCONTROL: + case WM8958_DSP2_SWVERSIONREG: + case WM8958_DSP2_CONFIGXMEM: + case WM8958_DSP2_CONFIGYMEM: + case WM8958_DSP2_CONFIGZMEM: + case WM8958_FW_BUILD_1: + case WM8958_FW_BUILD_0: + case WM8958_FW_ID_1: + case WM8958_FW_ID_0: + case WM8958_FW_MAJOR_1: + case WM8958_FW_MAJOR_0: + case WM8958_FW_MINOR_1: + case WM8958_FW_MINOR_0: + case WM8958_FW_PATCH_1: + case WM8958_FW_PATCH_0: + return true; + default: + return wm8994_volatile_register(dev, reg); + } +} + +struct regmap_config wm1811_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + + .reg_defaults = wm1811_defaults, + .num_reg_defaults = ARRAY_SIZE(wm1811_defaults), + + .max_register = WM8994_MAX_REGISTER, + .volatile_reg = wm1811_volatile_register, + .readable_reg = wm1811_readable_register, +}; +EXPORT_SYMBOL(wm1811_regmap_config); + +struct regmap_config wm8994_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + + .reg_defaults = wm8994_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8994_defaults), + + .max_register = WM8994_MAX_REGISTER, + .volatile_reg = wm8994_volatile_register, + .readable_reg = wm8994_readable_register, +}; +EXPORT_SYMBOL(wm8994_regmap_config); + +struct regmap_config wm8958_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + + .reg_defaults = wm8958_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8958_defaults), + + .max_register = WM8994_MAX_REGISTER, + .volatile_reg = wm8958_volatile_register, + .readable_reg = wm8958_readable_register, +}; +EXPORT_SYMBOL(wm8958_regmap_config); + +struct regmap_config wm8994_base_regmap_config = { + .reg_bits = 16, + .val_bits = 16, +}; +EXPORT_SYMBOL(wm8994_base_regmap_config); diff --git a/kernel/drivers/mfd/wm8994.h b/kernel/drivers/mfd/wm8994.h new file mode 100644 index 000000000..6f39a84ee --- /dev/null +++ b/kernel/drivers/mfd/wm8994.h @@ -0,0 +1,25 @@ +/* + * wm8994.h -- WM8994 MFD internals + * + * Copyright 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. + * + */ + +#ifndef __MFD_WM8994_H__ +#define __MFD_WM8994_H__ + +#include <linux/regmap.h> + +extern struct regmap_config wm1811_regmap_config; +extern struct regmap_config wm8994_regmap_config; +extern struct regmap_config wm8958_regmap_config; +extern struct regmap_config wm8994_base_regmap_config; + +#endif diff --git a/kernel/drivers/mfd/wm8997-tables.c b/kernel/drivers/mfd/wm8997-tables.c new file mode 100644 index 000000000..c0c25d75a --- /dev/null +++ b/kernel/drivers/mfd/wm8997-tables.c @@ -0,0 +1,1533 @@ +/* + * wm8997-tables.c -- WM8997 data tables + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Charles Keepax <ckeepax@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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/registers.h> + +#include "arizona.h" + +static const struct reg_default wm8997_reva_patch[] = { + { 0x80, 0x0003 }, + { 0x214, 0x0008 }, + { 0x458, 0x0000 }, + { 0x0081, 0xE022 }, + { 0x294, 0x0000 }, + { 0x80, 0x0000 }, + { 0x171, 0x0000 }, +}; + +/* We use a function so we can use ARRAY_SIZE() */ +int wm8997_patch(struct arizona *arizona) +{ + switch (arizona->rev) { + case 0: + return regmap_register_patch(arizona->regmap, + wm8997_reva_patch, + ARRAY_SIZE(wm8997_reva_patch)); + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(wm8997_patch); + +static const struct regmap_irq wm8997_aod_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_GP5_FALL] = { .mask = ARIZONA_GP5_FALL_EINT1 }, + [ARIZONA_IRQ_GP5_RISE] = { .mask = ARIZONA_GP5_RISE_EINT1 }, + [ARIZONA_IRQ_JD_FALL] = { .mask = ARIZONA_JD1_FALL_EINT1 }, + [ARIZONA_IRQ_JD_RISE] = { .mask = ARIZONA_JD1_RISE_EINT1 }, +}; + +const struct regmap_irq_chip wm8997_aod = { + .name = "wm8997 AOD", + .status_base = ARIZONA_AOD_IRQ1, + .mask_base = ARIZONA_AOD_IRQ_MASK_IRQ1, + .ack_base = ARIZONA_AOD_IRQ1, + .num_regs = 1, + .irqs = wm8997_aod_irqs, + .num_irqs = ARRAY_SIZE(wm8997_aod_irqs), +}; +EXPORT_SYMBOL_GPL(wm8997_aod); + +static const struct regmap_irq wm8997_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_GP4] = { .reg_offset = 0, .mask = ARIZONA_GP4_EINT1 }, + [ARIZONA_IRQ_GP3] = { .reg_offset = 0, .mask = ARIZONA_GP3_EINT1 }, + [ARIZONA_IRQ_GP2] = { .reg_offset = 0, .mask = ARIZONA_GP2_EINT1 }, + [ARIZONA_IRQ_GP1] = { .reg_offset = 0, .mask = ARIZONA_GP1_EINT1 }, + + [ARIZONA_IRQ_SPK_OVERHEAT_WARN] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_WARN_EINT1 + }, + [ARIZONA_IRQ_SPK_OVERHEAT] = { + .reg_offset = 2, .mask = ARIZONA_SPK_OVERHEAT_EINT1 + }, + [ARIZONA_IRQ_HPDET] = { + .reg_offset = 2, .mask = ARIZONA_HPDET_EINT1 + }, + [ARIZONA_IRQ_MICDET] = { + .reg_offset = 2, .mask = ARIZONA_MICDET_EINT1 + }, + [ARIZONA_IRQ_WSEQ_DONE] = { + .reg_offset = 2, .mask = ARIZONA_WSEQ_DONE_EINT1 + }, + [ARIZONA_IRQ_DRC1_SIG_DET] = { + .reg_offset = 2, .mask = ARIZONA_DRC1_SIG_DET_EINT1 + }, + [ARIZONA_IRQ_UNDERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_UNDERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_OVERCLOCKED] = { + .reg_offset = 2, .mask = ARIZONA_OVERCLOCKED_EINT1 + }, + [ARIZONA_IRQ_FLL2_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL2_LOCK_EINT1 + }, + [ARIZONA_IRQ_FLL1_LOCK] = { + .reg_offset = 2, .mask = ARIZONA_FLL1_LOCK_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_EINT1 + }, + [ARIZONA_IRQ_CLKGEN_ERR_ASYNC] = { + .reg_offset = 2, .mask = ARIZONA_CLKGEN_ERR_ASYNC_EINT1 + }, + + [ARIZONA_IRQ_AIF2_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF2_ERR_EINT1 + }, + [ARIZONA_IRQ_AIF1_ERR] = { + .reg_offset = 3, .mask = ARIZONA_AIF1_ERR_EINT1 + }, + [ARIZONA_IRQ_CTRLIF_ERR] = { + .reg_offset = 3, .mask = ARIZONA_CTRLIF_ERR_EINT1 + }, + [ARIZONA_IRQ_MIXER_DROPPED_SAMPLES] = { + .reg_offset = 3, .mask = ARIZONA_MIXER_DROPPED_SAMPLE_EINT1 + }, + [ARIZONA_IRQ_ASYNC_CLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_ASYNC_CLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_SYSCLK_ENA_LOW] = { + .reg_offset = 3, .mask = ARIZONA_SYSCLK_ENA_LOW_EINT1 + }, + [ARIZONA_IRQ_ISRC1_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ISRC1_CFG_ERR_EINT1 + }, + [ARIZONA_IRQ_ISRC2_CFG_ERR] = { + .reg_offset = 3, .mask = ARIZONA_ISRC2_CFG_ERR_EINT1 + }, + + [ARIZONA_IRQ_BOOT_DONE] = { + .reg_offset = 4, .mask = ARIZONA_BOOT_DONE_EINT1 + }, + [ARIZONA_IRQ_DCS_DAC_DONE] = { + .reg_offset = 4, .mask = ARIZONA_DCS_DAC_DONE_EINT1 + }, + [ARIZONA_IRQ_DCS_HP_DONE] = { + .reg_offset = 4, .mask = ARIZONA_DCS_HP_DONE_EINT1 + }, + [ARIZONA_IRQ_FLL2_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL2_CLOCK_OK_EINT1 + }, + [ARIZONA_IRQ_FLL1_CLOCK_OK] = { + .reg_offset = 4, .mask = ARIZONA_FLL1_CLOCK_OK_EINT1 + }, +}; + +const struct regmap_irq_chip wm8997_irq = { + .name = "wm8997 IRQ", + .status_base = ARIZONA_INTERRUPT_STATUS_1, + .mask_base = ARIZONA_INTERRUPT_STATUS_1_MASK, + .ack_base = ARIZONA_INTERRUPT_STATUS_1, + .num_regs = 5, + .irqs = wm8997_irqs, + .num_irqs = ARRAY_SIZE(wm8997_irqs), +}; +EXPORT_SYMBOL_GPL(wm8997_irq); + +static const struct reg_default wm8997_reg_default[] = { + { 0x00000009, 0x0001 }, /* R9 - Ctrl IF I2C1 CFG 1 */ + { 0x00000016, 0x0000 }, /* R22 - Write Sequencer Ctrl 0 */ + { 0x00000017, 0x0000 }, /* R23 - Write Sequencer Ctrl 1 */ + { 0x00000018, 0x0000 }, /* R24 - Write Sequencer Ctrl 2 */ + { 0x00000020, 0x0000 }, /* R32 - Tone Generator 1 */ + { 0x00000021, 0x1000 }, /* R33 - Tone Generator 2 */ + { 0x00000022, 0x0000 }, /* R34 - Tone Generator 3 */ + { 0x00000023, 0x1000 }, /* R35 - Tone Generator 4 */ + { 0x00000024, 0x0000 }, /* R36 - Tone Generator 5 */ + { 0x00000030, 0x0000 }, /* R48 - PWM Drive 1 */ + { 0x00000031, 0x0100 }, /* R49 - PWM Drive 2 */ + { 0x00000032, 0x0100 }, /* R50 - PWM Drive 3 */ + { 0x00000040, 0x0000 }, /* R64 - Wake control */ + { 0x00000041, 0x0000 }, /* R65 - Sequence control */ + { 0x00000061, 0x01FF }, /* R97 - Sample Rate Sequence Select 1 */ + { 0x00000062, 0x01FF }, /* R98 - Sample Rate Sequence Select 2 */ + { 0x00000063, 0x01FF }, /* R99 - Sample Rate Sequence Select 3 */ + { 0x00000064, 0x01FF }, /* R100 - Sample Rate Sequence Select 4 */ + { 0x00000068, 0x01FF }, /* R104 - AlwaysOn Triggers Seq Select 3 */ + { 0x00000069, 0x01FF }, /* R105 - AlwaysOn Triggers Seq Select 4 */ + { 0x0000006A, 0x01FF }, /* R106 - AlwaysOn Triggers Seq Select 5 */ + { 0x0000006B, 0x01FF }, /* R107 - AlwaysOn Triggers Seq Select 6 */ + { 0x00000070, 0x0000 }, /* R112 - Comfort Noise Generator */ + { 0x00000090, 0x0000 }, /* R144 - Haptics Control 1 */ + { 0x00000091, 0x7FFF }, /* R145 - Haptics Control 2 */ + { 0x00000092, 0x0000 }, /* R146 - Haptics phase 1 intensity */ + { 0x00000093, 0x0000 }, /* R147 - Haptics phase 1 duration */ + { 0x00000094, 0x0000 }, /* R148 - Haptics phase 2 intensity */ + { 0x00000095, 0x0000 }, /* R149 - Haptics phase 2 duration */ + { 0x00000096, 0x0000 }, /* R150 - Haptics phase 3 intensity */ + { 0x00000097, 0x0000 }, /* R151 - Haptics phase 3 duration */ + { 0x00000100, 0x0002 }, /* R256 - Clock 32k 1 */ + { 0x00000101, 0x0304 }, /* R257 - System Clock 1 */ + { 0x00000102, 0x0011 }, /* R258 - Sample rate 1 */ + { 0x00000103, 0x0011 }, /* R259 - Sample rate 2 */ + { 0x00000104, 0x0011 }, /* R260 - Sample rate 3 */ + { 0x00000112, 0x0305 }, /* R274 - Async clock 1 */ + { 0x00000113, 0x0011 }, /* R275 - Async sample rate 1 */ + { 0x00000149, 0x0000 }, /* R329 - Output system clock */ + { 0x0000014A, 0x0000 }, /* R330 - Output async clock */ + { 0x00000152, 0x0000 }, /* R338 - Rate Estimator 1 */ + { 0x00000153, 0x0000 }, /* R339 - Rate Estimator 2 */ + { 0x00000154, 0x0000 }, /* R340 - Rate Estimator 3 */ + { 0x00000155, 0x0000 }, /* R341 - Rate Estimator 4 */ + { 0x00000156, 0x0000 }, /* R342 - Rate Estimator 5 */ + { 0x00000161, 0x0000 }, /* R353 - Dynamic Frequency Scaling 1 */ + { 0x00000171, 0x0000 }, /* R369 - FLL1 Control 1 */ + { 0x00000172, 0x0008 }, /* R370 - FLL1 Control 2 */ + { 0x00000173, 0x0018 }, /* R371 - FLL1 Control 3 */ + { 0x00000174, 0x007D }, /* R372 - FLL1 Control 4 */ + { 0x00000175, 0x0004 }, /* R373 - FLL1 Control 5 */ + { 0x00000176, 0x0000 }, /* R374 - FLL1 Control 6 */ + { 0x00000177, 0x0181 }, /* R375 - FLL1 Loop Filter Test 1 */ + { 0x00000181, 0x0000 }, /* R385 - FLL1 Synchroniser 1 */ + { 0x00000182, 0x0000 }, /* R386 - FLL1 Synchroniser 2 */ + { 0x00000183, 0x0000 }, /* R387 - FLL1 Synchroniser 3 */ + { 0x00000184, 0x0000 }, /* R388 - FLL1 Synchroniser 4 */ + { 0x00000185, 0x0000 }, /* R389 - FLL1 Synchroniser 5 */ + { 0x00000186, 0x0000 }, /* R390 - FLL1 Synchroniser 6 */ + { 0x00000189, 0x0000 }, /* R393 - FLL1 Spread Spectrum */ + { 0x0000018A, 0x0004 }, /* R394 - FLL1 GPIO Clock */ + { 0x00000191, 0x0000 }, /* R401 - FLL2 Control 1 */ + { 0x00000192, 0x0008 }, /* R402 - FLL2 Control 2 */ + { 0x00000193, 0x0018 }, /* R403 - FLL2 Control 3 */ + { 0x00000194, 0x007D }, /* R404 - FLL2 Control 4 */ + { 0x00000195, 0x0004 }, /* R405 - FLL2 Control 5 */ + { 0x00000196, 0x0000 }, /* R406 - FLL2 Control 6 */ + { 0x00000197, 0x0000 }, /* R407 - FLL2 Loop Filter Test 1 */ + { 0x000001A1, 0x0000 }, /* R417 - FLL2 Synchroniser 1 */ + { 0x000001A2, 0x0000 }, /* R418 - FLL2 Synchroniser 2 */ + { 0x000001A3, 0x0000 }, /* R419 - FLL2 Synchroniser 3 */ + { 0x000001A4, 0x0000 }, /* R420 - FLL2 Synchroniser 4 */ + { 0x000001A5, 0x0000 }, /* R421 - FLL2 Synchroniser 5 */ + { 0x000001A6, 0x0000 }, /* R422 - FLL2 Synchroniser 6 */ + { 0x000001A9, 0x0000 }, /* R425 - FLL2 Spread Spectrum */ + { 0x000001AA, 0x0004 }, /* R426 - FLL2 GPIO Clock */ + { 0x00000200, 0x0006 }, /* R512 - Mic Charge Pump 1 */ + { 0x00000210, 0x00D4 }, /* R528 - LDO1 Control 1 */ + { 0x00000212, 0x0000 }, /* R530 - LDO1 Control 2 */ + { 0x00000213, 0x0344 }, /* R531 - LDO2 Control 1 */ + { 0x00000218, 0x01A6 }, /* R536 - Mic Bias Ctrl 1 */ + { 0x00000219, 0x01A6 }, /* R537 - Mic Bias Ctrl 2 */ + { 0x0000021A, 0x01A6 }, /* R538 - Mic Bias Ctrl 3 */ + { 0x00000293, 0x0000 }, /* R659 - Accessory Detect Mode 1 */ + { 0x0000029B, 0x0020 }, /* R667 - Headphone Detect 1 */ + { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ + { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ + { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ + { 0x000002C3, 0x0000 }, /* R707 - Mic noise mix control 1 */ + { 0x000002CB, 0x0000 }, /* R715 - Isolation control */ + { 0x000002D3, 0x0000 }, /* R723 - Jack detect analogue */ + { 0x00000300, 0x0000 }, /* R768 - Input Enables */ + { 0x00000308, 0x0000 }, /* R776 - Input Rate */ + { 0x00000309, 0x0022 }, /* R777 - Input Volume Ramp */ + { 0x00000310, 0x2080 }, /* R784 - IN1L Control */ + { 0x00000311, 0x0180 }, /* R785 - ADC Digital Volume 1L */ + { 0x00000312, 0x0000 }, /* R786 - DMIC1L Control */ + { 0x00000314, 0x0080 }, /* R788 - IN1R Control */ + { 0x00000315, 0x0180 }, /* R789 - ADC Digital Volume 1R */ + { 0x00000316, 0x0000 }, /* R790 - DMIC1R Control */ + { 0x00000318, 0x2080 }, /* R792 - IN2L Control */ + { 0x00000319, 0x0180 }, /* R793 - ADC Digital Volume 2L */ + { 0x0000031A, 0x0000 }, /* R794 - DMIC2L Control */ + { 0x0000031C, 0x0080 }, /* R796 - IN2R Control */ + { 0x0000031D, 0x0180 }, /* R797 - ADC Digital Volume 2R */ + { 0x0000031E, 0x0000 }, /* R798 - DMIC2R Control */ + { 0x00000400, 0x0000 }, /* R1024 - Output Enables 1 */ + { 0x00000408, 0x0000 }, /* R1032 - Output Rate 1 */ + { 0x00000409, 0x0022 }, /* R1033 - Output Volume Ramp */ + { 0x00000410, 0x0080 }, /* R1040 - Output Path Config 1L */ + { 0x00000411, 0x0180 }, /* R1041 - DAC Digital Volume 1L */ + { 0x00000412, 0x0080 }, /* R1042 - DAC Volume Limit 1L */ + { 0x00000413, 0x0001 }, /* R1043 - Noise Gate Select 1L */ + { 0x00000414, 0x0080 }, /* R1044 - Output Path Config 1R */ + { 0x00000415, 0x0180 }, /* R1045 - DAC Digital Volume 1R */ + { 0x00000416, 0x0080 }, /* R1046 - DAC Volume Limit 1R */ + { 0x00000417, 0x0002 }, /* R1047 - Noise Gate Select 1R */ + { 0x00000420, 0x0080 }, /* R1056 - Output Path Config 3L */ + { 0x00000421, 0x0180 }, /* R1057 - DAC Digital Volume 3L */ + { 0x00000422, 0x0080 }, /* R1058 - DAC Volume Limit 3L */ + { 0x00000423, 0x0010 }, /* R1059 - Noise Gate Select 3L */ + { 0x00000428, 0x0000 }, /* R1064 - Output Path Config 4L */ + { 0x00000429, 0x0180 }, /* R1065 - DAC Digital Volume 4L */ + { 0x0000042A, 0x0080 }, /* R1066 - Out Volume 4L */ + { 0x0000042B, 0x0040 }, /* R1067 - Noise Gate Select 4L */ + { 0x00000430, 0x0000 }, /* R1072 - Output Path Config 5L */ + { 0x00000431, 0x0180 }, /* R1073 - DAC Digital Volume 5L */ + { 0x00000432, 0x0080 }, /* R1074 - DAC Volume Limit 5L */ + { 0x00000433, 0x0100 }, /* R1075 - Noise Gate Select 5L */ + { 0x00000435, 0x0180 }, /* R1077 - DAC Digital Volume 5R */ + { 0x00000436, 0x0080 }, /* R1078 - DAC Volume Limit 5R */ + { 0x00000437, 0x0200 }, /* R1079 - Noise Gate Select 5R */ + { 0x00000450, 0x0000 }, /* R1104 - DAC AEC Control 1 */ + { 0x00000458, 0x0000 }, /* R1112 - Noise Gate Control */ + { 0x00000490, 0x0069 }, /* R1168 - PDM SPK1 CTRL 1 */ + { 0x00000491, 0x0000 }, /* R1169 - PDM SPK1 CTRL 2 */ + { 0x00000500, 0x000C }, /* R1280 - AIF1 BCLK Ctrl */ + { 0x00000501, 0x0008 }, /* R1281 - AIF1 Tx Pin Ctrl */ + { 0x00000502, 0x0000 }, /* R1282 - AIF1 Rx Pin Ctrl */ + { 0x00000503, 0x0000 }, /* R1283 - AIF1 Rate Ctrl */ + { 0x00000504, 0x0000 }, /* R1284 - AIF1 Format */ + { 0x00000505, 0x0040 }, /* R1285 - AIF1 Tx BCLK Rate */ + { 0x00000506, 0x0040 }, /* R1286 - AIF1 Rx BCLK Rate */ + { 0x00000507, 0x1818 }, /* R1287 - AIF1 Frame Ctrl 1 */ + { 0x00000508, 0x1818 }, /* R1288 - AIF1 Frame Ctrl 2 */ + { 0x00000509, 0x0000 }, /* R1289 - AIF1 Frame Ctrl 3 */ + { 0x0000050A, 0x0001 }, /* R1290 - AIF1 Frame Ctrl 4 */ + { 0x0000050B, 0x0002 }, /* R1291 - AIF1 Frame Ctrl 5 */ + { 0x0000050C, 0x0003 }, /* R1292 - AIF1 Frame Ctrl 6 */ + { 0x0000050D, 0x0004 }, /* R1293 - AIF1 Frame Ctrl 7 */ + { 0x0000050E, 0x0005 }, /* R1294 - AIF1 Frame Ctrl 8 */ + { 0x0000050F, 0x0006 }, /* R1295 - AIF1 Frame Ctrl 9 */ + { 0x00000510, 0x0007 }, /* R1296 - AIF1 Frame Ctrl 10 */ + { 0x00000511, 0x0000 }, /* R1297 - AIF1 Frame Ctrl 11 */ + { 0x00000512, 0x0001 }, /* R1298 - AIF1 Frame Ctrl 12 */ + { 0x00000513, 0x0002 }, /* R1299 - AIF1 Frame Ctrl 13 */ + { 0x00000514, 0x0003 }, /* R1300 - AIF1 Frame Ctrl 14 */ + { 0x00000515, 0x0004 }, /* R1301 - AIF1 Frame Ctrl 15 */ + { 0x00000516, 0x0005 }, /* R1302 - AIF1 Frame Ctrl 16 */ + { 0x00000517, 0x0006 }, /* R1303 - AIF1 Frame Ctrl 17 */ + { 0x00000518, 0x0007 }, /* R1304 - AIF1 Frame Ctrl 18 */ + { 0x00000519, 0x0000 }, /* R1305 - AIF1 Tx Enables */ + { 0x0000051A, 0x0000 }, /* R1306 - AIF1 Rx Enables */ + { 0x00000540, 0x000C }, /* R1344 - AIF2 BCLK Ctrl */ + { 0x00000541, 0x0008 }, /* R1345 - AIF2 Tx Pin Ctrl */ + { 0x00000542, 0x0000 }, /* R1346 - AIF2 Rx Pin Ctrl */ + { 0x00000543, 0x0000 }, /* R1347 - AIF2 Rate Ctrl */ + { 0x00000544, 0x0000 }, /* R1348 - AIF2 Format */ + { 0x00000545, 0x0040 }, /* R1349 - AIF2 Tx BCLK Rate */ + { 0x00000546, 0x0040 }, /* R1350 - AIF2 Rx BCLK Rate */ + { 0x00000547, 0x1818 }, /* R1351 - AIF2 Frame Ctrl 1 */ + { 0x00000548, 0x1818 }, /* R1352 - AIF2 Frame Ctrl 2 */ + { 0x00000549, 0x0000 }, /* R1353 - AIF2 Frame Ctrl 3 */ + { 0x0000054A, 0x0001 }, /* R1354 - AIF2 Frame Ctrl 4 */ + { 0x00000551, 0x0000 }, /* R1361 - AIF2 Frame Ctrl 11 */ + { 0x00000552, 0x0001 }, /* R1362 - AIF2 Frame Ctrl 12 */ + { 0x00000559, 0x0000 }, /* R1369 - AIF2 Tx Enables */ + { 0x0000055A, 0x0000 }, /* R1370 - AIF2 Rx Enables */ + { 0x000005E3, 0x0004 }, /* R1507 - SLIMbus Framer Ref Gear */ + { 0x000005E5, 0x0000 }, /* R1509 - SLIMbus Rates 1 */ + { 0x000005E6, 0x0000 }, /* R1510 - SLIMbus Rates 2 */ + { 0x000005E7, 0x0000 }, /* R1511 - SLIMbus Rates 3 */ + { 0x000005E8, 0x0000 }, /* R1512 - SLIMbus Rates 4 */ + { 0x000005E9, 0x0000 }, /* R1513 - SLIMbus Rates 5 */ + { 0x000005EA, 0x0000 }, /* R1514 - SLIMbus Rates 6 */ + { 0x000005EB, 0x0000 }, /* R1515 - SLIMbus Rates 7 */ + { 0x000005EC, 0x0000 }, /* R1516 - SLIMbus Rates 8 */ + { 0x000005F5, 0x0000 }, /* R1525 - SLIMbus RX Channel Enable */ + { 0x000005F6, 0x0000 }, /* R1526 - SLIMbus TX Channel Enable */ + { 0x00000640, 0x0000 }, /* R1600 - PWM1MIX Input 1 Source */ + { 0x00000641, 0x0080 }, /* R1601 - PWM1MIX Input 1 Volume */ + { 0x00000642, 0x0000 }, /* R1602 - PWM1MIX Input 2 Source */ + { 0x00000643, 0x0080 }, /* R1603 - PWM1MIX Input 2 Volume */ + { 0x00000644, 0x0000 }, /* R1604 - PWM1MIX Input 3 Source */ + { 0x00000645, 0x0080 }, /* R1605 - PWM1MIX Input 3 Volume */ + { 0x00000646, 0x0000 }, /* R1606 - PWM1MIX Input 4 Source */ + { 0x00000647, 0x0080 }, /* R1607 - PWM1MIX Input 4 Volume */ + { 0x00000648, 0x0000 }, /* R1608 - PWM2MIX Input 1 Source */ + { 0x00000649, 0x0080 }, /* R1609 - PWM2MIX Input 1 Volume */ + { 0x0000064A, 0x0000 }, /* R1610 - PWM2MIX Input 2 Source */ + { 0x0000064B, 0x0080 }, /* R1611 - PWM2MIX Input 2 Volume */ + { 0x0000064C, 0x0000 }, /* R1612 - PWM2MIX Input 3 Source */ + { 0x0000064D, 0x0080 }, /* R1613 - PWM2MIX Input 3 Volume */ + { 0x0000064E, 0x0000 }, /* R1614 - PWM2MIX Input 4 Source */ + { 0x0000064F, 0x0080 }, /* R1615 - PWM2MIX Input 4 Volume */ + { 0x00000660, 0x0000 }, /* R1632 - MICMIX Input 1 Source */ + { 0x00000661, 0x0080 }, /* R1633 - MICMIX Input 1 Volume */ + { 0x00000662, 0x0000 }, /* R1634 - MICMIX Input 2 Source */ + { 0x00000663, 0x0080 }, /* R1635 - MICMIX Input 2 Volume */ + { 0x00000664, 0x0000 }, /* R1636 - MICMIX Input 3 Source */ + { 0x00000665, 0x0080 }, /* R1637 - MICMIX Input 3 Volume */ + { 0x00000666, 0x0000 }, /* R1638 - MICMIX Input 4 Source */ + { 0x00000667, 0x0080 }, /* R1639 - MICMIX Input 4 Volume */ + { 0x00000668, 0x0000 }, /* R1640 - NOISEMIX Input 1 Source */ + { 0x00000669, 0x0080 }, /* R1641 - NOISEMIX Input 1 Volume */ + { 0x0000066A, 0x0000 }, /* R1642 - NOISEMIX Input 2 Source */ + { 0x0000066B, 0x0080 }, /* R1643 - NOISEMIX Input 2 Volume */ + { 0x0000066C, 0x0000 }, /* R1644 - NOISEMIX Input 3 Source */ + { 0x0000066D, 0x0080 }, /* R1645 - NOISEMIX Input 3 Volume */ + { 0x0000066E, 0x0000 }, /* R1646 - NOISEMIX Input 4 Source */ + { 0x0000066F, 0x0080 }, /* R1647 - NOISEMIX Input 4 Volume */ + { 0x00000680, 0x0000 }, /* R1664 - OUT1LMIX Input 1 Source */ + { 0x00000681, 0x0080 }, /* R1665 - OUT1LMIX Input 1 Volume */ + { 0x00000682, 0x0000 }, /* R1666 - OUT1LMIX Input 2 Source */ + { 0x00000683, 0x0080 }, /* R1667 - OUT1LMIX Input 2 Volume */ + { 0x00000684, 0x0000 }, /* R1668 - OUT1LMIX Input 3 Source */ + { 0x00000685, 0x0080 }, /* R1669 - OUT1LMIX Input 3 Volume */ + { 0x00000686, 0x0000 }, /* R1670 - OUT1LMIX Input 4 Source */ + { 0x00000687, 0x0080 }, /* R1671 - OUT1LMIX Input 4 Volume */ + { 0x00000688, 0x0000 }, /* R1672 - OUT1RMIX Input 1 Source */ + { 0x00000689, 0x0080 }, /* R1673 - OUT1RMIX Input 1 Volume */ + { 0x0000068A, 0x0000 }, /* R1674 - OUT1RMIX Input 2 Source */ + { 0x0000068B, 0x0080 }, /* R1675 - OUT1RMIX Input 2 Volume */ + { 0x0000068C, 0x0000 }, /* R1676 - OUT1RMIX Input 3 Source */ + { 0x0000068D, 0x0080 }, /* R1677 - OUT1RMIX Input 3 Volume */ + { 0x0000068E, 0x0000 }, /* R1678 - OUT1RMIX Input 4 Source */ + { 0x0000068F, 0x0080 }, /* R1679 - OUT1RMIX Input 4 Volume */ + { 0x000006A0, 0x0000 }, /* R1696 - OUT3LMIX Input 1 Source */ + { 0x000006A1, 0x0080 }, /* R1697 - OUT3LMIX Input 1 Volume */ + { 0x000006A2, 0x0000 }, /* R1698 - OUT3LMIX Input 2 Source */ + { 0x000006A3, 0x0080 }, /* R1699 - OUT3LMIX Input 2 Volume */ + { 0x000006A4, 0x0000 }, /* R1700 - OUT3LMIX Input 3 Source */ + { 0x000006A5, 0x0080 }, /* R1701 - OUT3LMIX Input 3 Volume */ + { 0x000006A6, 0x0000 }, /* R1702 - OUT3LMIX Input 4 Source */ + { 0x000006A7, 0x0080 }, /* R1703 - OUT3LMIX Input 4 Volume */ + { 0x000006B0, 0x0000 }, /* R1712 - OUT4LMIX Input 1 Source */ + { 0x000006B1, 0x0080 }, /* R1713 - OUT4LMIX Input 1 Volume */ + { 0x000006B2, 0x0000 }, /* R1714 - OUT4LMIX Input 2 Source */ + { 0x000006B3, 0x0080 }, /* R1715 - OUT4LMIX Input 2 Volume */ + { 0x000006B4, 0x0000 }, /* R1716 - OUT4LMIX Input 3 Source */ + { 0x000006B5, 0x0080 }, /* R1717 - OUT4LMIX Input 3 Volume */ + { 0x000006B6, 0x0000 }, /* R1718 - OUT4LMIX Input 4 Source */ + { 0x000006B7, 0x0080 }, /* R1719 - OUT4LMIX Input 4 Volume */ + { 0x000006C0, 0x0000 }, /* R1728 - OUT5LMIX Input 1 Source */ + { 0x000006C1, 0x0080 }, /* R1729 - OUT5LMIX Input 1 Volume */ + { 0x000006C2, 0x0000 }, /* R1730 - OUT5LMIX Input 2 Source */ + { 0x000006C3, 0x0080 }, /* R1731 - OUT5LMIX Input 2 Volume */ + { 0x000006C4, 0x0000 }, /* R1732 - OUT5LMIX Input 3 Source */ + { 0x000006C5, 0x0080 }, /* R1733 - OUT5LMIX Input 3 Volume */ + { 0x000006C6, 0x0000 }, /* R1734 - OUT5LMIX Input 4 Source */ + { 0x000006C7, 0x0080 }, /* R1735 - OUT5LMIX Input 4 Volume */ + { 0x000006C8, 0x0000 }, /* R1736 - OUT5RMIX Input 1 Source */ + { 0x000006C9, 0x0080 }, /* R1737 - OUT5RMIX Input 1 Volume */ + { 0x000006CA, 0x0000 }, /* R1738 - OUT5RMIX Input 2 Source */ + { 0x000006CB, 0x0080 }, /* R1739 - OUT5RMIX Input 2 Volume */ + { 0x000006CC, 0x0000 }, /* R1740 - OUT5RMIX Input 3 Source */ + { 0x000006CD, 0x0080 }, /* R1741 - OUT5RMIX Input 3 Volume */ + { 0x000006CE, 0x0000 }, /* R1742 - OUT5RMIX Input 4 Source */ + { 0x000006CF, 0x0080 }, /* R1743 - OUT5RMIX Input 4 Volume */ + { 0x00000700, 0x0000 }, /* R1792 - AIF1TX1MIX Input 1 Source */ + { 0x00000701, 0x0080 }, /* R1793 - AIF1TX1MIX Input 1 Volume */ + { 0x00000702, 0x0000 }, /* R1794 - AIF1TX1MIX Input 2 Source */ + { 0x00000703, 0x0080 }, /* R1795 - AIF1TX1MIX Input 2 Volume */ + { 0x00000704, 0x0000 }, /* R1796 - AIF1TX1MIX Input 3 Source */ + { 0x00000705, 0x0080 }, /* R1797 - AIF1TX1MIX Input 3 Volume */ + { 0x00000706, 0x0000 }, /* R1798 - AIF1TX1MIX Input 4 Source */ + { 0x00000707, 0x0080 }, /* R1799 - AIF1TX1MIX Input 4 Volume */ + { 0x00000708, 0x0000 }, /* R1800 - AIF1TX2MIX Input 1 Source */ + { 0x00000709, 0x0080 }, /* R1801 - AIF1TX2MIX Input 1 Volume */ + { 0x0000070A, 0x0000 }, /* R1802 - AIF1TX2MIX Input 2 Source */ + { 0x0000070B, 0x0080 }, /* R1803 - AIF1TX2MIX Input 2 Volume */ + { 0x0000070C, 0x0000 }, /* R1804 - AIF1TX2MIX Input 3 Source */ + { 0x0000070D, 0x0080 }, /* R1805 - AIF1TX2MIX Input 3 Volume */ + { 0x0000070E, 0x0000 }, /* R1806 - AIF1TX2MIX Input 4 Source */ + { 0x0000070F, 0x0080 }, /* R1807 - AIF1TX2MIX Input 4 Volume */ + { 0x00000710, 0x0000 }, /* R1808 - AIF1TX3MIX Input 1 Source */ + { 0x00000711, 0x0080 }, /* R1809 - AIF1TX3MIX Input 1 Volume */ + { 0x00000712, 0x0000 }, /* R1810 - AIF1TX3MIX Input 2 Source */ + { 0x00000713, 0x0080 }, /* R1811 - AIF1TX3MIX Input 2 Volume */ + { 0x00000714, 0x0000 }, /* R1812 - AIF1TX3MIX Input 3 Source */ + { 0x00000715, 0x0080 }, /* R1813 - AIF1TX3MIX Input 3 Volume */ + { 0x00000716, 0x0000 }, /* R1814 - AIF1TX3MIX Input 4 Source */ + { 0x00000717, 0x0080 }, /* R1815 - AIF1TX3MIX Input 4 Volume */ + { 0x00000718, 0x0000 }, /* R1816 - AIF1TX4MIX Input 1 Source */ + { 0x00000719, 0x0080 }, /* R1817 - AIF1TX4MIX Input 1 Volume */ + { 0x0000071A, 0x0000 }, /* R1818 - AIF1TX4MIX Input 2 Source */ + { 0x0000071B, 0x0080 }, /* R1819 - AIF1TX4MIX Input 2 Volume */ + { 0x0000071C, 0x0000 }, /* R1820 - AIF1TX4MIX Input 3 Source */ + { 0x0000071D, 0x0080 }, /* R1821 - AIF1TX4MIX Input 3 Volume */ + { 0x0000071E, 0x0000 }, /* R1822 - AIF1TX4MIX Input 4 Source */ + { 0x0000071F, 0x0080 }, /* R1823 - AIF1TX4MIX Input 4 Volume */ + { 0x00000720, 0x0000 }, /* R1824 - AIF1TX5MIX Input 1 Source */ + { 0x00000721, 0x0080 }, /* R1825 - AIF1TX5MIX Input 1 Volume */ + { 0x00000722, 0x0000 }, /* R1826 - AIF1TX5MIX Input 2 Source */ + { 0x00000723, 0x0080 }, /* R1827 - AIF1TX5MIX Input 2 Volume */ + { 0x00000724, 0x0000 }, /* R1828 - AIF1TX5MIX Input 3 Source */ + { 0x00000725, 0x0080 }, /* R1829 - AIF1TX5MIX Input 3 Volume */ + { 0x00000726, 0x0000 }, /* R1830 - AIF1TX5MIX Input 4 Source */ + { 0x00000727, 0x0080 }, /* R1831 - AIF1TX5MIX Input 4 Volume */ + { 0x00000728, 0x0000 }, /* R1832 - AIF1TX6MIX Input 1 Source */ + { 0x00000729, 0x0080 }, /* R1833 - AIF1TX6MIX Input 1 Volume */ + { 0x0000072A, 0x0000 }, /* R1834 - AIF1TX6MIX Input 2 Source */ + { 0x0000072B, 0x0080 }, /* R1835 - AIF1TX6MIX Input 2 Volume */ + { 0x0000072C, 0x0000 }, /* R1836 - AIF1TX6MIX Input 3 Source */ + { 0x0000072D, 0x0080 }, /* R1837 - AIF1TX6MIX Input 3 Volume */ + { 0x0000072E, 0x0000 }, /* R1838 - AIF1TX6MIX Input 4 Source */ + { 0x0000072F, 0x0080 }, /* R1839 - AIF1TX6MIX Input 4 Volume */ + { 0x00000730, 0x0000 }, /* R1840 - AIF1TX7MIX Input 1 Source */ + { 0x00000731, 0x0080 }, /* R1841 - AIF1TX7MIX Input 1 Volume */ + { 0x00000732, 0x0000 }, /* R1842 - AIF1TX7MIX Input 2 Source */ + { 0x00000733, 0x0080 }, /* R1843 - AIF1TX7MIX Input 2 Volume */ + { 0x00000734, 0x0000 }, /* R1844 - AIF1TX7MIX Input 3 Source */ + { 0x00000735, 0x0080 }, /* R1845 - AIF1TX7MIX Input 3 Volume */ + { 0x00000736, 0x0000 }, /* R1846 - AIF1TX7MIX Input 4 Source */ + { 0x00000737, 0x0080 }, /* R1847 - AIF1TX7MIX Input 4 Volume */ + { 0x00000738, 0x0000 }, /* R1848 - AIF1TX8MIX Input 1 Source */ + { 0x00000739, 0x0080 }, /* R1849 - AIF1TX8MIX Input 1 Volume */ + { 0x0000073A, 0x0000 }, /* R1850 - AIF1TX8MIX Input 2 Source */ + { 0x0000073B, 0x0080 }, /* R1851 - AIF1TX8MIX Input 2 Volume */ + { 0x0000073C, 0x0000 }, /* R1852 - AIF1TX8MIX Input 3 Source */ + { 0x0000073D, 0x0080 }, /* R1853 - AIF1TX8MIX Input 3 Volume */ + { 0x0000073E, 0x0000 }, /* R1854 - AIF1TX8MIX Input 4 Source */ + { 0x0000073F, 0x0080 }, /* R1855 - AIF1TX8MIX Input 4 Volume */ + { 0x00000740, 0x0000 }, /* R1856 - AIF2TX1MIX Input 1 Source */ + { 0x00000741, 0x0080 }, /* R1857 - AIF2TX1MIX Input 1 Volume */ + { 0x00000742, 0x0000 }, /* R1858 - AIF2TX1MIX Input 2 Source */ + { 0x00000743, 0x0080 }, /* R1859 - AIF2TX1MIX Input 2 Volume */ + { 0x00000744, 0x0000 }, /* R1860 - AIF2TX1MIX Input 3 Source */ + { 0x00000745, 0x0080 }, /* R1861 - AIF2TX1MIX Input 3 Volume */ + { 0x00000746, 0x0000 }, /* R1862 - AIF2TX1MIX Input 4 Source */ + { 0x00000747, 0x0080 }, /* R1863 - AIF2TX1MIX Input 4 Volume */ + { 0x00000748, 0x0000 }, /* R1864 - AIF2TX2MIX Input 1 Source */ + { 0x00000749, 0x0080 }, /* R1865 - AIF2TX2MIX Input 1 Volume */ + { 0x0000074A, 0x0000 }, /* R1866 - AIF2TX2MIX Input 2 Source */ + { 0x0000074B, 0x0080 }, /* R1867 - AIF2TX2MIX Input 2 Volume */ + { 0x0000074C, 0x0000 }, /* R1868 - AIF2TX2MIX Input 3 Source */ + { 0x0000074D, 0x0080 }, /* R1869 - AIF2TX2MIX Input 3 Volume */ + { 0x0000074E, 0x0000 }, /* R1870 - AIF2TX2MIX Input 4 Source */ + { 0x0000074F, 0x0080 }, /* R1871 - AIF2TX2MIX Input 4 Volume */ + { 0x000007C0, 0x0000 }, /* R1984 - SLIMTX1MIX Input 1 Source */ + { 0x000007C1, 0x0080 }, /* R1985 - SLIMTX1MIX Input 1 Volume */ + { 0x000007C2, 0x0000 }, /* R1986 - SLIMTX1MIX Input 2 Source */ + { 0x000007C3, 0x0080 }, /* R1987 - SLIMTX1MIX Input 2 Volume */ + { 0x000007C4, 0x0000 }, /* R1988 - SLIMTX1MIX Input 3 Source */ + { 0x000007C5, 0x0080 }, /* R1989 - SLIMTX1MIX Input 3 Volume */ + { 0x000007C6, 0x0000 }, /* R1990 - SLIMTX1MIX Input 4 Source */ + { 0x000007C7, 0x0080 }, /* R1991 - SLIMTX1MIX Input 4 Volume */ + { 0x000007C8, 0x0000 }, /* R1992 - SLIMTX2MIX Input 1 Source */ + { 0x000007C9, 0x0080 }, /* R1993 - SLIMTX2MIX Input 1 Volume */ + { 0x000007CA, 0x0000 }, /* R1994 - SLIMTX2MIX Input 2 Source */ + { 0x000007CB, 0x0080 }, /* R1995 - SLIMTX2MIX Input 2 Volume */ + { 0x000007CC, 0x0000 }, /* R1996 - SLIMTX2MIX Input 3 Source */ + { 0x000007CD, 0x0080 }, /* R1997 - SLIMTX2MIX Input 3 Volume */ + { 0x000007CE, 0x0000 }, /* R1998 - SLIMTX2MIX Input 4 Source */ + { 0x000007CF, 0x0080 }, /* R1999 - SLIMTX2MIX Input 4 Volume */ + { 0x000007D0, 0x0000 }, /* R2000 - SLIMTX3MIX Input 1 Source */ + { 0x000007D1, 0x0080 }, /* R2001 - SLIMTX3MIX Input 1 Volume */ + { 0x000007D2, 0x0000 }, /* R2002 - SLIMTX3MIX Input 2 Source */ + { 0x000007D3, 0x0080 }, /* R2003 - SLIMTX3MIX Input 2 Volume */ + { 0x000007D4, 0x0000 }, /* R2004 - SLIMTX3MIX Input 3 Source */ + { 0x000007D5, 0x0080 }, /* R2005 - SLIMTX3MIX Input 3 Volume */ + { 0x000007D6, 0x0000 }, /* R2006 - SLIMTX3MIX Input 4 Source */ + { 0x000007D7, 0x0080 }, /* R2007 - SLIMTX3MIX Input 4 Volume */ + { 0x000007D8, 0x0000 }, /* R2008 - SLIMTX4MIX Input 1 Source */ + { 0x000007D9, 0x0080 }, /* R2009 - SLIMTX4MIX Input 1 Volume */ + { 0x000007DA, 0x0000 }, /* R2010 - SLIMTX4MIX Input 2 Source */ + { 0x000007DB, 0x0080 }, /* R2011 - SLIMTX4MIX Input 2 Volume */ + { 0x000007DC, 0x0000 }, /* R2012 - SLIMTX4MIX Input 3 Source */ + { 0x000007DD, 0x0080 }, /* R2013 - SLIMTX4MIX Input 3 Volume */ + { 0x000007DE, 0x0000 }, /* R2014 - SLIMTX4MIX Input 4 Source */ + { 0x000007DF, 0x0080 }, /* R2015 - SLIMTX4MIX Input 4 Volume */ + { 0x000007E0, 0x0000 }, /* R2016 - SLIMTX5MIX Input 1 Source */ + { 0x000007E1, 0x0080 }, /* R2017 - SLIMTX5MIX Input 1 Volume */ + { 0x000007E2, 0x0000 }, /* R2018 - SLIMTX5MIX Input 2 Source */ + { 0x000007E3, 0x0080 }, /* R2019 - SLIMTX5MIX Input 2 Volume */ + { 0x000007E4, 0x0000 }, /* R2020 - SLIMTX5MIX Input 3 Source */ + { 0x000007E5, 0x0080 }, /* R2021 - SLIMTX5MIX Input 3 Volume */ + { 0x000007E6, 0x0000 }, /* R2022 - SLIMTX5MIX Input 4 Source */ + { 0x000007E7, 0x0080 }, /* R2023 - SLIMTX5MIX Input 4 Volume */ + { 0x000007E8, 0x0000 }, /* R2024 - SLIMTX6MIX Input 1 Source */ + { 0x000007E9, 0x0080 }, /* R2025 - SLIMTX6MIX Input 1 Volume */ + { 0x000007EA, 0x0000 }, /* R2026 - SLIMTX6MIX Input 2 Source */ + { 0x000007EB, 0x0080 }, /* R2027 - SLIMTX6MIX Input 2 Volume */ + { 0x000007EC, 0x0000 }, /* R2028 - SLIMTX6MIX Input 3 Source */ + { 0x000007ED, 0x0080 }, /* R2029 - SLIMTX6MIX Input 3 Volume */ + { 0x000007EE, 0x0000 }, /* R2030 - SLIMTX6MIX Input 4 Source */ + { 0x000007EF, 0x0080 }, /* R2031 - SLIMTX6MIX Input 4 Volume */ + { 0x000007F0, 0x0000 }, /* R2032 - SLIMTX7MIX Input 1 Source */ + { 0x000007F1, 0x0080 }, /* R2033 - SLIMTX7MIX Input 1 Volume */ + { 0x000007F2, 0x0000 }, /* R2034 - SLIMTX7MIX Input 2 Source */ + { 0x000007F3, 0x0080 }, /* R2035 - SLIMTX7MIX Input 2 Volume */ + { 0x000007F4, 0x0000 }, /* R2036 - SLIMTX7MIX Input 3 Source */ + { 0x000007F5, 0x0080 }, /* R2037 - SLIMTX7MIX Input 3 Volume */ + { 0x000007F6, 0x0000 }, /* R2038 - SLIMTX7MIX Input 4 Source */ + { 0x000007F7, 0x0080 }, /* R2039 - SLIMTX7MIX Input 4 Volume */ + { 0x000007F8, 0x0000 }, /* R2040 - SLIMTX8MIX Input 1 Source */ + { 0x000007F9, 0x0080 }, /* R2041 - SLIMTX8MIX Input 1 Volume */ + { 0x000007FA, 0x0000 }, /* R2042 - SLIMTX8MIX Input 2 Source */ + { 0x000007FB, 0x0080 }, /* R2043 - SLIMTX8MIX Input 2 Volume */ + { 0x000007FC, 0x0000 }, /* R2044 - SLIMTX8MIX Input 3 Source */ + { 0x000007FD, 0x0080 }, /* R2045 - SLIMTX8MIX Input 3 Volume */ + { 0x000007FE, 0x0000 }, /* R2046 - SLIMTX8MIX Input 4 Source */ + { 0x000007FF, 0x0080 }, /* R2047 - SLIMTX8MIX Input 4 Volume */ + { 0x00000880, 0x0000 }, /* R2176 - EQ1MIX Input 1 Source */ + { 0x00000881, 0x0080 }, /* R2177 - EQ1MIX Input 1 Volume */ + { 0x00000882, 0x0000 }, /* R2178 - EQ1MIX Input 2 Source */ + { 0x00000883, 0x0080 }, /* R2179 - EQ1MIX Input 2 Volume */ + { 0x00000884, 0x0000 }, /* R2180 - EQ1MIX Input 3 Source */ + { 0x00000885, 0x0080 }, /* R2181 - EQ1MIX Input 3 Volume */ + { 0x00000886, 0x0000 }, /* R2182 - EQ1MIX Input 4 Source */ + { 0x00000887, 0x0080 }, /* R2183 - EQ1MIX Input 4 Volume */ + { 0x00000888, 0x0000 }, /* R2184 - EQ2MIX Input 1 Source */ + { 0x00000889, 0x0080 }, /* R2185 - EQ2MIX Input 1 Volume */ + { 0x0000088A, 0x0000 }, /* R2186 - EQ2MIX Input 2 Source */ + { 0x0000088B, 0x0080 }, /* R2187 - EQ2MIX Input 2 Volume */ + { 0x0000088C, 0x0000 }, /* R2188 - EQ2MIX Input 3 Source */ + { 0x0000088D, 0x0080 }, /* R2189 - EQ2MIX Input 3 Volume */ + { 0x0000088E, 0x0000 }, /* R2190 - EQ2MIX Input 4 Source */ + { 0x0000088F, 0x0080 }, /* R2191 - EQ2MIX Input 4 Volume */ + { 0x00000890, 0x0000 }, /* R2192 - EQ3MIX Input 1 Source */ + { 0x00000891, 0x0080 }, /* R2193 - EQ3MIX Input 1 Volume */ + { 0x00000892, 0x0000 }, /* R2194 - EQ3MIX Input 2 Source */ + { 0x00000893, 0x0080 }, /* R2195 - EQ3MIX Input 2 Volume */ + { 0x00000894, 0x0000 }, /* R2196 - EQ3MIX Input 3 Source */ + { 0x00000895, 0x0080 }, /* R2197 - EQ3MIX Input 3 Volume */ + { 0x00000896, 0x0000 }, /* R2198 - EQ3MIX Input 4 Source */ + { 0x00000897, 0x0080 }, /* R2199 - EQ3MIX Input 4 Volume */ + { 0x00000898, 0x0000 }, /* R2200 - EQ4MIX Input 1 Source */ + { 0x00000899, 0x0080 }, /* R2201 - EQ4MIX Input 1 Volume */ + { 0x0000089A, 0x0000 }, /* R2202 - EQ4MIX Input 2 Source */ + { 0x0000089B, 0x0080 }, /* R2203 - EQ4MIX Input 2 Volume */ + { 0x0000089C, 0x0000 }, /* R2204 - EQ4MIX Input 3 Source */ + { 0x0000089D, 0x0080 }, /* R2205 - EQ4MIX Input 3 Volume */ + { 0x0000089E, 0x0000 }, /* R2206 - EQ4MIX Input 4 Source */ + { 0x0000089F, 0x0080 }, /* R2207 - EQ4MIX Input 4 Volume */ + { 0x000008C0, 0x0000 }, /* R2240 - DRC1LMIX Input 1 Source */ + { 0x000008C1, 0x0080 }, /* R2241 - DRC1LMIX Input 1 Volume */ + { 0x000008C2, 0x0000 }, /* R2242 - DRC1LMIX Input 2 Source */ + { 0x000008C3, 0x0080 }, /* R2243 - DRC1LMIX Input 2 Volume */ + { 0x000008C4, 0x0000 }, /* R2244 - DRC1LMIX Input 3 Source */ + { 0x000008C5, 0x0080 }, /* R2245 - DRC1LMIX Input 3 Volume */ + { 0x000008C6, 0x0000 }, /* R2246 - DRC1LMIX Input 4 Source */ + { 0x000008C7, 0x0080 }, /* R2247 - DRC1LMIX Input 4 Volume */ + { 0x000008C8, 0x0000 }, /* R2248 - DRC1RMIX Input 1 Source */ + { 0x000008C9, 0x0080 }, /* R2249 - DRC1RMIX Input 1 Volume */ + { 0x000008CA, 0x0000 }, /* R2250 - DRC1RMIX Input 2 Source */ + { 0x000008CB, 0x0080 }, /* R2251 - DRC1RMIX Input 2 Volume */ + { 0x000008CC, 0x0000 }, /* R2252 - DRC1RMIX Input 3 Source */ + { 0x000008CD, 0x0080 }, /* R2253 - DRC1RMIX Input 3 Volume */ + { 0x000008CE, 0x0000 }, /* R2254 - DRC1RMIX Input 4 Source */ + { 0x000008CF, 0x0080 }, /* R2255 - DRC1RMIX Input 4 Volume */ + { 0x00000900, 0x0000 }, /* R2304 - HPLP1MIX Input 1 Source */ + { 0x00000901, 0x0080 }, /* R2305 - HPLP1MIX Input 1 Volume */ + { 0x00000902, 0x0000 }, /* R2306 - HPLP1MIX Input 2 Source */ + { 0x00000903, 0x0080 }, /* R2307 - HPLP1MIX Input 2 Volume */ + { 0x00000904, 0x0000 }, /* R2308 - HPLP1MIX Input 3 Source */ + { 0x00000905, 0x0080 }, /* R2309 - HPLP1MIX Input 3 Volume */ + { 0x00000906, 0x0000 }, /* R2310 - HPLP1MIX Input 4 Source */ + { 0x00000907, 0x0080 }, /* R2311 - HPLP1MIX Input 4 Volume */ + { 0x00000908, 0x0000 }, /* R2312 - HPLP2MIX Input 1 Source */ + { 0x00000909, 0x0080 }, /* R2313 - HPLP2MIX Input 1 Volume */ + { 0x0000090A, 0x0000 }, /* R2314 - HPLP2MIX Input 2 Source */ + { 0x0000090B, 0x0080 }, /* R2315 - HPLP2MIX Input 2 Volume */ + { 0x0000090C, 0x0000 }, /* R2316 - HPLP2MIX Input 3 Source */ + { 0x0000090D, 0x0080 }, /* R2317 - HPLP2MIX Input 3 Volume */ + { 0x0000090E, 0x0000 }, /* R2318 - HPLP2MIX Input 4 Source */ + { 0x0000090F, 0x0080 }, /* R2319 - HPLP2MIX Input 4 Volume */ + { 0x00000910, 0x0000 }, /* R2320 - HPLP3MIX Input 1 Source */ + { 0x00000911, 0x0080 }, /* R2321 - HPLP3MIX Input 1 Volume */ + { 0x00000912, 0x0000 }, /* R2322 - HPLP3MIX Input 2 Source */ + { 0x00000913, 0x0080 }, /* R2323 - HPLP3MIX Input 2 Volume */ + { 0x00000914, 0x0000 }, /* R2324 - HPLP3MIX Input 3 Source */ + { 0x00000915, 0x0080 }, /* R2325 - HPLP3MIX Input 3 Volume */ + { 0x00000916, 0x0000 }, /* R2326 - HPLP3MIX Input 4 Source */ + { 0x00000917, 0x0080 }, /* R2327 - HPLP3MIX Input 4 Volume */ + { 0x00000918, 0x0000 }, /* R2328 - HPLP4MIX Input 1 Source */ + { 0x00000919, 0x0080 }, /* R2329 - HPLP4MIX Input 1 Volume */ + { 0x0000091A, 0x0000 }, /* R2330 - HPLP4MIX Input 2 Source */ + { 0x0000091B, 0x0080 }, /* R2331 - HPLP4MIX Input 2 Volume */ + { 0x0000091C, 0x0000 }, /* R2332 - HPLP4MIX Input 3 Source */ + { 0x0000091D, 0x0080 }, /* R2333 - HPLP4MIX Input 3 Volume */ + { 0x0000091E, 0x0000 }, /* R2334 - HPLP4MIX Input 4 Source */ + { 0x0000091F, 0x0080 }, /* R2335 - HPLP4MIX Input 4 Volume */ + { 0x00000B00, 0x0000 }, /* R2816 - ISRC1DEC1MIX Input 1 Source */ + { 0x00000B08, 0x0000 }, /* R2824 - ISRC1DEC2MIX Input 1 Source */ + { 0x00000B20, 0x0000 }, /* R2848 - ISRC1INT1MIX Input 1 Source */ + { 0x00000B28, 0x0000 }, /* R2856 - ISRC1INT2MIX Input 1 Source */ + { 0x00000B40, 0x0000 }, /* R2880 - ISRC2DEC1MIX Input 1 Source */ + { 0x00000B48, 0x0000 }, /* R2888 - ISRC2DEC2MIX Input 1 Source */ + { 0x00000B60, 0x0000 }, /* R2912 - ISRC2INT1MIX Input 1 Source */ + { 0x00000B68, 0x0000 }, /* R2920 - ISRC2INT2MIX Input 1 Source */ + { 0x00000C00, 0xA101 }, /* R3072 - GPIO1 CTRL */ + { 0x00000C01, 0xA101 }, /* R3073 - GPIO2 CTRL */ + { 0x00000C02, 0xA101 }, /* R3074 - GPIO3 CTRL */ + { 0x00000C03, 0xA101 }, /* R3075 - GPIO4 CTRL */ + { 0x00000C04, 0xA101 }, /* R3076 - GPIO5 CTRL */ + { 0x00000C0F, 0x0400 }, /* R3087 - IRQ CTRL 1 */ + { 0x00000C10, 0x1000 }, /* R3088 - GPIO Debounce Config */ + { 0x00000C20, 0x8002 }, /* R3104 - Misc Pad Ctrl 1 */ + { 0x00000C21, 0x0001 }, /* R3105 - Misc Pad Ctrl 2 */ + { 0x00000C22, 0x0000 }, /* R3106 - Misc Pad Ctrl 3 */ + { 0x00000C23, 0x0000 }, /* R3107 - Misc Pad Ctrl 4 */ + { 0x00000C24, 0x0000 }, /* R3108 - Misc Pad Ctrl 5 */ + { 0x00000D08, 0xFFFF }, /* R3336 - Interrupt Status 1 Mask */ + { 0x00000D09, 0xFFFF }, /* R3337 - Interrupt Status 2 Mask */ + { 0x00000D0A, 0xFFFF }, /* R3338 - Interrupt Status 3 Mask */ + { 0x00000D0B, 0xFFFF }, /* R3339 - Interrupt Status 4 Mask */ + { 0x00000D0C, 0xFEFF }, /* R3340 - Interrupt Status 5 Mask */ + { 0x00000D0F, 0x0000 }, /* R3343 - Interrupt Control */ + { 0x00000D18, 0xFFFF }, /* R3352 - IRQ2 Status 1 Mask */ + { 0x00000D1A, 0xFFFF }, /* R3354 - IRQ2 Status 3 Mask */ + { 0x00000D1B, 0xFFFF }, /* R3355 - IRQ2 Status 4 Mask */ + { 0x00000D1C, 0xFFFF }, /* R3356 - IRQ2 Status 5 Mask */ + { 0x00000D1F, 0x0000 }, /* R3359 - IRQ2 Control */ + { 0x00000D53, 0xFFFF }, /* R3411 - AOD IRQ Mask IRQ1 */ + { 0x00000D54, 0xFFFF }, /* R3412 - AOD IRQ Mask IRQ2 */ + { 0x00000D56, 0x0000 }, /* R3414 - Jack detect debounce */ + { 0x00000E00, 0x0000 }, /* R3584 - FX_Ctrl1 */ + { 0x00000E01, 0x0000 }, /* R3585 - FX_Ctrl2 */ + { 0x00000E10, 0x6318 }, /* R3600 - EQ1_1 */ + { 0x00000E11, 0x6300 }, /* R3601 - EQ1_2 */ + { 0x00000E12, 0x0FC8 }, /* R3602 - EQ1_3 */ + { 0x00000E13, 0x03FE }, /* R3603 - EQ1_4 */ + { 0x00000E14, 0x00E0 }, /* R3604 - EQ1_5 */ + { 0x00000E15, 0x1EC4 }, /* R3605 - EQ1_6 */ + { 0x00000E16, 0xF136 }, /* R3606 - EQ1_7 */ + { 0x00000E17, 0x0409 }, /* R3607 - EQ1_8 */ + { 0x00000E18, 0x04CC }, /* R3608 - EQ1_9 */ + { 0x00000E19, 0x1C9B }, /* R3609 - EQ1_10 */ + { 0x00000E1A, 0xF337 }, /* R3610 - EQ1_11 */ + { 0x00000E1B, 0x040B }, /* R3611 - EQ1_12 */ + { 0x00000E1C, 0x0CBB }, /* R3612 - EQ1_13 */ + { 0x00000E1D, 0x16F8 }, /* R3613 - EQ1_14 */ + { 0x00000E1E, 0xF7D9 }, /* R3614 - EQ1_15 */ + { 0x00000E1F, 0x040A }, /* R3615 - EQ1_16 */ + { 0x00000E20, 0x1F14 }, /* R3616 - EQ1_17 */ + { 0x00000E21, 0x058C }, /* R3617 - EQ1_18 */ + { 0x00000E22, 0x0563 }, /* R3618 - EQ1_19 */ + { 0x00000E23, 0x4000 }, /* R3619 - EQ1_20 */ + { 0x00000E24, 0x0B75 }, /* R3620 - EQ1_21 */ + { 0x00000E26, 0x6318 }, /* R3622 - EQ2_1 */ + { 0x00000E27, 0x6300 }, /* R3623 - EQ2_2 */ + { 0x00000E28, 0x0FC8 }, /* R3624 - EQ2_3 */ + { 0x00000E29, 0x03FE }, /* R3625 - EQ2_4 */ + { 0x00000E2A, 0x00E0 }, /* R3626 - EQ2_5 */ + { 0x00000E2B, 0x1EC4 }, /* R3627 - EQ2_6 */ + { 0x00000E2C, 0xF136 }, /* R3628 - EQ2_7 */ + { 0x00000E2D, 0x0409 }, /* R3629 - EQ2_8 */ + { 0x00000E2E, 0x04CC }, /* R3630 - EQ2_9 */ + { 0x00000E2F, 0x1C9B }, /* R3631 - EQ2_10 */ + { 0x00000E30, 0xF337 }, /* R3632 - EQ2_11 */ + { 0x00000E31, 0x040B }, /* R3633 - EQ2_12 */ + { 0x00000E32, 0x0CBB }, /* R3634 - EQ2_13 */ + { 0x00000E33, 0x16F8 }, /* R3635 - EQ2_14 */ + { 0x00000E34, 0xF7D9 }, /* R3636 - EQ2_15 */ + { 0x00000E35, 0x040A }, /* R3637 - EQ2_16 */ + { 0x00000E36, 0x1F14 }, /* R3638 - EQ2_17 */ + { 0x00000E37, 0x058C }, /* R3639 - EQ2_18 */ + { 0x00000E38, 0x0563 }, /* R3640 - EQ2_19 */ + { 0x00000E39, 0x4000 }, /* R3641 - EQ2_20 */ + { 0x00000E3A, 0x0B75 }, /* R3642 - EQ2_21 */ + { 0x00000E3C, 0x6318 }, /* R3644 - EQ3_1 */ + { 0x00000E3D, 0x6300 }, /* R3645 - EQ3_2 */ + { 0x00000E3E, 0x0FC8 }, /* R3646 - EQ3_3 */ + { 0x00000E3F, 0x03FE }, /* R3647 - EQ3_4 */ + { 0x00000E40, 0x00E0 }, /* R3648 - EQ3_5 */ + { 0x00000E41, 0x1EC4 }, /* R3649 - EQ3_6 */ + { 0x00000E42, 0xF136 }, /* R3650 - EQ3_7 */ + { 0x00000E43, 0x0409 }, /* R3651 - EQ3_8 */ + { 0x00000E44, 0x04CC }, /* R3652 - EQ3_9 */ + { 0x00000E45, 0x1C9B }, /* R3653 - EQ3_10 */ + { 0x00000E46, 0xF337 }, /* R3654 - EQ3_11 */ + { 0x00000E47, 0x040B }, /* R3655 - EQ3_12 */ + { 0x00000E48, 0x0CBB }, /* R3656 - EQ3_13 */ + { 0x00000E49, 0x16F8 }, /* R3657 - EQ3_14 */ + { 0x00000E4A, 0xF7D9 }, /* R3658 - EQ3_15 */ + { 0x00000E4B, 0x040A }, /* R3659 - EQ3_16 */ + { 0x00000E4C, 0x1F14 }, /* R3660 - EQ3_17 */ + { 0x00000E4D, 0x058C }, /* R3661 - EQ3_18 */ + { 0x00000E4E, 0x0563 }, /* R3662 - EQ3_19 */ + { 0x00000E4F, 0x4000 }, /* R3663 - EQ3_20 */ + { 0x00000E50, 0x0B75 }, /* R3664 - EQ3_21 */ + { 0x00000E52, 0x6318 }, /* R3666 - EQ4_1 */ + { 0x00000E53, 0x6300 }, /* R3667 - EQ4_2 */ + { 0x00000E54, 0x0FC8 }, /* R3668 - EQ4_3 */ + { 0x00000E55, 0x03FE }, /* R3669 - EQ4_4 */ + { 0x00000E56, 0x00E0 }, /* R3670 - EQ4_5 */ + { 0x00000E57, 0x1EC4 }, /* R3671 - EQ4_6 */ + { 0x00000E58, 0xF136 }, /* R3672 - EQ4_7 */ + { 0x00000E59, 0x0409 }, /* R3673 - EQ4_8 */ + { 0x00000E5A, 0x04CC }, /* R3674 - EQ4_9 */ + { 0x00000E5B, 0x1C9B }, /* R3675 - EQ4_10 */ + { 0x00000E5C, 0xF337 }, /* R3676 - EQ4_11 */ + { 0x00000E5D, 0x040B }, /* R3677 - EQ4_12 */ + { 0x00000E5E, 0x0CBB }, /* R3678 - EQ4_13 */ + { 0x00000E5F, 0x16F8 }, /* R3679 - EQ4_14 */ + { 0x00000E60, 0xF7D9 }, /* R3680 - EQ4_15 */ + { 0x00000E61, 0x040A }, /* R3681 - EQ4_16 */ + { 0x00000E62, 0x1F14 }, /* R3682 - EQ4_17 */ + { 0x00000E63, 0x058C }, /* R3683 - EQ4_18 */ + { 0x00000E64, 0x0563 }, /* R3684 - EQ4_19 */ + { 0x00000E65, 0x4000 }, /* R3685 - EQ4_20 */ + { 0x00000E66, 0x0B75 }, /* R3686 - EQ4_21 */ + { 0x00000E80, 0x0018 }, /* R3712 - DRC1 ctrl1 */ + { 0x00000E81, 0x0933 }, /* R3713 - DRC1 ctrl2 */ + { 0x00000E82, 0x0018 }, /* R3714 - DRC1 ctrl3 */ + { 0x00000E83, 0x0000 }, /* R3715 - DRC1 ctrl4 */ + { 0x00000E84, 0x0000 }, /* R3716 - DRC1 ctrl5 */ + { 0x00000EC0, 0x0000 }, /* R3776 - HPLPF1_1 */ + { 0x00000EC1, 0x0000 }, /* R3777 - HPLPF1_2 */ + { 0x00000EC4, 0x0000 }, /* R3780 - HPLPF2_1 */ + { 0x00000EC5, 0x0000 }, /* R3781 - HPLPF2_2 */ + { 0x00000EC8, 0x0000 }, /* R3784 - HPLPF3_1 */ + { 0x00000EC9, 0x0000 }, /* R3785 - HPLPF3_2 */ + { 0x00000ECC, 0x0000 }, /* R3788 - HPLPF4_1 */ + { 0x00000ECD, 0x0000 }, /* R3789 - HPLPF4_2 */ + { 0x00000EF0, 0x0000 }, /* R3824 - ISRC 1 CTRL 1 */ + { 0x00000EF1, 0x0000 }, /* R3825 - ISRC 1 CTRL 2 */ + { 0x00000EF2, 0x0000 }, /* R3826 - ISRC 1 CTRL 3 */ + { 0x00000EF3, 0x0000 }, /* R3827 - ISRC 2 CTRL 1 */ + { 0x00000EF4, 0x0000 }, /* R3828 - ISRC 2 CTRL 2 */ + { 0x00000EF5, 0x0000 }, /* R3829 - ISRC 2 CTRL 3 */ + { 0x00001100, 0x0010 }, /* R4352 - DSP1 Control 1 */ + { 0x00001101, 0x0000 }, /* R4353 - DSP1 Clocking 1 */ +}; + +static bool wm8997_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ARIZONA_SOFTWARE_RESET: + case ARIZONA_DEVICE_REVISION: + case ARIZONA_CTRL_IF_I2C1_CFG_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_TONE_GENERATOR_1: + case ARIZONA_TONE_GENERATOR_2: + case ARIZONA_TONE_GENERATOR_3: + case ARIZONA_TONE_GENERATOR_4: + case ARIZONA_TONE_GENERATOR_5: + case ARIZONA_PWM_DRIVE_1: + case ARIZONA_PWM_DRIVE_2: + case ARIZONA_PWM_DRIVE_3: + case ARIZONA_WAKE_CONTROL: + case ARIZONA_SEQUENCE_CONTROL: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_1: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_2: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_3: + case ARIZONA_SAMPLE_RATE_SEQUENCE_SELECT_4: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_3: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_4: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_5: + case ARIZONA_ALWAYS_ON_TRIGGERS_SEQUENCE_SELECT_6: + case ARIZONA_COMFORT_NOISE_GENERATOR: + case ARIZONA_HAPTICS_CONTROL_1: + case ARIZONA_HAPTICS_CONTROL_2: + case ARIZONA_HAPTICS_PHASE_1_INTENSITY: + case ARIZONA_HAPTICS_PHASE_1_DURATION: + case ARIZONA_HAPTICS_PHASE_2_INTENSITY: + case ARIZONA_HAPTICS_PHASE_2_DURATION: + case ARIZONA_HAPTICS_PHASE_3_INTENSITY: + case ARIZONA_HAPTICS_PHASE_3_DURATION: + case ARIZONA_HAPTICS_STATUS: + case ARIZONA_CLOCK_32K_1: + case ARIZONA_SYSTEM_CLOCK_1: + case ARIZONA_SAMPLE_RATE_1: + case ARIZONA_SAMPLE_RATE_2: + case ARIZONA_SAMPLE_RATE_3: + case ARIZONA_SAMPLE_RATE_1_STATUS: + case ARIZONA_SAMPLE_RATE_2_STATUS: + case ARIZONA_SAMPLE_RATE_3_STATUS: + case ARIZONA_ASYNC_CLOCK_1: + case ARIZONA_ASYNC_SAMPLE_RATE_1: + case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_OUTPUT_SYSTEM_CLOCK: + case ARIZONA_OUTPUT_ASYNC_CLOCK: + case ARIZONA_RATE_ESTIMATOR_1: + case ARIZONA_RATE_ESTIMATOR_2: + case ARIZONA_RATE_ESTIMATOR_3: + case ARIZONA_RATE_ESTIMATOR_4: + case ARIZONA_RATE_ESTIMATOR_5: + case ARIZONA_DYNAMIC_FREQUENCY_SCALING_1: + case ARIZONA_FLL1_CONTROL_1: + case ARIZONA_FLL1_CONTROL_2: + case ARIZONA_FLL1_CONTROL_3: + case ARIZONA_FLL1_CONTROL_4: + case ARIZONA_FLL1_CONTROL_5: + case ARIZONA_FLL1_CONTROL_6: + case ARIZONA_FLL1_LOOP_FILTER_TEST_1: + case ARIZONA_FLL1_NCO_TEST_0: + case ARIZONA_FLL1_SYNCHRONISER_1: + case ARIZONA_FLL1_SYNCHRONISER_2: + case ARIZONA_FLL1_SYNCHRONISER_3: + case ARIZONA_FLL1_SYNCHRONISER_4: + case ARIZONA_FLL1_SYNCHRONISER_5: + case ARIZONA_FLL1_SYNCHRONISER_6: + case ARIZONA_FLL1_SPREAD_SPECTRUM: + case ARIZONA_FLL1_GPIO_CLOCK: + case ARIZONA_FLL2_CONTROL_1: + case ARIZONA_FLL2_CONTROL_2: + case ARIZONA_FLL2_CONTROL_3: + case ARIZONA_FLL2_CONTROL_4: + case ARIZONA_FLL2_CONTROL_5: + case ARIZONA_FLL2_CONTROL_6: + case ARIZONA_FLL2_LOOP_FILTER_TEST_1: + case ARIZONA_FLL2_NCO_TEST_0: + case ARIZONA_FLL2_SYNCHRONISER_1: + case ARIZONA_FLL2_SYNCHRONISER_2: + case ARIZONA_FLL2_SYNCHRONISER_3: + case ARIZONA_FLL2_SYNCHRONISER_4: + case ARIZONA_FLL2_SYNCHRONISER_5: + case ARIZONA_FLL2_SYNCHRONISER_6: + case ARIZONA_FLL2_SPREAD_SPECTRUM: + case ARIZONA_FLL2_GPIO_CLOCK: + case ARIZONA_MIC_CHARGE_PUMP_1: + case ARIZONA_LDO1_CONTROL_1: + case ARIZONA_LDO1_CONTROL_2: + case ARIZONA_LDO2_CONTROL_1: + case ARIZONA_MIC_BIAS_CTRL_1: + case ARIZONA_MIC_BIAS_CTRL_2: + case ARIZONA_MIC_BIAS_CTRL_3: + case ARIZONA_HP_CTRL_1L: + case ARIZONA_HP_CTRL_1R: + case ARIZONA_ACCESSORY_DETECT_MODE_1: + case ARIZONA_HEADPHONE_DETECT_1: + case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_MIC_DETECT_1: + case ARIZONA_MIC_DETECT_2: + case ARIZONA_MIC_DETECT_3: + case ARIZONA_MIC_NOISE_MIX_CONTROL_1: + case ARIZONA_ISOLATION_CONTROL: + case ARIZONA_JACK_DETECT_ANALOGUE: + case ARIZONA_INPUT_ENABLES: + case ARIZONA_INPUT_ENABLES_STATUS: + case ARIZONA_INPUT_RATE: + case ARIZONA_INPUT_VOLUME_RAMP: + case ARIZONA_IN1L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_1L: + case ARIZONA_DMIC1L_CONTROL: + case ARIZONA_IN1R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_1R: + case ARIZONA_DMIC1R_CONTROL: + case ARIZONA_IN2L_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_2L: + case ARIZONA_DMIC2L_CONTROL: + case ARIZONA_IN2R_CONTROL: + case ARIZONA_ADC_DIGITAL_VOLUME_2R: + case ARIZONA_DMIC2R_CONTROL: + case ARIZONA_OUTPUT_ENABLES_1: + case ARIZONA_OUTPUT_STATUS_1: + case ARIZONA_RAW_OUTPUT_STATUS_1: + case ARIZONA_OUTPUT_RATE_1: + case ARIZONA_OUTPUT_VOLUME_RAMP: + case ARIZONA_OUTPUT_PATH_CONFIG_1L: + case ARIZONA_DAC_DIGITAL_VOLUME_1L: + case ARIZONA_DAC_VOLUME_LIMIT_1L: + case ARIZONA_NOISE_GATE_SELECT_1L: + case ARIZONA_OUTPUT_PATH_CONFIG_1R: + case ARIZONA_DAC_DIGITAL_VOLUME_1R: + case ARIZONA_DAC_VOLUME_LIMIT_1R: + case ARIZONA_NOISE_GATE_SELECT_1R: + case ARIZONA_OUTPUT_PATH_CONFIG_3L: + case ARIZONA_DAC_DIGITAL_VOLUME_3L: + case ARIZONA_DAC_VOLUME_LIMIT_3L: + case ARIZONA_NOISE_GATE_SELECT_3L: + case ARIZONA_OUTPUT_PATH_CONFIG_4L: + case ARIZONA_DAC_DIGITAL_VOLUME_4L: + case ARIZONA_OUT_VOLUME_4L: + case ARIZONA_NOISE_GATE_SELECT_4L: + case ARIZONA_OUTPUT_PATH_CONFIG_5L: + case ARIZONA_DAC_DIGITAL_VOLUME_5L: + case ARIZONA_DAC_VOLUME_LIMIT_5L: + case ARIZONA_NOISE_GATE_SELECT_5L: + case ARIZONA_DAC_DIGITAL_VOLUME_5R: + case ARIZONA_DAC_VOLUME_LIMIT_5R: + case ARIZONA_NOISE_GATE_SELECT_5R: + case ARIZONA_DAC_AEC_CONTROL_1: + case ARIZONA_NOISE_GATE_CONTROL: + case ARIZONA_PDM_SPK1_CTRL_1: + case ARIZONA_PDM_SPK1_CTRL_2: + case ARIZONA_AIF1_BCLK_CTRL: + case ARIZONA_AIF1_TX_PIN_CTRL: + case ARIZONA_AIF1_RX_PIN_CTRL: + case ARIZONA_AIF1_RATE_CTRL: + case ARIZONA_AIF1_FORMAT: + case ARIZONA_AIF1_TX_BCLK_RATE: + case ARIZONA_AIF1_RX_BCLK_RATE: + case ARIZONA_AIF1_FRAME_CTRL_1: + case ARIZONA_AIF1_FRAME_CTRL_2: + case ARIZONA_AIF1_FRAME_CTRL_3: + case ARIZONA_AIF1_FRAME_CTRL_4: + case ARIZONA_AIF1_FRAME_CTRL_5: + case ARIZONA_AIF1_FRAME_CTRL_6: + case ARIZONA_AIF1_FRAME_CTRL_7: + case ARIZONA_AIF1_FRAME_CTRL_8: + case ARIZONA_AIF1_FRAME_CTRL_9: + case ARIZONA_AIF1_FRAME_CTRL_10: + case ARIZONA_AIF1_FRAME_CTRL_11: + case ARIZONA_AIF1_FRAME_CTRL_12: + case ARIZONA_AIF1_FRAME_CTRL_13: + case ARIZONA_AIF1_FRAME_CTRL_14: + case ARIZONA_AIF1_FRAME_CTRL_15: + case ARIZONA_AIF1_FRAME_CTRL_16: + case ARIZONA_AIF1_FRAME_CTRL_17: + case ARIZONA_AIF1_FRAME_CTRL_18: + case ARIZONA_AIF1_TX_ENABLES: + case ARIZONA_AIF1_RX_ENABLES: + case ARIZONA_AIF2_BCLK_CTRL: + case ARIZONA_AIF2_TX_PIN_CTRL: + case ARIZONA_AIF2_RX_PIN_CTRL: + case ARIZONA_AIF2_RATE_CTRL: + case ARIZONA_AIF2_FORMAT: + case ARIZONA_AIF2_TX_BCLK_RATE: + case ARIZONA_AIF2_RX_BCLK_RATE: + case ARIZONA_AIF2_FRAME_CTRL_1: + case ARIZONA_AIF2_FRAME_CTRL_2: + case ARIZONA_AIF2_FRAME_CTRL_3: + case ARIZONA_AIF2_FRAME_CTRL_4: + case ARIZONA_AIF2_FRAME_CTRL_11: + case ARIZONA_AIF2_FRAME_CTRL_12: + case ARIZONA_AIF2_TX_ENABLES: + case ARIZONA_AIF2_RX_ENABLES: + case ARIZONA_SLIMBUS_FRAMER_REF_GEAR: + case ARIZONA_SLIMBUS_RATES_1: + case ARIZONA_SLIMBUS_RATES_2: + case ARIZONA_SLIMBUS_RATES_3: + case ARIZONA_SLIMBUS_RATES_4: + case ARIZONA_SLIMBUS_RATES_5: + case ARIZONA_SLIMBUS_RATES_6: + case ARIZONA_SLIMBUS_RATES_7: + case ARIZONA_SLIMBUS_RATES_8: + case ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE: + case ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE: + case ARIZONA_SLIMBUS_RX_PORT_STATUS: + case ARIZONA_SLIMBUS_TX_PORT_STATUS: + case ARIZONA_PWM1MIX_INPUT_1_SOURCE: + case ARIZONA_PWM1MIX_INPUT_1_VOLUME: + case ARIZONA_PWM1MIX_INPUT_2_SOURCE: + case ARIZONA_PWM1MIX_INPUT_2_VOLUME: + case ARIZONA_PWM1MIX_INPUT_3_SOURCE: + case ARIZONA_PWM1MIX_INPUT_3_VOLUME: + case ARIZONA_PWM1MIX_INPUT_4_SOURCE: + case ARIZONA_PWM1MIX_INPUT_4_VOLUME: + case ARIZONA_PWM2MIX_INPUT_1_SOURCE: + case ARIZONA_PWM2MIX_INPUT_1_VOLUME: + case ARIZONA_PWM2MIX_INPUT_2_SOURCE: + case ARIZONA_PWM2MIX_INPUT_2_VOLUME: + case ARIZONA_PWM2MIX_INPUT_3_SOURCE: + case ARIZONA_PWM2MIX_INPUT_3_VOLUME: + case ARIZONA_PWM2MIX_INPUT_4_SOURCE: + case ARIZONA_PWM2MIX_INPUT_4_VOLUME: + case ARIZONA_MICMIX_INPUT_1_SOURCE: + case ARIZONA_MICMIX_INPUT_1_VOLUME: + case ARIZONA_MICMIX_INPUT_2_SOURCE: + case ARIZONA_MICMIX_INPUT_2_VOLUME: + case ARIZONA_MICMIX_INPUT_3_SOURCE: + case ARIZONA_MICMIX_INPUT_3_VOLUME: + case ARIZONA_MICMIX_INPUT_4_SOURCE: + case ARIZONA_MICMIX_INPUT_4_VOLUME: + case ARIZONA_NOISEMIX_INPUT_1_SOURCE: + case ARIZONA_NOISEMIX_INPUT_1_VOLUME: + case ARIZONA_NOISEMIX_INPUT_2_SOURCE: + case ARIZONA_NOISEMIX_INPUT_2_VOLUME: + case ARIZONA_NOISEMIX_INPUT_3_SOURCE: + case ARIZONA_NOISEMIX_INPUT_3_VOLUME: + case ARIZONA_NOISEMIX_INPUT_4_SOURCE: + case ARIZONA_NOISEMIX_INPUT_4_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT1LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT1LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT1RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT1RMIX_INPUT_4_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT3LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT3LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT4LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT4LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_1_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_1_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_2_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_2_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_3_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_3_VOLUME: + case ARIZONA_OUT5LMIX_INPUT_4_SOURCE: + case ARIZONA_OUT5LMIX_INPUT_4_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_1_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_1_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_2_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_2_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_3_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_3_VOLUME: + case ARIZONA_OUT5RMIX_INPUT_4_SOURCE: + case ARIZONA_OUT5RMIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX2MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX3MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX3MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX4MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX4MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX5MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX5MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX6MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX6MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX7MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX7MIX_INPUT_4_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_1_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_2_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_2_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_3_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_3_VOLUME: + case ARIZONA_AIF1TX8MIX_INPUT_4_SOURCE: + case ARIZONA_AIF1TX8MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX1MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX1MIX_INPUT_4_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_1_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_2_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_2_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_3_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_3_VOLUME: + case ARIZONA_AIF2TX2MIX_INPUT_4_SOURCE: + case ARIZONA_AIF2TX2MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX1MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX1MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX2MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX2MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX3MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX3MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX4MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX4MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX5MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX5MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX6MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX6MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX7MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX7MIX_INPUT_4_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_1_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_2_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_2_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_3_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_3_VOLUME: + case ARIZONA_SLIMTX8MIX_INPUT_4_SOURCE: + case ARIZONA_SLIMTX8MIX_INPUT_4_VOLUME: + case ARIZONA_EQ1MIX_INPUT_1_SOURCE: + case ARIZONA_EQ1MIX_INPUT_1_VOLUME: + case ARIZONA_EQ1MIX_INPUT_2_SOURCE: + case ARIZONA_EQ1MIX_INPUT_2_VOLUME: + case ARIZONA_EQ1MIX_INPUT_3_SOURCE: + case ARIZONA_EQ1MIX_INPUT_3_VOLUME: + case ARIZONA_EQ1MIX_INPUT_4_SOURCE: + case ARIZONA_EQ1MIX_INPUT_4_VOLUME: + case ARIZONA_EQ2MIX_INPUT_1_SOURCE: + case ARIZONA_EQ2MIX_INPUT_1_VOLUME: + case ARIZONA_EQ2MIX_INPUT_2_SOURCE: + case ARIZONA_EQ2MIX_INPUT_2_VOLUME: + case ARIZONA_EQ2MIX_INPUT_3_SOURCE: + case ARIZONA_EQ2MIX_INPUT_3_VOLUME: + case ARIZONA_EQ2MIX_INPUT_4_SOURCE: + case ARIZONA_EQ2MIX_INPUT_4_VOLUME: + case ARIZONA_EQ3MIX_INPUT_1_SOURCE: + case ARIZONA_EQ3MIX_INPUT_1_VOLUME: + case ARIZONA_EQ3MIX_INPUT_2_SOURCE: + case ARIZONA_EQ3MIX_INPUT_2_VOLUME: + case ARIZONA_EQ3MIX_INPUT_3_SOURCE: + case ARIZONA_EQ3MIX_INPUT_3_VOLUME: + case ARIZONA_EQ3MIX_INPUT_4_SOURCE: + case ARIZONA_EQ3MIX_INPUT_4_VOLUME: + case ARIZONA_EQ4MIX_INPUT_1_SOURCE: + case ARIZONA_EQ4MIX_INPUT_1_VOLUME: + case ARIZONA_EQ4MIX_INPUT_2_SOURCE: + case ARIZONA_EQ4MIX_INPUT_2_VOLUME: + case ARIZONA_EQ4MIX_INPUT_3_SOURCE: + case ARIZONA_EQ4MIX_INPUT_3_VOLUME: + case ARIZONA_EQ4MIX_INPUT_4_SOURCE: + case ARIZONA_EQ4MIX_INPUT_4_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_1_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_1_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_2_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_2_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_3_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_3_VOLUME: + case ARIZONA_DRC1LMIX_INPUT_4_SOURCE: + case ARIZONA_DRC1LMIX_INPUT_4_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_1_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_1_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_2_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_2_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_3_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_3_VOLUME: + case ARIZONA_DRC1RMIX_INPUT_4_SOURCE: + case ARIZONA_DRC1RMIX_INPUT_4_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP1MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP1MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP2MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP2MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP3MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP3MIX_INPUT_4_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_1_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_1_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_2_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_2_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_3_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_3_VOLUME: + case ARIZONA_HPLP4MIX_INPUT_4_SOURCE: + case ARIZONA_HPLP4MIX_INPUT_4_VOLUME: + case ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE: + case ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE: + case ARIZONA_GPIO1_CTRL: + case ARIZONA_GPIO2_CTRL: + case ARIZONA_GPIO3_CTRL: + case ARIZONA_GPIO4_CTRL: + case ARIZONA_GPIO5_CTRL: + case ARIZONA_IRQ_CTRL_1: + case ARIZONA_GPIO_DEBOUNCE_CONFIG: + case ARIZONA_MISC_PAD_CTRL_1: + case ARIZONA_MISC_PAD_CTRL_2: + case ARIZONA_MISC_PAD_CTRL_3: + case ARIZONA_MISC_PAD_CTRL_4: + case ARIZONA_MISC_PAD_CTRL_5: + case ARIZONA_INTERRUPT_STATUS_1: + case ARIZONA_INTERRUPT_STATUS_2: + case ARIZONA_INTERRUPT_STATUS_3: + case ARIZONA_INTERRUPT_STATUS_4: + case ARIZONA_INTERRUPT_STATUS_5: + case ARIZONA_INTERRUPT_STATUS_1_MASK: + case ARIZONA_INTERRUPT_STATUS_2_MASK: + case ARIZONA_INTERRUPT_STATUS_3_MASK: + case ARIZONA_INTERRUPT_STATUS_4_MASK: + case ARIZONA_INTERRUPT_STATUS_5_MASK: + case ARIZONA_INTERRUPT_CONTROL: + case ARIZONA_IRQ2_STATUS_1: + case ARIZONA_IRQ2_STATUS_3: + case ARIZONA_IRQ2_STATUS_4: + case ARIZONA_IRQ2_STATUS_5: + case ARIZONA_IRQ2_STATUS_1_MASK: + case ARIZONA_IRQ2_STATUS_3_MASK: + case ARIZONA_IRQ2_STATUS_4_MASK: + case ARIZONA_IRQ2_STATUS_5_MASK: + case ARIZONA_IRQ2_CONTROL: + case ARIZONA_INTERRUPT_RAW_STATUS_3: + case ARIZONA_INTERRUPT_RAW_STATUS_4: + case ARIZONA_INTERRUPT_RAW_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_7: + case ARIZONA_INTERRUPT_RAW_STATUS_8: + case ARIZONA_IRQ_PIN_STATUS: + case ARIZONA_AOD_WKUP_AND_TRIG: + case ARIZONA_AOD_IRQ1: + case ARIZONA_AOD_IRQ2: + case ARIZONA_AOD_IRQ_MASK_IRQ1: + case ARIZONA_AOD_IRQ_MASK_IRQ2: + case ARIZONA_AOD_IRQ_RAW_STATUS: + case ARIZONA_JACK_DETECT_DEBOUNCE: + case ARIZONA_FX_CTRL1: + case ARIZONA_FX_CTRL2: + case ARIZONA_EQ1_1: + case ARIZONA_EQ1_2: + case ARIZONA_EQ1_3: + case ARIZONA_EQ1_4: + case ARIZONA_EQ1_5: + case ARIZONA_EQ1_6: + case ARIZONA_EQ1_7: + case ARIZONA_EQ1_8: + case ARIZONA_EQ1_9: + case ARIZONA_EQ1_10: + case ARIZONA_EQ1_11: + case ARIZONA_EQ1_12: + case ARIZONA_EQ1_13: + case ARIZONA_EQ1_14: + case ARIZONA_EQ1_15: + case ARIZONA_EQ1_16: + case ARIZONA_EQ1_17: + case ARIZONA_EQ1_18: + case ARIZONA_EQ1_19: + case ARIZONA_EQ1_20: + case ARIZONA_EQ1_21: + case ARIZONA_EQ2_1: + case ARIZONA_EQ2_2: + case ARIZONA_EQ2_3: + case ARIZONA_EQ2_4: + case ARIZONA_EQ2_5: + case ARIZONA_EQ2_6: + case ARIZONA_EQ2_7: + case ARIZONA_EQ2_8: + case ARIZONA_EQ2_9: + case ARIZONA_EQ2_10: + case ARIZONA_EQ2_11: + case ARIZONA_EQ2_12: + case ARIZONA_EQ2_13: + case ARIZONA_EQ2_14: + case ARIZONA_EQ2_15: + case ARIZONA_EQ2_16: + case ARIZONA_EQ2_17: + case ARIZONA_EQ2_18: + case ARIZONA_EQ2_19: + case ARIZONA_EQ2_20: + case ARIZONA_EQ2_21: + case ARIZONA_EQ3_1: + case ARIZONA_EQ3_2: + case ARIZONA_EQ3_3: + case ARIZONA_EQ3_4: + case ARIZONA_EQ3_5: + case ARIZONA_EQ3_6: + case ARIZONA_EQ3_7: + case ARIZONA_EQ3_8: + case ARIZONA_EQ3_9: + case ARIZONA_EQ3_10: + case ARIZONA_EQ3_11: + case ARIZONA_EQ3_12: + case ARIZONA_EQ3_13: + case ARIZONA_EQ3_14: + case ARIZONA_EQ3_15: + case ARIZONA_EQ3_16: + case ARIZONA_EQ3_17: + case ARIZONA_EQ3_18: + case ARIZONA_EQ3_19: + case ARIZONA_EQ3_20: + case ARIZONA_EQ3_21: + case ARIZONA_EQ4_1: + case ARIZONA_EQ4_2: + case ARIZONA_EQ4_3: + case ARIZONA_EQ4_4: + case ARIZONA_EQ4_5: + case ARIZONA_EQ4_6: + case ARIZONA_EQ4_7: + case ARIZONA_EQ4_8: + case ARIZONA_EQ4_9: + case ARIZONA_EQ4_10: + case ARIZONA_EQ4_11: + case ARIZONA_EQ4_12: + case ARIZONA_EQ4_13: + case ARIZONA_EQ4_14: + case ARIZONA_EQ4_15: + case ARIZONA_EQ4_16: + case ARIZONA_EQ4_17: + case ARIZONA_EQ4_18: + case ARIZONA_EQ4_19: + case ARIZONA_EQ4_20: + case ARIZONA_EQ4_21: + case ARIZONA_DRC1_CTRL1: + case ARIZONA_DRC1_CTRL2: + case ARIZONA_DRC1_CTRL3: + case ARIZONA_DRC1_CTRL4: + case ARIZONA_DRC1_CTRL5: + case ARIZONA_HPLPF1_1: + case ARIZONA_HPLPF1_2: + case ARIZONA_HPLPF2_1: + case ARIZONA_HPLPF2_2: + case ARIZONA_HPLPF3_1: + case ARIZONA_HPLPF3_2: + case ARIZONA_HPLPF4_1: + case ARIZONA_HPLPF4_2: + case ARIZONA_ISRC_1_CTRL_1: + case ARIZONA_ISRC_1_CTRL_2: + case ARIZONA_ISRC_1_CTRL_3: + case ARIZONA_ISRC_2_CTRL_1: + case ARIZONA_ISRC_2_CTRL_2: + case ARIZONA_ISRC_2_CTRL_3: + return true; + default: + return false; + } +} + +static bool wm8997_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ARIZONA_SOFTWARE_RESET: + case ARIZONA_DEVICE_REVISION: + case ARIZONA_HAPTICS_STATUS: + case ARIZONA_SAMPLE_RATE_1_STATUS: + case ARIZONA_SAMPLE_RATE_2_STATUS: + case ARIZONA_SAMPLE_RATE_3_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_MIC_DETECT_3: + case ARIZONA_HP_CTRL_1L: + case ARIZONA_HP_CTRL_1R: + case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_INPUT_ENABLES_STATUS: + case ARIZONA_OUTPUT_STATUS_1: + case ARIZONA_RAW_OUTPUT_STATUS_1: + case ARIZONA_SLIMBUS_RX_PORT_STATUS: + case ARIZONA_SLIMBUS_TX_PORT_STATUS: + case ARIZONA_INTERRUPT_STATUS_1: + case ARIZONA_INTERRUPT_STATUS_2: + case ARIZONA_INTERRUPT_STATUS_3: + case ARIZONA_INTERRUPT_STATUS_4: + case ARIZONA_INTERRUPT_STATUS_5: + case ARIZONA_IRQ2_STATUS_1: + case ARIZONA_IRQ2_STATUS_3: + case ARIZONA_IRQ2_STATUS_4: + case ARIZONA_IRQ2_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_3: + case ARIZONA_INTERRUPT_RAW_STATUS_4: + case ARIZONA_INTERRUPT_RAW_STATUS_5: + case ARIZONA_INTERRUPT_RAW_STATUS_6: + case ARIZONA_INTERRUPT_RAW_STATUS_7: + case ARIZONA_INTERRUPT_RAW_STATUS_8: + case ARIZONA_IRQ_PIN_STATUS: + case ARIZONA_AOD_WKUP_AND_TRIG: + case ARIZONA_AOD_IRQ1: + case ARIZONA_AOD_IRQ2: + case ARIZONA_AOD_IRQ_RAW_STATUS: + case ARIZONA_FX_CTRL2: + return true; + default: + return false; + } +} + +#define WM8997_MAX_REGISTER 0x31ff + +const struct regmap_config wm8997_i2c_regmap = { + .reg_bits = 32, + .val_bits = 16, + + .max_register = WM8997_MAX_REGISTER, + .readable_reg = wm8997_readable_register, + .volatile_reg = wm8997_volatile_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8997_reg_default, + .num_reg_defaults = ARRAY_SIZE(wm8997_reg_default), +}; +EXPORT_SYMBOL_GPL(wm8997_i2c_regmap); |