702 lines
18 KiB
Diff
702 lines
18 KiB
Diff
From 2bfa63340e722d730513c64fd5ad7b9c5965d6eb Mon Sep 17 00:00:00 2001
|
|
From: Sean Cross <xobs@kosagi.com>
|
|
Date: Thu, 23 Oct 2014 21:10:44 +0800
|
|
Subject: [PATCH 05/65] drivers: mfd: senoko: Add Senoko device
|
|
|
|
Senoko is the power supply board present on some Kosagi boards. It
|
|
acts as a combination RTC, power supply, keyboard, and GPIO board. This
|
|
driver implements some of these features.
|
|
|
|
Signed-off-by: Sean Cross <xobs@kosagi.com>
|
|
---
|
|
drivers/mfd/Kconfig | 15 ++
|
|
drivers/mfd/Makefile | 1 +
|
|
drivers/mfd/senoko.c | 637 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
3 files changed, 653 insertions(+)
|
|
create mode 100644 drivers/mfd/senoko.c
|
|
|
|
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
|
|
index 4d92df6..d0f8fef 100644
|
|
--- a/drivers/mfd/Kconfig
|
|
+++ b/drivers/mfd/Kconfig
|
|
@@ -941,6 +941,21 @@ config MFD_DB8500_PRCMU
|
|
system controller running an XP70 microprocessor, which is accessed
|
|
through a register map.
|
|
|
|
+config MFD_SENOKO
|
|
+ tristate "Kosagi Senoko battery board"
|
|
+ depends on I2C=y
|
|
+ depends on OF
|
|
+ select MFD_CORE
|
|
+ help
|
|
+ Support for the Kosagi Senoko battery board, which acts as a
|
|
+ GPIO port expander, power supply, and RTC.
|
|
+
|
|
+ Currently available sub drivers are:
|
|
+
|
|
+ GPIO: senoko-gpio
|
|
+ Keypad: senoko-keypad
|
|
+ Regulator: senoko-ts
|
|
+
|
|
config MFD_STMPE
|
|
bool "STMicroelectronics STMPE"
|
|
depends on (I2C=y || SPI_MASTER=y)
|
|
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
|
|
index a8b76b8..fc19354 100644
|
|
--- a/drivers/mfd/Makefile
|
|
+++ b/drivers/mfd/Makefile
|
|
@@ -25,6 +25,7 @@ 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_SENOKO) += senoko.o
|
|
obj-$(CONFIG_MFD_STA2X11) += sta2x11-mfd.o
|
|
obj-$(CONFIG_MFD_STMPE) += stmpe.o
|
|
obj-$(CONFIG_STMPE_I2C) += stmpe-i2c.o
|
|
diff --git a/drivers/mfd/senoko.c b/drivers/mfd/senoko.c
|
|
new file mode 100644
|
|
index 0000000..7460473
|
|
--- /dev/null
|
|
+++ b/drivers/mfd/senoko.c
|
|
@@ -0,0 +1,637 @@
|
|
+/*
|
|
+ * Kosagi Senoko battery board
|
|
+ *
|
|
+ * Copyright 2015 Sutajio Ko-Usagi PTE Ltd.
|
|
+ *
|
|
+ * Licensed under the GPL-2 or later.
|
|
+ */
|
|
+
|
|
+#include <linux/delay.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/mfd/core.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/workqueue.h>
|
|
+
|
|
+#define BACKOFF_MS 800
|
|
+
|
|
+/*
|
|
+ * IRQ 0: GPIO
|
|
+ * IRQ 1: Keypad
|
|
+ * IRQ 2: Power supply
|
|
+ * IRQ 3: Wake alarm
|
|
+ */
|
|
+enum senoko_irq {
|
|
+ senoko_gpio_irq = 0,
|
|
+ senoko_keypad_irq = 1,
|
|
+ senoko_senoko_power_supply_irq = 2,
|
|
+ senoko_rtc_irq = 3,
|
|
+ __senoko_num_irqs = 4,
|
|
+};
|
|
+
|
|
+struct i2c_registers {
|
|
+ uint8_t signature; /* 0x00 */
|
|
+ uint8_t version_major; /* 0x01 */
|
|
+ uint8_t version_minor; /* 0x02 */
|
|
+ uint8_t features; /* 0x03 */
|
|
+ uint8_t uptime[4]; /* 0x04 - 0x07 */
|
|
+ uint8_t irq_enable; /* 0x08 */
|
|
+ uint8_t irq_status; /* 0x09 */
|
|
+ uint8_t padding0[5]; /* 0x0a - 0x0e */
|
|
+ uint8_t power; /* 0x0f */
|
|
+
|
|
+ /* -- GPIO block -- */
|
|
+ uint8_t gpio_dir_a; /* 0x10 */
|
|
+ uint8_t gpio_dir_b; /* 0x11 */
|
|
+ uint8_t gpio_val_a; /* 0x12 */
|
|
+ uint8_t gpio_val_b; /* 0x13 */
|
|
+ uint8_t gpio_irq_rise_a; /* 0x14 */
|
|
+ uint8_t gpio_irq_rise_b; /* 0x15 */
|
|
+ uint8_t gpio_irq_fall_a; /* 0x16 */
|
|
+ uint8_t gpio_irq_fall_b; /* 0x17 */
|
|
+ uint8_t gpio_irq_stat_a; /* 0x18 */
|
|
+ uint8_t gpio_irq_stat_b; /* 0x19 */
|
|
+ uint8_t gpio_pull_ena_a; /* 0x1a */
|
|
+ uint8_t gpio_pull_ena_b; /* 0x1b */
|
|
+ uint8_t gpio_pull_dir_a; /* 0x1c */
|
|
+ uint8_t gpio_pull_dir_b; /* 0x1d */
|
|
+ uint8_t padding1[2]; /* 0x1e - 0x1f */
|
|
+
|
|
+ /* -- RTC block -- */
|
|
+ uint8_t seconds[4]; /* 0x20 - 0x23 */
|
|
+ uint8_t alarm_seconds[4]; /* 0x24 - 0x27 */
|
|
+ uint8_t wdt_seconds; /* 0x28 */
|
|
+};
|
|
+
|
|
+struct senoko {
|
|
+ struct i2c_client *client;
|
|
+ struct device *dev;
|
|
+ struct irq_domain *domain;
|
|
+
|
|
+ /* protect serialized access to the interrupt controller bus */
|
|
+ struct mutex irq_lock;
|
|
+
|
|
+ int num_irqs;
|
|
+ int irq_gpio;
|
|
+ enum of_gpio_flags irq_trigger;
|
|
+ int irq;
|
|
+ struct delayed_work reenable_work;
|
|
+
|
|
+ /* IRQ Enable Register */
|
|
+ int ier;
|
|
+ int oldier;
|
|
+
|
|
+ /* IRQ Wake Register, which IRQs are active during suspend */
|
|
+ int iwr;
|
|
+
|
|
+ /* A list of reported features */
|
|
+ int features;
|
|
+
|
|
+ struct regmap *regmap;
|
|
+
|
|
+ /* Previous pm_power_off function */
|
|
+ void (*old_power_off)(void);
|
|
+};
|
|
+
|
|
+static struct senoko *g_senoko;
|
|
+
|
|
+#define REG_SIGNATURE 0x00
|
|
+#define REG_VERSION_MAJOR 0x01
|
|
+#define REG_VERSION_MINOR 0x02
|
|
+#define REG_FEATURES 0x03
|
|
+#define REG_FEATURES_BATTERY (1 << 0)
|
|
+#define REG_FEATURES_GPIO (1 << 1)
|
|
+
|
|
+#define REG_IRQ_ENABLE 0x08
|
|
+#define REG_IRQ_STATUS 0x09
|
|
+#define REG_IRQ_GPIO_MASK (1 << 0)
|
|
+#define REG_IRQ_KEYPAD_MASK (1 << 1)
|
|
+#define REG_IRQ_POWER_MASK (1 << 2)
|
|
+#define REG_IRQ_ALARM_MASK (1 << 3)
|
|
+
|
|
+#define REG_POWER 0x0f
|
|
+#define REG_POWER_STATE_MASK (3 << 0)
|
|
+#define REG_POWER_STATE_ON (0 << 0)
|
|
+#define REG_POWER_STATE_OFF (1 << 0)
|
|
+#define REG_POWER_STATE_REBOOT (2 << 0)
|
|
+#define REG_POWER_WDT_MASK (1 << 2)
|
|
+#define REG_POWER_WDT_DISABLE (0 << 2)
|
|
+#define REG_POWER_WDT_ENABLE (1 << 2)
|
|
+#define REG_POWER_WDT_STATE (1 << 2)
|
|
+#define REG_POWER_AC_STATUS_MASK (1 << 3)
|
|
+#define REG_POWER_AC_STATUS_SHIFT (3)
|
|
+#define REG_POWER_PB_STATUS_MASK (1 << 4)
|
|
+#define REG_POWER_PB_STATUS_SHIFT (4)
|
|
+#define REG_POWER_KEY_MASK (3 << 6)
|
|
+#define REG_POWER_KEY_READ (1 << 6)
|
|
+#define REG_POWER_KEY_WRITE (2 << 6)
|
|
+
|
|
+#define REG_WATCHDOG_SECONDS 0x28
|
|
+
|
|
+static bool senoko_regmap_is_volatile(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case REG_SIGNATURE:
|
|
+ case REG_VERSION_MAJOR:
|
|
+ case REG_VERSION_MINOR:
|
|
+ case REG_FEATURES:
|
|
+ return false;
|
|
+ default:
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool senoko_regmap_is_writeable(struct device *dev, unsigned int reg)
|
|
+{
|
|
+ switch (reg) {
|
|
+ case REG_IRQ_ENABLE:
|
|
+ case REG_IRQ_STATUS:
|
|
+ case REG_POWER:
|
|
+ case REG_WATCHDOG_SECONDS:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct regmap_config senoko_regmap_config = {
|
|
+ .reg_bits = 8,
|
|
+ .val_bits = 8,
|
|
+ .cache_type = REGCACHE_RBTREE,
|
|
+ .max_register = REG_WATCHDOG_SECONDS,
|
|
+ .writeable_reg = senoko_regmap_is_writeable,
|
|
+ .volatile_reg = senoko_regmap_is_volatile,
|
|
+};
|
|
+
|
|
+static struct resource senoko_gpio_resources[] = {
|
|
+ /* Start and end filled dynamically */
|
|
+ {
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct mfd_cell senoko_gpio_cell = {
|
|
+ .name = "senoko-gpio",
|
|
+ .of_compatible = "kosagi,senoko-gpio",
|
|
+ .resources = senoko_gpio_resources,
|
|
+ .num_resources = ARRAY_SIZE(senoko_gpio_resources),
|
|
+};
|
|
+
|
|
+static struct resource senoko_keypad_resources[] = {
|
|
+ /* Start and end filled dynamically */
|
|
+ {
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct mfd_cell senoko_keypad_cell = {
|
|
+ .name = "senoko-keypad",
|
|
+ .of_compatible = "kosagi,senoko-keypad",
|
|
+ .resources = senoko_keypad_resources,
|
|
+ .num_resources = ARRAY_SIZE(senoko_keypad_resources),
|
|
+};
|
|
+
|
|
+static struct resource senoko_senoko_power_supply_resources[] = {
|
|
+ /* Start and end filled dynamically */
|
|
+ {
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct mfd_cell senoko_senoko_power_supply_cell = {
|
|
+ .name = "senoko-power-supply",
|
|
+ .of_compatible = "kosagi,senoko-power-supply",
|
|
+ .resources = senoko_senoko_power_supply_resources,
|
|
+ .num_resources = ARRAY_SIZE(senoko_senoko_power_supply_resources),
|
|
+};
|
|
+
|
|
+static struct resource senoko_rtc_resources[] = {
|
|
+ /* Start and end filled dynamically */
|
|
+ {
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static const struct mfd_cell senoko_rtc_cell = {
|
|
+ .name = "senoko-rtc",
|
|
+ .of_compatible = "kosagi,senoko-rtc",
|
|
+ .resources = senoko_rtc_resources,
|
|
+ .num_resources = ARRAY_SIZE(senoko_rtc_resources),
|
|
+};
|
|
+
|
|
+int senoko_read(struct senoko *senoko, int offset)
|
|
+{
|
|
+ unsigned int value;
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_read(senoko->regmap, offset, &value);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return value;
|
|
+}
|
|
+EXPORT_SYMBOL(senoko_read);
|
|
+
|
|
+int senoko_write(struct senoko *senoko, int offset, u8 value)
|
|
+{
|
|
+ return regmap_write(senoko->regmap, offset, value);
|
|
+}
|
|
+EXPORT_SYMBOL(senoko_write);
|
|
+
|
|
+static void senoko_supply_power_off(void)
|
|
+{
|
|
+ struct senoko *senoko = g_senoko;
|
|
+ int err;
|
|
+
|
|
+ dev_info(senoko->dev, "shutting down\n");
|
|
+ regcache_cache_bypass(senoko->regmap, true);
|
|
+
|
|
+ while (1) {
|
|
+ err = senoko_write(senoko, REG_POWER,
|
|
+ REG_POWER_STATE_OFF | REG_POWER_KEY_WRITE);
|
|
+ /* Board should be off now */
|
|
+ }
|
|
+}
|
|
+
|
|
+static void senoko_irq_reenable(struct work_struct *work)
|
|
+{
|
|
+ struct senoko *senoko = container_of(work, struct senoko,
|
|
+ reenable_work.work);
|
|
+ dev_dbg(senoko->dev, "Re-enabling IRQ\n");
|
|
+ enable_irq(senoko->irq);
|
|
+}
|
|
+
|
|
+static irqreturn_t senoko_irq_handler(int irq_num, void *devid)
|
|
+{
|
|
+ struct senoko *senoko = devid;
|
|
+ unsigned irq;
|
|
+ int status;
|
|
+
|
|
+ status = senoko_read(senoko, REG_IRQ_STATUS);
|
|
+
|
|
+ if (status < 0) {
|
|
+ /*
|
|
+ * This happens when we reflash Senoko, so ignore
|
|
+ * this condition.
|
|
+ */
|
|
+ dev_err(senoko->dev,
|
|
+ "Error when reading IRQ status: %d\n", status);
|
|
+ disable_irq_nosync(irq_num);
|
|
+ schedule_delayed_work(&senoko->reenable_work,
|
|
+ msecs_to_jiffies(BACKOFF_MS));
|
|
+ return IRQ_HANDLED;
|
|
+ }
|
|
+
|
|
+ dev_dbg(senoko->dev, "IRQ status: %d\n", status);
|
|
+
|
|
+ for (irq = 0; irq < senoko->num_irqs; irq++) {
|
|
+ if (status & (1 << irq)) {
|
|
+ int child_irq = irq_create_mapping(senoko->domain, irq);
|
|
+ handle_nested_irq(child_irq);
|
|
+ status &= ~(1 << irq);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ senoko_write(senoko, REG_IRQ_STATUS, status);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void senoko_irq_lock(struct irq_data *data)
|
|
+{
|
|
+ struct senoko *senoko = irq_data_get_irq_chip_data(data);
|
|
+
|
|
+ mutex_lock(&senoko->irq_lock);
|
|
+}
|
|
+
|
|
+static void senoko_irq_sync_unlock(struct irq_data *data)
|
|
+{
|
|
+ struct senoko *senoko = irq_data_get_irq_chip_data(data);
|
|
+
|
|
+ u8 new = senoko->ier;
|
|
+ u8 old = senoko->oldier;
|
|
+
|
|
+ if (new != old) {
|
|
+ senoko->oldier = new;
|
|
+ senoko_write(senoko, REG_IRQ_ENABLE, new);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&senoko->irq_lock);
|
|
+}
|
|
+
|
|
+static void senoko_irq_mask(struct irq_data *data)
|
|
+{
|
|
+ struct senoko *senoko = irq_data_get_irq_chip_data(data);
|
|
+ int offset = data->hwirq;
|
|
+ int mask = 1 << (offset % 8);
|
|
+
|
|
+ senoko->ier &= ~mask;
|
|
+}
|
|
+
|
|
+static void senoko_irq_unmask(struct irq_data *data)
|
|
+{
|
|
+ struct senoko *senoko = irq_data_get_irq_chip_data(data);
|
|
+ int offset = data->hwirq;
|
|
+ int mask = 1 << (offset % 8);
|
|
+
|
|
+ senoko->ier |= mask;
|
|
+}
|
|
+
|
|
+int senoko_irq_set_wake(struct irq_data *data, unsigned int on)
|
|
+{
|
|
+ struct senoko *senoko = irq_data_get_irq_chip_data(data);
|
|
+ int offset = data->hwirq;
|
|
+ int mask = 1 << (offset % 8);
|
|
+
|
|
+ if (on)
|
|
+ senoko->iwr |= mask;
|
|
+ else
|
|
+ senoko->iwr &= ~mask;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void senoko_irq_ack(struct irq_data *data)
|
|
+{
|
|
+}
|
|
+
|
|
+static struct irq_chip senoko_irq_chip = {
|
|
+ .name = "senoko",
|
|
+ .irq_bus_lock = senoko_irq_lock,
|
|
+ .irq_bus_sync_unlock = senoko_irq_sync_unlock,
|
|
+ .irq_mask = senoko_irq_mask,
|
|
+ .irq_set_wake = senoko_irq_set_wake,
|
|
+ .irq_unmask = senoko_irq_unmask,
|
|
+ .irq_ack = senoko_irq_ack,
|
|
+};
|
|
+
|
|
+static int senoko_irq_map(struct irq_domain *d, unsigned int virq,
|
|
+ irq_hw_number_t hwirq)
|
|
+{
|
|
+ struct senoko *senoko = d->host_data;
|
|
+ struct irq_chip *chip = NULL;
|
|
+
|
|
+ chip = &senoko_irq_chip;
|
|
+
|
|
+ irq_set_chip_data(virq, senoko);
|
|
+ irq_set_chip_and_handler(virq, chip, handle_edge_irq);
|
|
+ irq_set_nested_thread(virq, 1);
|
|
+ irq_set_noprobe(virq);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void senoko_irq_unmap(struct irq_domain *d, unsigned int virq)
|
|
+{
|
|
+ irq_set_chip_and_handler(virq, NULL, NULL);
|
|
+ irq_set_chip_data(virq, NULL);
|
|
+}
|
|
+
|
|
+static struct irq_domain_ops senoko_irq_ops = {
|
|
+ .map = senoko_irq_map,
|
|
+ .unmap = senoko_irq_unmap,
|
|
+ .xlate = irq_domain_xlate_twocell,
|
|
+};
|
|
+
|
|
+static int senoko_irq_init(struct senoko *senoko, struct device_node *np)
|
|
+{
|
|
+ int base = 0;
|
|
+ int num_irqs = senoko->num_irqs;
|
|
+
|
|
+ INIT_DELAYED_WORK(&senoko->reenable_work, senoko_irq_reenable);
|
|
+
|
|
+ senoko->domain = irq_domain_add_simple(np, num_irqs, base,
|
|
+ &senoko_irq_ops, senoko);
|
|
+ if (!senoko->domain) {
|
|
+ dev_err(senoko->dev, "Failed to create irqdomain\n");
|
|
+ return -ENOSYS;
|
|
+ }
|
|
+
|
|
+ /* Pre-acknowledge all IRQs */
|
|
+ senoko_write(senoko, REG_IRQ_STATUS, 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void senoko_resource_irq_update(const struct mfd_cell *cell, int irq)
|
|
+{
|
|
+ int j;
|
|
+ for (j = 0; j < cell->num_resources; j++) {
|
|
+ struct resource *res = (struct resource *) &cell->resources[j];
|
|
+
|
|
+ /* Dynamically fill in a block's IRQ. */
|
|
+ if (res->flags & IORESOURCE_IRQ)
|
|
+ res->start = res->end = irq + j;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int senoko_devices_init(struct senoko *senoko)
|
|
+{
|
|
+ int ret = -EINVAL;
|
|
+ int blocknum = 0;
|
|
+
|
|
+ senoko_resource_irq_update(&senoko_gpio_cell, senoko_gpio_irq);
|
|
+ ret = mfd_add_devices(senoko->dev, blocknum++,
|
|
+ &senoko_gpio_cell, 1, NULL, 0, senoko->domain);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ senoko_resource_irq_update(&senoko_keypad_cell, senoko_keypad_irq);
|
|
+ ret = mfd_add_devices(senoko->dev, blocknum++,
|
|
+ &senoko_keypad_cell, 1, NULL, 0, senoko->domain);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ senoko_resource_irq_update(&senoko_senoko_power_supply_cell,
|
|
+ senoko_senoko_power_supply_irq);
|
|
+ ret = mfd_add_devices(senoko->dev, blocknum++,
|
|
+ &senoko_senoko_power_supply_cell,
|
|
+ 1, NULL, 0, senoko->domain);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ senoko_resource_irq_update(&senoko_rtc_cell, senoko_rtc_irq);
|
|
+ ret = mfd_add_devices(senoko->dev, blocknum++,
|
|
+ &senoko_rtc_cell, 1, NULL, 0, senoko->domain);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int senoko_probe(struct i2c_client *client,
|
|
+ const struct i2c_device_id *id)
|
|
+{
|
|
+ struct senoko *senoko;
|
|
+ struct device_node *np = client->dev.of_node;
|
|
+ int ret;
|
|
+ int signature, ver_major, ver_minor;
|
|
+
|
|
+ senoko = devm_kzalloc(&client->dev, sizeof(*senoko), GFP_KERNEL);
|
|
+ if (senoko == NULL)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ dev_set_drvdata(&client->dev, senoko);
|
|
+
|
|
+ mutex_init(&senoko->irq_lock);
|
|
+
|
|
+ senoko->client = client;
|
|
+ senoko->num_irqs = __senoko_num_irqs;
|
|
+ senoko->dev = &client->dev;
|
|
+
|
|
+ i2c_set_clientdata(client, senoko);
|
|
+
|
|
+ senoko->regmap = devm_regmap_init_i2c(client, &senoko_regmap_config);
|
|
+ if (IS_ERR(senoko->regmap)) {
|
|
+ dev_err(senoko->dev, "unable to allocate register map: %ld\n",
|
|
+ PTR_ERR(senoko->regmap));
|
|
+ return PTR_ERR(senoko->regmap);
|
|
+ }
|
|
+
|
|
+ senoko->features = senoko_read(senoko, REG_FEATURES);
|
|
+ if (senoko->features < 0) {
|
|
+ dev_err(senoko->dev, "unable to read features: %d\n",
|
|
+ senoko->features);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ signature = senoko_read(senoko, REG_SIGNATURE);
|
|
+ if (signature < 0) {
|
|
+ dev_err(senoko->dev, "unable to read signature: %d\n",
|
|
+ signature);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ ver_major = senoko_read(senoko, REG_VERSION_MAJOR);
|
|
+ if (ver_major < 0) {
|
|
+ dev_err(senoko->dev, "unable to read major version: %d\n",
|
|
+ ver_major);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ ver_minor = senoko_read(senoko, REG_VERSION_MINOR);
|
|
+ if (ver_minor < 0) {
|
|
+ dev_err(senoko->dev, "unable to read minor version: %d\n",
|
|
+ ver_minor);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ dev_info(senoko->dev, "senoko '%c' version %d.%d (features: 0x%02x)\n",
|
|
+ signature, ver_major, ver_minor, senoko->features);
|
|
+
|
|
+ senoko->irq_gpio = of_get_named_gpio_flags(np, "irq-gpio", 0,
|
|
+ &senoko->irq_trigger);
|
|
+ dev_dbg(senoko->dev, "GPIO IRQ: %d\n", senoko->irq_gpio);
|
|
+ dev_dbg(senoko->dev, "GPIO IRQ trigger: %x\n", senoko->irq_trigger);
|
|
+ ret = devm_gpio_request_one(&client->dev, senoko->irq_gpio,
|
|
+ GPIOF_DIR_IN, "senoko");
|
|
+ if (ret) {
|
|
+ dev_err(senoko->dev, "failed to request IRQ GPIO: %d\n", ret);
|
|
+ goto err_free;
|
|
+ }
|
|
+ senoko->irq = gpio_to_irq(senoko->irq_gpio);
|
|
+
|
|
+ senoko_irq_init(senoko, np);
|
|
+ ret = devm_request_threaded_irq(senoko->dev, senoko->irq, NULL,
|
|
+ senoko_irq_handler, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
|
+ "senoko", senoko);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "unable to get irq: %d\n", ret);
|
|
+ goto err_domain;
|
|
+ }
|
|
+
|
|
+ ret = senoko_devices_init(senoko);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "unable to add devices: %d\n", ret);
|
|
+ goto remove_devices;
|
|
+ }
|
|
+
|
|
+ senoko->old_power_off = pm_power_off;
|
|
+ g_senoko = senoko;
|
|
+ pm_power_off = senoko_supply_power_off;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+remove_devices:
|
|
+ mfd_remove_devices(senoko->dev);
|
|
+
|
|
+err_domain:
|
|
+ irq_domain_remove(senoko->domain);
|
|
+
|
|
+err_free:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int senoko_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct senoko *senoko = i2c_get_clientdata(client);
|
|
+
|
|
+ cancel_delayed_work_sync(&senoko->reenable_work);
|
|
+ pm_power_off = senoko->old_power_off;
|
|
+ mfd_remove_devices(senoko->dev);
|
|
+ irq_domain_remove(senoko->domain);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int senoko_suspend(struct device *dev)
|
|
+{
|
|
+ struct senoko *senoko = dev_get_drvdata(dev);
|
|
+
|
|
+ cancel_delayed_work_sync(&senoko->reenable_work);
|
|
+ if (senoko->iwr)
|
|
+ enable_irq_wake(senoko->irq);
|
|
+ senoko_write(senoko, REG_IRQ_ENABLE, senoko->iwr);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int senoko_resume(struct device *dev)
|
|
+{
|
|
+ struct senoko *senoko = dev_get_drvdata(dev);
|
|
+
|
|
+ if (senoko->iwr)
|
|
+ disable_irq_wake(senoko->irq);
|
|
+ senoko_write(senoko, REG_IRQ_ENABLE, senoko->ier);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static const struct of_device_id senoko_of_match[] = {
|
|
+ { .compatible = "kosagi,senoko", },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, senoko_of_match);
|
|
+
|
|
+static const struct i2c_device_id senoko_id[] = {
|
|
+ {"senoko", 0},
|
|
+ {}
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(i2c, senoko_id);
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(senoko_pm_ops, senoko_suspend, senoko_resume);
|
|
+
|
|
+static struct i2c_driver senoko_driver = {
|
|
+ .driver = {
|
|
+ .name = "senoko",
|
|
+ .of_match_table = senoko_of_match,
|
|
+ .pm = &senoko_pm_ops,
|
|
+ },
|
|
+ .probe = senoko_probe,
|
|
+ .remove = senoko_remove,
|
|
+ .id_table = senoko_id,
|
|
+};
|
|
+
|
|
+module_i2c_driver(senoko_driver);
|
|
+
|
|
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
|
|
+MODULE_DESCRIPTION("Senoko MFD Driver");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.7.3
|
|
|