summaryrefslogtreecommitdiff
path: root/drivers/mfd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig12
-rw-r--r--drivers/mfd/Makefile3
-rw-r--r--drivers/mfd/da9063-core.c185
-rw-r--r--drivers/mfd/da9063-i2c.c182
-rw-r--r--drivers/mfd/da9063-irq.c193
-rw-r--r--drivers/mfd/palmas.c97
-rw-r--r--drivers/mfd/rtl8411.c90
-rw-r--r--drivers/mfd/rts5209.c61
-rw-r--r--drivers/mfd/rts5227.c113
-rw-r--r--drivers/mfd/rts5229.c51
-rw-r--r--drivers/mfd/rts5249.c108
-rw-r--r--drivers/mfd/rtsx_pcr.c76
-rw-r--r--drivers/mfd/rtsx_pcr.h32
-rw-r--r--drivers/mfd/twl6030-irq.c377
14 files changed, 1314 insertions, 266 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 633ee43..e0e46f5 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -139,6 +139,18 @@ config MFD_DA9055
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 && GENERIC_HARDIRQS
+ 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_MC13783
tristate
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 3c90051..15b905c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -107,6 +107,9 @@ 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_MAX77686) += max77686.o max77686-irq.o
obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o
obj-$(CONFIG_MFD_MAX8907) += max8907.o
diff --git a/drivers/mfd/da9063-core.c b/drivers/mfd/da9063-core.c
new file mode 100644
index 0000000..c9cf8d9
--- /dev/null
+++ b/drivers/mfd/da9063-core.c
@@ -0,0 +1,185 @@
+/*
+ * 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 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,
+ },
+ {
+ .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,
+ },
+ {
+ .name = DA9063_DRVNAME_VIBRATION,
+ },
+};
+
+int da9063_device_init(struct da9063 *da9063, unsigned int irq)
+{
+ struct da9063_pdata *pdata = da9063->dev->platform_data;
+ int model, revision;
+ int ret;
+
+ if (pdata) {
+ da9063->flags = pdata->flags;
+ da9063->irq_base = pdata->irq_base;
+ } else {
+ da9063->flags = 0;
+ da9063->irq_base = 0;
+ }
+ 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, &revision);
+ if (ret < 0) {
+ dev_err(da9063->dev, "Cannot read chip revision id.\n");
+ return -EIO;
+ }
+ revision >>= DA9063_CHIP_VARIANT_SHIFT;
+ if (revision != 3) {
+ dev_err(da9063->dev, "Unknown chip revision: %d\n", revision);
+ return -ENODEV;
+ }
+
+ da9063->model = model;
+ da9063->revision = revision;
+
+ dev_info(da9063->dev,
+ "Device detected (model-ID: 0x%02X rev-ID: 0x%02X)\n",
+ model, revision);
+
+ ret = da9063_irq_init(da9063);
+ if (ret) {
+ dev_err(da9063->dev, "Cannot initialize interrupts.\n");
+ return ret;
+ }
+
+ 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/drivers/mfd/da9063-i2c.c b/drivers/mfd/da9063-i2c.c
new file mode 100644
index 0000000..8db5c80
--- /dev/null
+++ b/drivers/mfd/da9063-i2c.c
@@ -0,0 +1,182 @@
+/* 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>
+
+static const struct regmap_range da9063_readable_ranges[] = {
+ {
+ .range_min = DA9063_REG_PAGE_CON,
+ .range_max = DA9063_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_REG_GP_ID_19,
+ }, {
+ .range_min = DA9063_REG_CHIP_ID,
+ .range_max = DA9063_REG_CHIP_VARIANT,
+ },
+};
+
+static const struct regmap_range da9063_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_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_REG_MON_REG_4,
+ }, {
+ .range_min = DA9063_REG_GP_ID_0,
+ .range_max = DA9063_REG_GP_ID_19,
+ },
+};
+
+static const struct regmap_range da9063_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_REG_SECOND_D,
+ }, {
+ .range_min = DA9063_REG_MON_REG_5,
+ .range_max = DA9063_REG_MON_REG_6,
+ },
+};
+
+static const struct regmap_access_table da9063_readable_table = {
+ .yes_ranges = da9063_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(da9063_readable_ranges),
+};
+
+static const struct regmap_access_table da9063_writeable_table = {
+ .yes_ranges = da9063_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(da9063_writeable_ranges),
+};
+
+static const struct regmap_access_table da9063_volatile_table = {
+ .yes_ranges = da9063_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(da9063_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,
+
+ .rd_table = &da9063_readable_table,
+ .wr_table = &da9063_writeable_table,
+ .volatile_table = &da9063_volatile_table,
+};
+
+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;
+
+ 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,
+ },
+ .probe = da9063_i2c_probe,
+ .remove = da9063_i2c_remove,
+ .id_table = da9063_i2c_id,
+};
+
+module_i2c_driver(da9063_i2c_driver);
diff --git a/drivers/mfd/da9063-irq.c b/drivers/mfd/da9063-irq.c
new file mode 100644
index 0000000..8229226
--- /dev/null
+++ b/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/drivers/mfd/palmas.c b/drivers/mfd/palmas.c
index 220a34d..135afab 100644
--- a/drivers/mfd/palmas.c
+++ b/drivers/mfd/palmas.c
@@ -25,6 +25,52 @@
#include <linux/mfd/palmas.h>
#include <linux/of_device.h>
+#define PALMAS_EXT_REQ (PALMAS_EXT_CONTROL_ENABLE1 | \
+ PALMAS_EXT_CONTROL_ENABLE2 | \
+ PALMAS_EXT_CONTROL_NSLEEP)
+
+struct palmas_sleep_requestor_info {
+ int id;
+ int reg_offset;
+ int bit_pos;
+};
+
+#define EXTERNAL_REQUESTOR(_id, _offset, _pos) \
+ [PALMAS_EXTERNAL_REQSTR_ID_##_id] = { \
+ .id = PALMAS_EXTERNAL_REQSTR_ID_##_id, \
+ .reg_offset = _offset, \
+ .bit_pos = _pos, \
+ }
+
+static struct palmas_sleep_requestor_info sleep_req_info[] = {
+ EXTERNAL_REQUESTOR(REGEN1, 0, 0),
+ EXTERNAL_REQUESTOR(REGEN2, 0, 1),
+ EXTERNAL_REQUESTOR(SYSEN1, 0, 2),
+ EXTERNAL_REQUESTOR(SYSEN2, 0, 3),
+ EXTERNAL_REQUESTOR(CLK32KG, 0, 4),
+ EXTERNAL_REQUESTOR(CLK32KGAUDIO, 0, 5),
+ EXTERNAL_REQUESTOR(REGEN3, 0, 6),
+ EXTERNAL_REQUESTOR(SMPS12, 1, 0),
+ EXTERNAL_REQUESTOR(SMPS3, 1, 1),
+ EXTERNAL_REQUESTOR(SMPS45, 1, 2),
+ EXTERNAL_REQUESTOR(SMPS6, 1, 3),
+ EXTERNAL_REQUESTOR(SMPS7, 1, 4),
+ EXTERNAL_REQUESTOR(SMPS8, 1, 5),
+ EXTERNAL_REQUESTOR(SMPS9, 1, 6),
+ EXTERNAL_REQUESTOR(SMPS10, 1, 7),
+ EXTERNAL_REQUESTOR(LDO1, 2, 0),
+ EXTERNAL_REQUESTOR(LDO2, 2, 1),
+ EXTERNAL_REQUESTOR(LDO3, 2, 2),
+ EXTERNAL_REQUESTOR(LDO4, 2, 3),
+ EXTERNAL_REQUESTOR(LDO5, 2, 4),
+ EXTERNAL_REQUESTOR(LDO6, 2, 5),
+ EXTERNAL_REQUESTOR(LDO7, 2, 6),
+ EXTERNAL_REQUESTOR(LDO8, 2, 7),
+ EXTERNAL_REQUESTOR(LDO9, 3, 0),
+ EXTERNAL_REQUESTOR(LDOLN, 3, 1),
+ EXTERNAL_REQUESTOR(LDOUSB, 3, 2),
+};
+
static const struct regmap_config palmas_regmap_config[PALMAS_NUM_CLIENTS] = {
{
.reg_bits = 8,
@@ -186,6 +232,57 @@ static struct regmap_irq_chip palmas_irq_chip = {
PALMAS_INT1_MASK),
};
+int palmas_ext_control_req_config(struct palmas *palmas,
+ enum palmas_external_requestor_id id, int ext_ctrl, bool enable)
+{
+ int preq_mask_bit = 0;
+ int reg_add = 0;
+ int bit_pos;
+ int 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 = sleep_req_info[id].bit_pos;
+ reg_add += 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)
{
diff --git a/drivers/mfd/rtl8411.c b/drivers/mfd/rtl8411.c
index c436bf2..e4c1833 100644
--- a/drivers/mfd/rtl8411.c
+++ b/drivers/mfd/rtl8411.c
@@ -1,6 +1,6 @@
/* Driver for Realtek PCI-Express card reader
*
- * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved.
+ * 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
@@ -17,7 +17,7 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
+ * Roger Tseng <rogerable@realtek.com>
*/
#include <linux/module.h>
@@ -47,19 +47,77 @@ static int rtl8411b_is_qfn48(struct rtsx_pcr *pcr)
return 0;
}
+static void rtl8411_fetch_vendor_settings(struct rtsx_pcr *pcr)
+{
+ u32 reg1;
+ u8 reg3;
+
+ rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, &reg1);
+ dev_dbg(&(pcr->pci->dev), "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, &reg3);
+ dev_dbg(&(pcr->pci->dev), "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;
+
+ rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, &reg);
+ dev_dbg(&(pcr->pci->dev), "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)
{
- return rtsx_pci_write_register(pcr, CD_PAD_CTL,
+ 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)
{
- if (rtl8411b_is_qfn48(pcr))
- rtsx_pci_write_register(pcr, CARD_PULL_CTL3, 0xFF, 0xF5);
+ rtsx_pci_init_cmd(pcr);
- return rtsx_pci_write_register(pcr, CD_PAD_CTL,
+ 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)
@@ -141,13 +199,13 @@ static int rtl8411_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
mask = (BPP_REG_TUNED18 << BPP_TUNED18_SHIFT_8411) | BPP_PAD_MASK;
if (voltage == OUTPUT_3V3) {
err = rtsx_pci_write_register(pcr,
- SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_D);
+ SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_3v3);
if (err < 0)
return err;
val = (BPP_ASIC_3V3 << BPP_TUNED18_SHIFT_8411) | BPP_PAD_3V3;
} else if (voltage == OUTPUT_1V8) {
err = rtsx_pci_write_register(pcr,
- SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_B);
+ SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_1v8);
if (err < 0)
return err;
val = (BPP_ASIC_1V8 << BPP_TUNED18_SHIFT_8411) | BPP_PAD_1V8;
@@ -222,6 +280,7 @@ static int rtl8411_conv_clk_and_div_n(int input, int dir)
}
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,
@@ -233,9 +292,11 @@ static const struct pcr_ops rtl8411_pcr_ops = {
.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 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,
@@ -247,6 +308,7 @@ static const struct pcr_ops rtl8411b_pcr_ops = {
.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:
@@ -385,6 +447,12 @@ void rtl8411_init_params(struct rtsx_pcr *pcr)
pcr->num_slots = 2;
pcr->ops = &rtl8411_pcr_ops;
+ 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->ic_version = rtl8411_get_ic_version(pcr);
pcr->sd_pull_ctl_enable_tbl = rtl8411_sd_pull_ctl_enable_tbl;
pcr->sd_pull_ctl_disable_tbl = rtl8411_sd_pull_ctl_disable_tbl;
@@ -398,6 +466,12 @@ void rtl8411b_init_params(struct rtsx_pcr *pcr)
pcr->num_slots = 2;
pcr->ops = &rtl8411b_pcr_ops;
+ 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->ic_version = rtl8411_get_ic_version(pcr);
if (rtl8411b_is_qfn48(pcr)) {
diff --git a/drivers/mfd/rts5209.c b/drivers/mfd/rts5209.c
index ec78d9f..4026e1f 100644
--- a/drivers/mfd/rts5209.c
+++ b/drivers/mfd/rts5209.c
@@ -1,6 +1,6 @@
/* Driver for Realtek PCI-Express card reader
*
- * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved.
+ * 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
@@ -17,7 +17,6 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
*/
#include <linux/module.h>
@@ -34,19 +33,34 @@ static u8 rts5209_get_ic_version(struct rtsx_pcr *pcr)
return val & 0x0F;
}
-static void rts5209_init_vendor_cfg(struct rtsx_pcr *pcr)
+static void rts5209_fetch_vendor_settings(struct rtsx_pcr *pcr)
{
- u32 val;
+ u32 reg;
- rtsx_pci_read_config_dword(pcr, 0x724, &val);
- dev_dbg(&(pcr->pci->dev), "Cfg 0x724: 0x%x\n", val);
+ rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, &reg);
+ dev_dbg(&(pcr->pci->dev), "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg);
- if (!(val & 0x80)) {
- if (val & 0x08)
- pcr->ms_pmos = false;
- else
- pcr->ms_pmos = true;
+ 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, &reg);
+ dev_dbg(&(pcr->pci->dev), "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)
@@ -55,8 +69,15 @@ static int rts5209_extra_init_hw(struct rtsx_pcr *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);
}
@@ -95,7 +116,7 @@ static int rts5209_card_power_on(struct rtsx_pcr *pcr, int card)
partial_pwr_on = SD_PARTIAL_POWER_ON;
pwr_on = SD_POWER_ON;
- if (pcr->ms_pmos && (card == RTSX_MS_CARD)) {
+ 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;
@@ -131,7 +152,7 @@ static int rts5209_card_power_off(struct rtsx_pcr *pcr, int card)
pwr_mask = SD_POWER_MASK;
pwr_off = SD_POWER_OFF;
- if (pcr->ms_pmos && (card == RTSX_MS_CARD)) {
+ if ((pcr->flags & PCR_MS_PMOS) && (card == RTSX_MS_CARD)) {
pwr_mask = MS_POWER_MASK;
pwr_off = MS_POWER_OFF;
}
@@ -140,7 +161,7 @@ static int rts5209_card_power_off(struct rtsx_pcr *pcr, int card)
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);
+ LDO3318_PWR_MASK, 0x06);
return rtsx_pci_send_cmd(pcr, 100);
}
@@ -150,7 +171,7 @@ static int rts5209_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
if (voltage == OUTPUT_3V3) {
err = rtsx_pci_write_register(pcr,
- SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_D);
+ SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_3v3);
if (err < 0)
return err;
err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24);
@@ -158,7 +179,7 @@ static int rts5209_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
return err;
} else if (voltage == OUTPUT_1V8) {
err = rtsx_pci_write_register(pcr,
- SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_B);
+ SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_1v8);
if (err < 0)
return err;
err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C40 | 0x24);
@@ -172,6 +193,7 @@ static int rts5209_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
}
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,
@@ -183,6 +205,7 @@ static const struct pcr_ops rts5209_pcr_ops = {
.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:
@@ -242,7 +265,11 @@ void rts5209_init_params(struct rtsx_pcr *pcr)
pcr->num_slots = 2;
pcr->ops = &rts5209_pcr_ops;
- rts5209_init_vendor_cfg(pcr);
+ 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->ic_version = rts5209_get_ic_version(pcr);
pcr->sd_pull_ctl_enable_tbl = rts5209_sd_pull_ctl_enable_tbl;
diff --git a/drivers/mfd/rts5227.c b/drivers/mfd/rts5227.c
index fc831dc..d7cae82 100644
--- a/drivers/mfd/rts5227.c
+++ b/drivers/mfd/rts5227.c
@@ -1,6 +1,6 @@
/* Driver for Realtek PCI-Express card reader
*
- * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved.
+ * 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
@@ -17,10 +17,7 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
- *
* Roger Tseng <rogerable@realtek.com>
- * No. 2, Innovation Road II, Hsinchu Science Park, Hsinchu 300, Taiwan
*/
#include <linux/module.h>
@@ -29,6 +26,73 @@
#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, &reg);
+ dev_dbg(&(pcr->pci->dev), "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, &reg);
+ dev_dbg(&(pcr->pci->dev), "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;
@@ -37,6 +101,8 @@ static int rts5227_extra_init_hw(struct rtsx_pcr *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);
@@ -48,17 +114,16 @@ static int rts5227_extra_init_hw(struct rtsx_pcr *pcr)
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 force_clock_req
- * Maybe We should define 0xFF03 as some name
- */
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, 0xFF03, 0x08, 0x08);
- /* Correct driving */
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
- SD30_CLK_DRIVE_SEL, 0xFF, 0x96);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
- SD30_CMD_DRIVE_SEL, 0xFF, 0x96);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
- SD30_DAT_DRIVE_SEL, 0xFF, 0x96);
+ /* 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,
+ AUTOLOAD_CFG_BASE + 3, 0xB8, 0xB8);
+ else
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
+ AUTOLOAD_CFG_BASE + 3, 0xB8, 0x88);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PM_CTRL3, 0x10, 0x00);
return rtsx_pci_send_cmd(pcr, 100);
}
@@ -131,13 +196,11 @@ static int rts5227_card_power_off(struct rtsx_pcr *pcr, int card)
static int rts5227_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
{
int err;
- u8 drive_sel;
if (voltage == OUTPUT_3V3) {
err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24);
if (err < 0)
return err;
- drive_sel = 0x96;
} else if (voltage == OUTPUT_1V8) {
err = rtsx_pci_write_phy_register(pcr, 0x11, 0x3C02);
if (err < 0)
@@ -145,23 +208,18 @@ static int rts5227_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C80 | 0x24);
if (err < 0)
return err;
- drive_sel = 0xB3;
} else {
return -EINVAL;
}
/* set pad drive */
rtsx_pci_init_cmd(pcr);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CLK_DRIVE_SEL,
- 0xFF, drive_sel);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CMD_DRIVE_SEL,
- 0xFF, drive_sel);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DAT_DRIVE_SEL,
- 0xFF, drive_sel);
+ 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,
@@ -173,6 +231,7 @@ static const struct pcr_ops rts5227_pcr_ops = {
.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:
@@ -227,6 +286,12 @@ void rts5227_init_params(struct rtsx_pcr *pcr)
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->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;
diff --git a/drivers/mfd/rts5229.c b/drivers/mfd/rts5229.c
index 58af4db..620e7fa 100644
--- a/drivers/mfd/rts5229.c
+++ b/drivers/mfd/rts5229.c
@@ -1,6 +1,6 @@
/* Driver for Realtek PCI-Express card reader
*
- * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved.
+ * 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
@@ -17,7 +17,6 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
*/
#include <linux/module.h>
@@ -34,17 +33,51 @@ static u8 rts5229_get_ic_version(struct rtsx_pcr *pcr)
return val & 0x0F;
}
+static void rts5229_fetch_vendor_settings(struct rtsx_pcr *pcr)
+{
+ u32 reg;
+
+ rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, &reg);
+ dev_dbg(&(pcr->pci->dev), "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, &reg);
+ dev_dbg(&(pcr->pci->dev), "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);
}
@@ -110,7 +143,7 @@ static int rts5229_card_power_off(struct rtsx_pcr *pcr, int card)
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);
+ LDO3318_PWR_MASK, 0x00);
return rtsx_pci_send_cmd(pcr, 100);
}
@@ -120,7 +153,7 @@ static int rts5229_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
if (voltage == OUTPUT_3V3) {
err = rtsx_pci_write_register(pcr,
- SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_D);
+ SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_3v3);
if (err < 0)
return err;
err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24);
@@ -128,7 +161,7 @@ static int rts5229_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
return err;
} else if (voltage == OUTPUT_1V8) {
err = rtsx_pci_write_register(pcr,
- SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_B);
+ SD30_DRIVE_SEL, 0x07, pcr->sd30_drive_sel_1v8);
if (err < 0)
return err;
err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C40 | 0x24);
@@ -142,6 +175,7 @@ static int rts5229_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
}
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,
@@ -153,6 +187,7 @@ static const struct pcr_ops rts5229_pcr_ops = {
.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:
@@ -221,6 +256,12 @@ void rts5229_init_params(struct rtsx_pcr *pcr)
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->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;
diff --git a/drivers/mfd/rts5249.c b/drivers/mfd/rts5249.c
index 15dc848..ea90f8f 100644
--- a/drivers/mfd/rts5249.c
+++ b/drivers/mfd/rts5249.c
@@ -17,7 +17,6 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 128, West Shenhu Road, Suzhou Industry Park, Suzhou, China
*/
#include <linux/module.h>
@@ -34,24 +33,95 @@ static u8 rts5249_get_ic_version(struct rtsx_pcr *pcr)
return val & 0x0F;
}
+static void rts5249_fill_driving(struct rtsx_pcr *pcr, u8 voltage)
+{
+ u8 driving_3v3[4][3] = {
+ {0x11, 0x11, 0x11},
+ {0x55, 0x55, 0x5C},
+ {0x99, 0x99, 0x92},
+ {0x99, 0x99, 0x92},
+ };
+ u8 driving_1v8[4][3] = {
+ {0x3C, 0x3C, 0x3C},
+ {0xB3, 0xB3, 0xB3},
+ {0xFE, 0xFE, 0xFE},
+ {0xC4, 0xC4, 0xC4},
+ };
+ 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 rts5249_fetch_vendor_settings(struct rtsx_pcr *pcr)
+{
+ u32 reg;
+
+ rtsx_pci_read_config_dword(pcr, PCR_SETTING_REG1, &reg);
+ dev_dbg(&(pcr->pci->dev), "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, &reg);
+ dev_dbg(&(pcr->pci->dev), "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 rts5249_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 rts5249_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);
/* 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);
- /* Correct driving */
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
- SD30_CLK_DRIVE_SEL, 0xFF, 0x99);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
- SD30_CMD_DRIVE_SEL, 0xFF, 0x99);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
- SD30_DAT_DRIVE_SEL, 0xFF, 0x92);
+ /* Configure driving */
+ rts5249_fill_driving(pcr, OUTPUT_3V3);
+ if (pcr->flags & PCR_REVERSE_SOCKET)
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
+ AUTOLOAD_CFG_BASE + 3, 0xB0, 0xB0);
+ else
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
+ AUTOLOAD_CFG_BASE + 3, 0xB0, 0x80);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PM_CTRL3, 0x10, 0x00);
return rtsx_pci_send_cmd(pcr, 100);
}
@@ -129,15 +199,11 @@ static int rts5249_card_power_off(struct rtsx_pcr *pcr, int card)
static int rts5249_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
{
int err;
- u8 clk_drive, cmd_drive, dat_drive;
if (voltage == OUTPUT_3V3) {
err = rtsx_pci_write_phy_register(pcr, PHY_TUNE, 0x4FC0 | 0x24);
if (err < 0)
return err;
- clk_drive = 0x99;
- cmd_drive = 0x99;
- dat_drive = 0x92;
} else if (voltage == OUTPUT_1V8) {
err = rtsx_pci_write_phy_register(pcr, PHY_BACR, 0x3C02);
if (err < 0)
@@ -145,25 +211,18 @@ static int rts5249_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage)
err = rtsx_pci_write_phy_register(pcr, PHY_TUNE, 0x4C40 | 0x24);
if (err < 0)
return err;
- clk_drive = 0xb3;
- cmd_drive = 0xb3;
- dat_drive = 0xb3;
} else {
return -EINVAL;
}
/* set pad drive */
rtsx_pci_init_cmd(pcr);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CLK_DRIVE_SEL,
- 0xFF, clk_drive);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_CMD_DRIVE_SEL,
- 0xFF, cmd_drive);
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DAT_DRIVE_SEL,
- 0xFF, dat_drive);
+ rts5249_fill_driving(pcr, voltage);
return rtsx_pci_send_cmd(pcr, 100);
}
static const struct pcr_ops rts5249_pcr_ops = {
+ .fetch_vendor_settings = rts5249_fetch_vendor_settings,
.extra_init_hw = rts5249_extra_init_hw,
.optimize_phy = rts5249_optimize_phy,
.turn_on_led = rts5249_turn_on_led,
@@ -173,6 +232,7 @@ static const struct pcr_ops rts5249_pcr_ops = {
.card_power_on = rts5249_card_power_on,
.card_power_off = rts5249_card_power_off,
.switch_output_voltage = rts5249_switch_output_voltage,
+ .force_power_down = rts5249_force_power_down,
};
/* SD Pull Control Enable:
@@ -233,6 +293,12 @@ void rts5249_init_params(struct rtsx_pcr *pcr)
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_C;
+ pcr->sd30_drive_sel_3v3 = CFG_DRIVER_TYPE_B;
+ pcr->aspm_en = ASPM_L1_EN;
+
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;
diff --git a/drivers/mfd/rtsx_pcr.c b/drivers/mfd/rtsx_pcr.c
index dd186c4..e6ae772 100644
--- a/drivers/mfd/rtsx_pcr.c
+++ b/drivers/mfd/rtsx_pcr.c
@@ -1,6 +1,6 @@
/* Driver for Realtek PCI-Express card reader
*
- * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved.
+ * 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
@@ -17,7 +17,6 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
*/
#include <linux/pci.h>
@@ -73,6 +72,9 @@ void rtsx_pci_start_run(struct rtsx_pcr *pcr)
pcr->state = PDEV_STAT_RUN;
if (pcr->ops->enable_auto_blink)
pcr->ops->enable_auto_blink(pcr);
+
+ if (pcr->aspm_en)
+ rtsx_pci_write_config_byte(pcr, LCTLR, 0);
}
mod_delayed_work(system_wq, &pcr->idle_work, msecs_to_jiffies(200));
@@ -717,7 +719,7 @@ int rtsx_pci_card_exclusive_check(struct rtsx_pcr *pcr, int card)
[RTSX_MS_CARD] = MS_EXIST
};
- if (!pcr->ms_pmos) {
+ if (!(pcr->flags & PCR_MS_PMOS)) {
/* When using single PMOS, accessing card is not permitted
* if the existing card is not the designated one.
*/
@@ -918,9 +920,27 @@ static void rtsx_pci_idle_work(struct work_struct *work)
if (pcr->ops->turn_off_led)
pcr->ops->turn_off_led(pcr);
+ if (pcr->aspm_en)
+ rtsx_pci_write_config_byte(pcr, LCTLR, pcr->aspm_en);
+
mutex_unlock(&pcr->pcr_mutex);
}
+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);
+}
+
static int rtsx_pci_init_hw(struct rtsx_pcr *pcr)
{
int err;
@@ -951,13 +971,11 @@ static int rtsx_pci_init_hw(struct rtsx_pcr *pcr)
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 ASPM state to default value */
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 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, SD30_DRIVE_SEL,
- 0x07, DRIVER_TYPE_D);
+ 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);
@@ -982,13 +1000,13 @@ static int rtsx_pci_init_hw(struct rtsx_pcr *pcr)
* 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);
- /* Force CLKREQ# PIN to drive 0 to request clock */
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x08, 0x08);
err = rtsx_pci_send_cmd(pcr, 100);
if (err < 0)
return err;
+ rtsx_pci_write_config_byte(pcr, LCTLR, 0);
+
/* Enable clk_request_n to enable clock power management */
rtsx_pci_write_config_byte(pcr, 0x81, 1);
/* Enter L1 when host tx idle */
@@ -1053,6 +1071,18 @@ static int rtsx_pci_init_chip(struct rtsx_pcr *pcr)
if (!pcr->slots)
return -ENOMEM;
+ if (pcr->ops->fetch_vendor_settings)
+ pcr->ops->fetch_vendor_settings(pcr);
+
+ dev_dbg(&(pcr->pci->dev), "pcr->aspm_en = 0x%x\n", pcr->aspm_en);
+ dev_dbg(&(pcr->pci->dev), "pcr->sd30_drive_sel_1v8 = 0x%x\n",
+ pcr->sd30_drive_sel_1v8);
+ dev_dbg(&(pcr->pci->dev), "pcr->sd30_drive_sel_3v3 = 0x%x\n",
+ pcr->sd30_drive_sel_3v3);
+ dev_dbg(&(pcr->pci->dev), "pcr->card_drive_sel = 0x%x\n",
+ pcr->card_drive_sel);
+ dev_dbg(&(pcr->pci->dev), "pcr->flags = 0x%x\n", pcr->flags);
+
pcr->state = PDEV_STAT_IDLE;
err = rtsx_pci_init_hw(pcr);
if (err < 0) {
@@ -1235,7 +1265,6 @@ static int rtsx_pci_suspend(struct pci_dev *pcidev, pm_message_t state)
{
struct pcr_handle *handle;
struct rtsx_pcr *pcr;
- int ret = 0;
dev_dbg(&(pcidev->dev), "--> %s\n", __func__);
@@ -1247,14 +1276,7 @@ static int rtsx_pci_suspend(struct pci_dev *pcidev, pm_message_t state)
mutex_lock(&pcr->pcr_mutex);
- 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, 0x02);
+ rtsx_pci_power_off(pcr, HOST_ENTER_S3);
pci_save_state(pcidev);
pci_enable_wake(pcidev, pci_choose_state(pcidev, state), 0);
@@ -1262,7 +1284,7 @@ static int rtsx_pci_suspend(struct pci_dev *pcidev, pm_message_t state)
pci_set_power_state(pcidev, pci_choose_state(pcidev, state));
mutex_unlock(&pcr->pcr_mutex);
- return ret;
+ return 0;
}
static int rtsx_pci_resume(struct pci_dev *pcidev)
@@ -1300,10 +1322,25 @@ out:
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 */
@@ -1314,6 +1351,7 @@ static struct pci_driver rtsx_pci_driver = {
.remove = rtsx_pci_remove,
.suspend = rtsx_pci_suspend,
.resume = rtsx_pci_resume,
+ .shutdown = rtsx_pci_shutdown,
};
module_pci_driver(rtsx_pci_driver);
diff --git a/drivers/mfd/rtsx_pcr.h b/drivers/mfd/rtsx_pcr.h
index c0cac7e..947e79b 100644
--- a/drivers/mfd/rtsx_pcr.h
+++ b/drivers/mfd/rtsx_pcr.h
@@ -1,6 +1,6 @@
/* Driver for Realtek PCI-Express card reader
*
- * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved.
+ * 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
@@ -17,7 +17,6 @@
*
* Author:
* Wei WANG <wei_wang@realsil.com.cn>
- * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
*/
#ifndef __RTSX_PCR_H
@@ -35,4 +34,33 @@ void rts5227_init_params(struct rtsx_pcr *pcr);
void rts5249_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)
+
#endif
diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c
index 277a8db..517eda8 100644
--- a/drivers/mfd/twl6030-irq.c
+++ b/drivers/mfd/twl6030-irq.c
@@ -41,6 +41,7 @@
#include <linux/suspend.h>
#include <linux/of.h>
#include <linux/irqdomain.h>
+#include <linux/of_device.h>
#include "twl-core.h"
@@ -84,39 +85,77 @@ static int twl6030_interrupt_mapping[24] = {
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 */
+};
+
/*----------------------------------------------------------------------*/
-static unsigned twl6030_irq_base;
-static int twl_irq;
-static bool twl_irq_wake_enabled;
+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 completion irq_event;
-static atomic_t twl6030_wakeirqs = ATOMIC_INIT(0);
+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(&twl6030_wakeirqs);
+ chained_wakeups = atomic_read(&pdata->wakeirqs);
- if (chained_wakeups && !twl_irq_wake_enabled) {
- if (enable_irq_wake(twl_irq))
+ if (chained_wakeups && !pdata->irq_wake_enabled) {
+ if (enable_irq_wake(pdata->twl_irq))
pr_err("twl6030 IRQ wake enable failed\n");
else
- twl_irq_wake_enabled = true;
- } else if (!chained_wakeups && twl_irq_wake_enabled) {
- disable_irq_wake(twl_irq);
- twl_irq_wake_enabled = false;
+ 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(twl_irq);
+ disable_irq(pdata->twl_irq);
break;
case PM_POST_SUSPEND:
- enable_irq(twl_irq);
+ enable_irq(pdata->twl_irq);
break;
default:
@@ -126,124 +165,77 @@ static int twl6030_irq_pm_notifier(struct notifier_block *notifier,
return NOTIFY_DONE;
}
-static struct notifier_block twl6030_irq_pm_notifier_block = {
- .notifier_call = twl6030_irq_pm_notifier,
-};
-
/*
- * This thread processes interrupts reported by the Primary Interrupt Handler.
- */
-static int twl6030_irq_thread(void *data)
+* 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)
{
- long irq = (long)data;
- static unsigned i2c_errors;
- static const unsigned max_i2c_errors = 100;
- int ret;
-
- while (!kthread_should_stop()) {
- int i;
- union {
+ int i, ret;
+ union {
u8 bytes[4];
u32 int_sts;
- } sts;
-
- /* Wait for IRQ, then read PIH irq status (also blocking) */
- wait_for_completion_interruptible(&irq_event);
-
- /* 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_warning("twl6030: I2C error %d reading PIH ISR\n",
- ret);
- if (++i2c_errors >= max_i2c_errors) {
- printk(KERN_ERR "Maximum I2C error count"
- " exceeded. Terminating %s.\n",
- __func__);
- break;
- }
- complete(&irq_event);
- continue;
- }
-
-
+ } sts;
+ 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*/
+ 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;
-
- for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) {
- local_irq_disable();
- if (sts.int_sts & 0x1) {
- int module_irq = twl6030_irq_base +
- twl6030_interrupt_mapping[i];
- generic_handle_irq(module_irq);
-
- }
- local_irq_enable();
+ /*
+ * 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;
+
+ for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++)
+ if (sts.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_warning("twl6030: I2C error in clearing PIH ISR\n");
-
- enable_irq(irq);
- }
-
- return 0;
-}
+ /*
+ * 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");
-/*
- * handle_twl6030_int() is the desc->handle method for the twl6030 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 twl6030 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_twl6030_pih(int irq, void *devid)
-{
- disable_irq_nosync(irq);
- complete(devid);
return IRQ_HANDLED;
}
/*----------------------------------------------------------------------*/
-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
-}
-
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(&twl6030_wakeirqs);
+ atomic_inc(&pdata->wakeirqs);
else
- atomic_dec(&twl6030_wakeirqs);
+ atomic_dec(&pdata->wakeirqs);
return 0;
}
@@ -318,7 +310,8 @@ int twl6030_mmc_card_detect_config(void)
return ret;
}
- return twl6030_irq_base + MMCDETECT_INTR_OFFSET;
+ return irq_find_mapping(twl6030_irq->irq_domain,
+ MMCDETECT_INTR_OFFSET);
}
EXPORT_SYMBOL(twl6030_mmc_card_detect_config);
@@ -347,99 +340,143 @@ int twl6030_mmc_card_detect(struct device *dev, int slot)
}
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, irq_base, irq_end;
- struct task_struct *task;
- static struct irq_chip twl6030_irq_chip;
- int status = 0;
- int i;
+ int nr_irqs;
+ int status;
u8 mask[3];
+ const struct of_device_id *of_id;
- nr_irqs = TWL6030_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;
+ 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;
}
- irq_domain_add_legacy(node, nr_irqs, irq_base, 0,
- &irq_domain_simple_ops, NULL);
+ nr_irqs = TWL6030_NR_IRQS;
- irq_end = irq_base + 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 */
- twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_MSK_LINE_A, 3);
+ status = twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_MSK_LINE_A, 3);
/* mask all int sts */
- twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_MSK_STS_A, 3);
+ status |= twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_MSK_STS_A, 3);
/* clear INT_STS_A,B,C */
- twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_STS_A, 3);
+ status |= twl_i2c_write(TWL_MODULE_PIH, &mask[0], REG_INT_STS_A, 3);
- twl6030_irq_base = irq_base;
+ 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_chip = dummy_irq_chip;
- twl6030_irq_chip.name = "twl6030";
- twl6030_irq_chip.irq_set_type = NULL;
- twl6030_irq_chip.irq_set_wake = twl6030_irq_set_wake;
-
- for (i = irq_base; i < irq_end; i++) {
- irq_set_chip_and_handler(i, &twl6030_irq_chip,
- handle_simple_irq);
- irq_set_chip_data(i, (void *)irq_num);
- activate_irq(i);
+ 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) chaining IRQs %d..%d\n",
- irq_num, irq_base, irq_end);
+ dev_info(dev, "PIH (irq %d) nested IRQs\n", irq_num);
/* install an irq handler to demultiplex the TWL6030 interrupt */
- init_completion(&irq_event);
-
- status = request_irq(irq_num, handle_twl6030_pih, 0, "TWL6030-PIH",
- &irq_event);
+ 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;
}
- task = kthread_run(twl6030_irq_thread, (void *)irq_num, "twl6030-irq");
- if (IS_ERR(task)) {
- dev_err(dev, "could not create irq %d thread!\n", irq_num);
- status = PTR_ERR(task);
- goto fail_kthread;
- }
-
- twl_irq = irq_num;
- register_pm_notifier(&twl6030_irq_pm_notifier_block);
- return irq_base;
-
-fail_kthread:
- free_irq(irq_num, &irq_event);
+ twl6030_irq->twl_irq = irq_num;
+ register_pm_notifier(&twl6030_irq->pm_nb);
+ return 0;
fail_irq:
- for (i = irq_base; i < irq_end; i++)
- irq_set_chip_and_handler(i, NULL, NULL);
-
+ irq_domain_remove(twl6030_irq->irq_domain);
return status;
}
int twl6030_exit_irq(void)
{
- unregister_pm_notifier(&twl6030_irq_pm_notifier_block);
-
- if (twl6030_irq_base) {
- pr_err("twl6030: can't yet clean up IRQs?\n");
- return -ENOSYS;
+ 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;
}