summaryrefslogtreecommitdiff
path: root/drivers/mfd/tps65217.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-10-07 15:35:35 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2016-10-07 15:35:35 (GMT)
commitd042380886fb2fc6c4b0fcfe1214ba473769a8e9 (patch)
tree61e29ff167e0f83f67930ee9911062415030e1ef /drivers/mfd/tps65217.c
parent3477d168ba61c5b0ca42d3d4642f3463609a5417 (diff)
parentb8d336ed90f541097a2ce583be430bb3e895dfbd (diff)
downloadlinux-d042380886fb2fc6c4b0fcfe1214ba473769a8e9.tar.xz
Merge tag 'mfd-for-linus-4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull MFD updates from Lee Jones: "Core framework: - Add the MFD bindings doc to MAINTAINERS New drivers: - X-Powers AC100 Audio CODEC and RTC - TI LP873x PMIC - Rockchip RK808 PMIC - Samsung Exynos Low Power Audio New device support: - Add support for STMPE1600 variant to stmpe - Add support for PM8018 PMIC to pm8921-core - Add support for AXP806 PMIC in axp20x - Add support for AXP209 GPIO in axp20x New functionality: - Add support for Reset to all STMPE variants - Add support for MKBP event support to cros_ec - Add support for USB to intel_soc_pmic_bxtwc - Add support for IRQs and Power Button to tps65217 Fix-ups: - Clean-up defunct author emails (da9063, max14577) - Kconfig fixups (wm8350-i2c, as37220 - Constify (altera-a10sr, sm501) - Supply PCI IDs (intel-lpss-pci) - Improve clocking (qcom_rpm) - Fix IRQ probing (ucb1x00-core) - Ensure fault log is cleared (da9052) - Remove NO_IRQ check (ucb1x00-core) - Supply I2C properties (intel-lpss-acpi, intel-lpss-pci) - Non standard declaration (tps65217, max8997-irq) - Remove unused code (lp873x, db8500-prcmu, ab8500-debugfs, cros_ec_spi) - Make non-modular (altera-a10sr, intel_msic, smsc-ece1099, sun6i-prcm, twl-core) - OF bindings (ac100, stmpe, qcom-pm8xxx, qcom-rpm, rk808, axp20x, lp873x, exynos5433-lpass, act8945a, aspeed-scu, twl6040, arizona) Bugfixes: - Release OF pointer (qcom_rpm) - Avoid double shifting in suspend/resume (88pm80x) - Fix 'defined but not used' error (exynos-lpass) - Fix 'sleeping whilst attomic' (atmel-hlcdc)" * tag 'mfd-for-linus-4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (69 commits) mfd: arizona: Handle probe deferral for reset GPIO mfd: arizona: Remove arizona_of_get_named_gpio helper function mfd: arizona: Add DT options for max_channels_clocked and PDM speaker config mfd: twl6040: Register child device for twl6040-pdmclk mfd: cros_ec_spi: Remove unused variable 'request' mfd: omap-usb-host: Return value is not 'const int' mfd: ab8500-debugfs: Remove 'weak' function suspend_test_wake_cause_interrupt_is_mine() mfd: ab8500-debugfs: Remove ab8500_dump_all_banks_to_mem() mfd: db8500-prcmu: Remove unused *prcmu_set_ddr_opp() calls mfd: ab8500-debugfs: Prevent initialised field from being over-written mfd: max8997-irq: 'inline' should be at the beginning of the declaration mfd: rk808: Fix RK818_IRQ_DISCHG_ILIM initializer mfd: tps65217: Fix nonstandard declaration mfd: lp873x: Remove unused mutex lock from struct lp873x mfd: atmel-hlcdc: Do not sleep in atomic context mfd: exynos-lpass: Mark PM functions as __maybe_unused mfd: intel-lpss: Add default I2C device properties for Apollo Lake mfd: twl-core: Make it explicitly non-modular mfd: sun6i-prcm: Make it explicitly non-modular mfd: smsc-ece1099: Make it explicitly non-modular ...
Diffstat (limited to 'drivers/mfd/tps65217.c')
-rw-r--r--drivers/mfd/tps65217.c205
1 files changed, 197 insertions, 8 deletions
diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c
index 049a6fc..9a4d868 100644
--- a/drivers/mfd/tps65217.c
+++ b/drivers/mfd/tps65217.c
@@ -15,22 +15,103 @@
* 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/err.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/i2c.h>
-#include <linux/slab.h>
-#include <linux/regmap.h>
-#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
#include <linux/mfd/core.h>
#include <linux/mfd/tps65217.h>
-static const struct mfd_cell tps65217s[] = {
+static struct resource charger_resources[] = {
+ DEFINE_RES_IRQ_NAMED(TPS65217_IRQ_AC, "AC"),
+ DEFINE_RES_IRQ_NAMED(TPS65217_IRQ_USB, "USB"),
+};
+
+static struct resource pb_resources[] = {
+ DEFINE_RES_IRQ_NAMED(TPS65217_IRQ_PB, "PB"),
+};
+
+struct tps65217_irq {
+ int mask;
+ int interrupt;
+};
+
+static const struct tps65217_irq tps65217_irqs[] = {
+ [TPS65217_IRQ_PB] = {
+ .mask = TPS65217_INT_PBM,
+ .interrupt = TPS65217_INT_PBI,
+ },
+ [TPS65217_IRQ_AC] = {
+ .mask = TPS65217_INT_ACM,
+ .interrupt = TPS65217_INT_ACI,
+ },
+ [TPS65217_IRQ_USB] = {
+ .mask = TPS65217_INT_USBM,
+ .interrupt = TPS65217_INT_USBI,
+ },
+};
+
+static void tps65217_irq_lock(struct irq_data *data)
+{
+ struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&tps->irq_lock);
+}
+
+static void tps65217_irq_sync_unlock(struct irq_data *data)
+{
+ struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+ int ret;
+
+ ret = tps65217_reg_write(tps, TPS65217_REG_INT, tps->irq_mask,
+ TPS65217_PROTECT_NONE);
+ if (ret != 0)
+ dev_err(tps->dev, "Failed to sync IRQ masks\n");
+
+ mutex_unlock(&tps->irq_lock);
+}
+
+static inline const struct tps65217_irq *
+irq_to_tps65217_irq(struct tps65217 *tps, struct irq_data *data)
+{
+ return &tps65217_irqs[data->hwirq];
+}
+
+static void tps65217_irq_enable(struct irq_data *data)
+{
+ struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+ const struct tps65217_irq *irq_data = irq_to_tps65217_irq(tps, data);
+
+ tps->irq_mask &= ~irq_data->mask;
+}
+
+static void tps65217_irq_disable(struct irq_data *data)
+{
+ struct tps65217 *tps = irq_data_get_irq_chip_data(data);
+ const struct tps65217_irq *irq_data = irq_to_tps65217_irq(tps, data);
+
+ tps->irq_mask |= irq_data->mask;
+}
+
+static struct irq_chip tps65217_irq_chip = {
+ .irq_bus_lock = tps65217_irq_lock,
+ .irq_bus_sync_unlock = tps65217_irq_sync_unlock,
+ .irq_enable = tps65217_irq_enable,
+ .irq_disable = tps65217_irq_disable,
+};
+
+static struct mfd_cell tps65217s[] = {
{
.name = "tps65217-pmic",
.of_compatible = "ti,tps65217-pmic",
@@ -41,10 +122,96 @@ static const struct mfd_cell tps65217s[] = {
},
{
.name = "tps65217-charger",
+ .num_resources = ARRAY_SIZE(charger_resources),
+ .resources = charger_resources,
.of_compatible = "ti,tps65217-charger",
},
+ {
+ .name = "tps65217-pwrbutton",
+ .num_resources = ARRAY_SIZE(pb_resources),
+ .resources = pb_resources,
+ .of_compatible = "ti,tps65217-pwrbutton",
+ },
+};
+
+static irqreturn_t tps65217_irq_thread(int irq, void *data)
+{
+ struct tps65217 *tps = data;
+ unsigned int status;
+ bool handled = false;
+ int i;
+ int ret;
+
+ ret = tps65217_reg_read(tps, TPS65217_REG_INT, &status);
+ if (ret < 0) {
+ dev_err(tps->dev, "Failed to read IRQ status: %d\n",
+ ret);
+ return IRQ_NONE;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tps65217_irqs); i++) {
+ if (status & tps65217_irqs[i].interrupt) {
+ handle_nested_irq(irq_find_mapping(tps->irq_domain, i));
+ handled = true;
+ }
+ }
+
+ if (handled)
+ return IRQ_HANDLED;
+
+ return IRQ_NONE;
+}
+
+static int tps65217_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct tps65217 *tps = h->host_data;
+
+ irq_set_chip_data(virq, tps);
+ irq_set_chip_and_handler(virq, &tps65217_irq_chip, handle_edge_irq);
+ irq_set_nested_thread(virq, 1);
+ irq_set_parent(virq, tps->irq);
+ irq_set_noprobe(virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops tps65217_irq_domain_ops = {
+ .map = tps65217_irq_map,
};
+static int tps65217_irq_init(struct tps65217 *tps, int irq)
+{
+ int ret;
+
+ mutex_init(&tps->irq_lock);
+ tps->irq = irq;
+
+ /* Mask all interrupt sources */
+ tps->irq_mask = (TPS65217_INT_RESERVEDM | TPS65217_INT_PBM
+ | TPS65217_INT_ACM | TPS65217_INT_USBM);
+ tps65217_reg_write(tps, TPS65217_REG_INT, tps->irq_mask,
+ TPS65217_PROTECT_NONE);
+
+ tps->irq_domain = irq_domain_add_linear(tps->dev->of_node,
+ TPS65217_NUM_IRQ, &tps65217_irq_domain_ops, tps);
+ if (!tps->irq_domain) {
+ dev_err(tps->dev, "Could not create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+ ret = devm_request_threaded_irq(tps->dev, irq, NULL,
+ tps65217_irq_thread, IRQF_ONESHOT,
+ "tps65217-irq", tps);
+ if (ret) {
+ dev_err(tps->dev, "Failed to request IRQ %d: %d\n",
+ irq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
/**
* tps65217_reg_read: Read a single tps65217 register.
*
@@ -149,11 +316,22 @@ int tps65217_clear_bits(struct tps65217 *tps, unsigned int reg,
}
EXPORT_SYMBOL_GPL(tps65217_clear_bits);
+static bool tps65217_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TPS65217_REG_INT:
+ return true;
+ default:
+ return false;
+ }
+}
+
static const struct regmap_config tps65217_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = TPS65217_REG_MAX,
+ .volatile_reg = tps65217_volatile_reg,
};
static const struct of_device_id tps65217_of_match[] = {
@@ -205,8 +383,19 @@ static int tps65217_probe(struct i2c_client *client,
return ret;
}
+ if (client->irq) {
+ tps65217_irq_init(tps, client->irq);
+ } else {
+ int i;
+
+ /* Don't tell children about IRQ resources which won't fire */
+ for (i = 0; i < ARRAY_SIZE(tps65217s); i++)
+ tps65217s[i].num_resources = 0;
+ }
+
ret = devm_mfd_add_devices(tps->dev, -1, tps65217s,
- ARRAY_SIZE(tps65217s), NULL, 0, NULL);
+ ARRAY_SIZE(tps65217s), NULL, 0,
+ tps->irq_domain);
if (ret < 0) {
dev_err(tps->dev, "mfd_add_devices failed: %d\n", ret);
return ret;