[PATCH V2 2/4] mfd: Interrupt handling support for AIC family

From: Mehar Bajwa
Date: Tue Apr 16 2013 - 09:44:34 EST


This provides Interrupt handling features for common interface
to series of low power AIC audio CODECS.

Signed-off-by: Mehar Bajwa <mehar.bajwa@xxxxxx>
---
drivers/mfd/Kconfig | 13 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/tlv320aic-irq.c | 234 +++++++++++++++++++++++++++++++
include/linux/mfd/tlv320aic-core.h | 49 ++++++-
include/linux/mfd/tlv320aic-registers.h | 57 ++++++++
5 files changed, 348 insertions(+), 6 deletions(-)
create mode 100644 drivers/mfd/tlv320aic-irq.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 629d374..3019897 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -58,6 +58,19 @@ config MFD_AIC
you have to select individual components like codec device
to use AIC features.

+menu "AIC Interface Drivers"
+ depends on MFD_AIC
+
+config MFD_AIC_IRQ
+ bool "Support of IRQ for AIC"
+ depends on MFD_AIC
+ help
+ Say yes here if you want support of IRQ for Texas Instruments
+ AIC codec family.
+ You have to select individual components like codec device
+ under the corresponding menus.
+endmenu
+
config MFD_SM501
tristate "Support for Silicon Motion SM501"
---help---
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b975c94..3b39454 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o
obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o
obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
obj-$(CONFIG_MFD_AIC) += tlv320aic-core.o
+obj-$(CONFIG_MFD_AIC_IRQ) += tlv320aic-irq.o
obj-$(CONFIG_MFD_SM501) += sm501.o
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o

diff --git a/drivers/mfd/tlv320aic-irq.c b/drivers/mfd/tlv320aic-irq.c
new file mode 100644
index 0000000..e299495
--- /dev/null
+++ b/drivers/mfd/tlv320aic-irq.c
@@ -0,0 +1,234 @@
+/*
+ * tlv320aic-irq.c -- Interrupt controller support for
+ * TI TLV320AIC family
+ *
+ * Author: Mukund Navada <navada@xxxxxx>
+ * Mehar Bajwa <mehar.bajwa@xxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+
+#include <linux/mfd/tlv320aic-core.h>
+#include <linux/mfd/tlv320aic-registers.h>
+
+#include <linux/delay.h>
+
+struct aic_irq_data {
+ int mask;
+ int status;
+};
+
+static struct aic_irq_data aic_irqs[] = {
+ {
+ .mask = AIC_HEADSET_IN_M,
+ .status = AIC_HEADSET_PLUG_UNPLUG_INT,
+ },
+ {
+ .mask = AIC_BUTTON_PRESS_M,
+ .status = AIC_BUTTON_PRESS_INT,
+ },
+ {
+ .mask = AIC_DAC_DRC_THRES_M,
+ .status = AIC_LEFT_DRC_THRES_INT | AIC_RIGHT_DRC_THRES_INT,
+ },
+ {
+ .mask = AIC_AGC_NOISE_M,
+ .status = AIC_LEFT_AGC_NOISE_INT | AIC_RIGHT_AGC_NOISE_INT,
+ },
+ {
+ .mask = AIC_OVER_CURRENT_M,
+ .status = AIC_LEFT_OUTPUT_DRIVER_OVERCURRENT_INT
+ | AIC_RIGHT_OUTPUT_DRIVER_OVERCURRENT_INT,
+ },
+ {
+ .mask = AIC_OVERFLOW_M,
+ .status =
+ AIC_LEFT_DAC_OVERFLOW_INT | AIC_RIGHT_DAC_OVERFLOW_INT |
+ AIC_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT |
+ AIC_LEFT_ADC_OVERFLOW_INT | AIC_RIGHT_ADC_OVERFLOW_INT |
+ AIC_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT,
+ },
+ {
+ .mask = AIC_SPK_OVERCURRENT_M,
+ .status = AIC_SPK_OVER_CURRENT_INT,
+ },
+
+};
+
+static void aic_irq_lock(struct irq_data *data)
+{
+ struct aic *aic = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&aic->irq_lock);
+}
+
+static void aic_irq_sync_unlock(struct irq_data *data)
+{
+ struct aic *aic = irq_data_get_irq_chip_data(data);
+
+ /* write back to hardware any change in irq mask */
+ if (aic->irq_masks_cur != aic->irq_masks_cache) {
+ aic->irq_masks_cache = aic->irq_masks_cur;
+ aic_reg_write(aic, AIC_INT1_CNTL,
+ aic->irq_masks_cur);
+ }
+
+ mutex_unlock(&aic->irq_lock);
+}
+
+
+static void aic_irq_enable(struct irq_data *data)
+{
+ struct aic *aic = irq_data_get_irq_chip_data(data);
+ struct aic_irq_data *irq_data = &aic_irqs[data->hwirq];
+ aic->irq_masks_cur |= irq_data->mask;
+}
+
+static void aic_irq_disable(struct irq_data *data)
+{
+ struct aic *aic = irq_data_get_irq_chip_data(data);
+ struct aic_irq_data *irq_data = &aic_irqs[data->hwirq];
+
+ aic->irq_masks_cur &= ~irq_data->mask;
+}
+
+static struct irq_chip aic_irq_chip = {
+ .name = "tlv320aic",
+ .irq_bus_lock = aic_irq_lock,
+ .irq_bus_sync_unlock = aic_irq_sync_unlock,
+ .irq_enable = aic_irq_enable,
+ .irq_disable = aic_irq_disable
+};
+
+static irqreturn_t aic_irq_thread(int irq, void *data)
+{
+ struct aic *aic = data;
+ u8 status[4];
+
+ /* Reading sticky bit registers acknowledges
+ the interrupt to the device */
+ aic_bulk_read(aic, AIC_INT_STICKY_FLAG1, 4, status);
+
+ /* report */
+ if (status[2] & aic_irqs[AIC_IRQ_HEADSET_DETECT].status)
+ handle_nested_irq(aic->irq_base);
+ if (status[2] & aic_irqs[AIC_IRQ_BUTTON_PRESS].status)
+ handle_nested_irq(aic->irq_base + 1);
+ if (status[2] & aic_irqs[AIC_IRQ_DAC_DRC].status)
+ handle_nested_irq(aic->irq_base + 2);
+ if (status[3] & aic_irqs[AIC_IRQ_AGC_NOISE].status)
+ handle_nested_irq(aic->irq_base + 3);
+ if (status[2] & aic_irqs[AIC_IRQ_OVER_CURRENT].status)
+ handle_nested_irq(aic->irq_base + 4);
+ if (status[0] & aic_irqs[AIC_IRQ_OVERFLOW_EVENT].status)
+ handle_nested_irq(aic->irq_base + 5);
+ if (status[3] & aic_irqs[AIC_IRQ_SPEAKER_OVER_TEMP].status)
+ handle_nested_irq(aic->irq_base + 6);
+
+ return IRQ_HANDLED;
+}
+
+static int aic_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct aic *aic = h->host_data;
+
+ irq_set_chip_data(virq, aic);
+ irq_set_chip_and_handler(virq, &aic_irq_chip, handle_edge_irq);
+ irq_set_nested_thread(virq, 1);
+
+ /* ARM needs us to explicitly flag the IRQ as valid
+ * and will set them noprobe when we do so. */
+#ifdef CONFIG_ARM
+ set_irq_flags(virq, IRQF_VALID);
+#else
+ irq_set_noprobe(virq);
+#endif
+
+ return 0;
+}
+
+static const struct irq_domain_ops aic_domain_ops = {
+ .map = aic_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+int aic_irq_init(struct aic *aic)
+{
+ int ret;
+
+ mutex_init(&aic->irq_lock);
+
+ /* mask the individual interrupt sources */
+ aic->irq_masks_cur = 0x0;
+ aic->irq_masks_cache = 0x0;
+ aic_reg_write(aic, AIC_INT1_CNTL, 0x0);
+
+ if (!aic->irq) {
+ dev_warn(aic->dev, "no interrupt specified\n");
+ aic->irq_base = 0;
+ return 0;
+ }
+ if (aic->irq_base) {
+ aic->domain = irq_domain_add_legacy(aic->dev->of_node,
+ ARRAY_SIZE(aic_irqs),
+ aic->irq_base, 0,
+ &aic_domain_ops, aic);
+ } else {
+ aic->domain = irq_domain_add_linear(aic->dev->of_node,
+ ARRAY_SIZE(aic_irqs),
+ &aic_domain_ops, aic);
+ /* initiallizing irq_base from irq_domain*/
+ }
+ if (!aic->domain) {
+ dev_err(aic->dev, "Failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+ aic->irq_base = irq_create_mapping(aic->domain, 0);
+
+ ret = request_threaded_irq(aic->irq, NULL, aic_irq_thread,
+ IRQF_ONESHOT,
+ "tlv320aic", aic);
+ if (ret < 0) {
+ dev_err(aic->dev, "failed to request IRQ %d: %d\n",
+ aic->irq, ret);
+ return ret;
+ }
+ irq_set_irq_type(aic->irq, IRQF_TRIGGER_RISING);
+
+ return 0;
+}
+EXPORT_SYMBOL(aic_irq_init);
+
+void aic_irq_exit(struct aic *aic)
+{
+ if (aic->irq)
+ free_irq(aic->irq, aic);
+}
+EXPORT_SYMBOL(aic_irq_exit);
+MODULE_AUTHOR("Mukund navada <navada@xxxxxx>");
+MODULE_AUTHOR("Mehar Bajwa <mehar.bajwa@xxxxxx>");
+MODULE_DESCRIPTION("Interrupt controller support for TI TLV320AIC family");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/tlv320aic-core.h b/include/linux/mfd/tlv320aic-core.h
index 60d7146..66a46fb 100644
--- a/include/linux/mfd/tlv320aic-core.h
+++ b/include/linux/mfd/tlv320aic-core.h
@@ -23,12 +23,22 @@
#ifndef __MFD_AIC_CORE_H__
#define __MFD_AIC_CORE_H__

+#include <linux/interrupt.h>
#include <linux/mfd/core.h>
+#include <linux/irqdomain.h>

enum aic_type {
TLV320AIC3262 = 0,
};

+#define AIC_IRQ_HEADSET_DETECT 0
+#define AIC_IRQ_BUTTON_PRESS 1
+#define AIC_IRQ_DAC_DRC 2
+#define AIC_IRQ_AGC_NOISE 3
+#define AIC_IRQ_OVER_CURRENT 4
+#define AIC_IRQ_OVERFLOW_EVENT 5
+#define AIC_IRQ_SPEAKER_OVER_TEMP 6
+
union aic_reg_union {
struct aic_reg {
u8 offset;
@@ -93,19 +103,46 @@ struct aic {
u8 page_no;
};

+static inline int aic_request_irq(struct aic *aic, int irq,
+ irq_handler_t handler,
+ unsigned long irqflags, const char *name,
+ void *data)
+{
+ irq = irq_create_mapping(aic->domain, irq);
+ if (irq < 0) {
+ dev_err(aic->dev,
+ "Mapping hardware interrupt failed %d\n", irq);
+ return irq;
+ }
+
+ return request_threaded_irq(irq, NULL, handler,
+ irqflags, name, data);
+}
+
+static inline int aic_free_irq(struct aic *aic, int irq, void *data)
+{
+ if (!aic->irq_base)
+ return -EINVAL;
+
+ free_irq(aic->irq_base + irq, data);
+ return 0;
+}
+
/* Device I/O API */
int aic_reg_read(struct aic *aic, unsigned int reg);
int aic_reg_write(struct aic *aic, unsigned int reg,
- unsigned char val);
+ unsigned char val);
int aic_set_bits(struct aic *aic, unsigned int reg,
- unsigned char mask, unsigned char val);
+ unsigned char mask, unsigned char val);
int aic_bulk_read(struct aic *aic, unsigned int reg,
- int count, u8 *buf);
+ int count, u8 *buf);
int aic_bulk_write(struct aic *aic, unsigned int reg,
- int count, const u8 *buf);
+ int count, const u8 *buf);
int aic_wait_bits(struct aic *aic, unsigned int reg,
- unsigned char mask, unsigned char val, int delay,
- int counter);
+ unsigned char mask, unsigned char val, int delay,
+ int counter);
+int aic_irq_init(struct aic *aic);
+void aic_irq_exit(struct aic *aic);
int aic_device_init(struct aic *aic);
void aic_device_exit(struct aic *aic);

diff --git a/include/linux/mfd/tlv320aic-registers.h b/include/linux/mfd/tlv320aic-registers.h
index 8b56532..c940fae 100644
--- a/include/linux/mfd/tlv320aic-registers.h
+++ b/include/linux/mfd/tlv320aic-registers.h
@@ -28,5 +28,62 @@
offset)

#define AIC_RESET AIC_MAKE_REG(0, 0, 1)
+#define AIC_REV_PG_ID AIC_MAKE_REG(0, 0, 2)
+#define AIC_INT_STICKY_FLAG1 AIC_MAKE_REG(0, 0, 42)
+#define AIC_INT_STICKY_FLAG2 AIC_MAKE_REG(0, 0, 44)
+#define AIC_INT_STICKY_FLAG3 AIC_MAKE_REG(0, 0, 45)
+#define AIC_INT1_CNTL AIC_MAKE_REG(0, 0, 48)
+#define AIC_INT2_CNTL AIC_MAKE_REG(0, 0, 49)
+#define AIC_INT_FMT AIC_MAKE_REG(0, 0, 51)
#define AIC_DEVICE_ID AIC_MAKE_REG(0, 0, 125)
+
+/*
+ * B0_P0_R2 (0x000002) â?? Revision ID register.
+ */
+#define AIC_REV_M (0b01110000)
+#define AIC_REV_S (0b00000100)
+#define AIC_PG_M (0b00000111)
+#define AIC_PG_S (0b00000000)
+/*
+ * B0_P0_R42 (0x00002a) â?? Interrupt Status 1
+ */
+#define AIC_LEFT_DAC_OVERFLOW_INT 0x80
+#define AIC_RIGHT_DAC_OVERFLOW_INT 0x40
+#define AIC_MINIDSP_D_BARREL_SHIFT_OVERFLOW_INT 0x20
+#define AIC_LEFT_ADC_OVERFLOW_INT 0x08
+#define AIC_RIGHT_ADC_OVERFLOW_INT 0x04
+#define AIC_MINIDSP_A_BARREL_SHIFT_OVERFLOW_INT 0x02
+/*
+ * B0_P0_R44 (0x00002c) - Interrupt Status 2
+ */
+#define AIC_LEFT_OUTPUT_DRIVER_OVERCURRENT_INT 0x80
+#define AIC_RIGHT_OUTPUT_DRIVER_OVERCURRENT_INT 0x40
+#define AIC_BUTTON_PRESS_INT 0x20
+#define AIC_HEADSET_PLUG_UNPLUG_INT 0x10
+#define AIC_LEFT_DRC_THRES_INT 0x08
+#define AIC_RIGHT_DRC_THRES_INT 0x04
+#define AIC_MINIDSP_D_STD_INT 0x02
+#define AIC_MINIDSP_D_AUX_INT 0x01
+/*
+ * B0_P0_R45 (0x00002d) - Interrupt Status 3
+ */
+#define AIC_SPK_OVER_CURRENT_INT 0x80
+#define AIC_LEFT_AGC_NOISE_INT 0x40
+#define AIC_RIGHT_AGC_NOISE_INT 0x20
+#define AIC_MINIDSP_A_STD_INT 0x10
+#define AIC_MINIDSP_A_AUX_INT 0x08
+#define AIC_LEFT_ADC_DC_DATA_AVAILABLE_INT 0x04
+#define AIC_RIGHT_ADC_DC_DATA_AVAILABLE_INT 0x02
+#define AIC_CP_SHORT_CIRCUIT_INT 0x01
+/*
+ * B0_P0_R48 (0x000030) - Interrupt Control 1
+ */
+#define AIC_HEADSET_IN_M 0x80
+#define AIC_BUTTON_PRESS_M 0x40
+#define AIC_DAC_DRC_THRES_M 0x20
+#define AIC_AGC_NOISE_M 0x10
+#define AIC_OVER_CURRENT_M 0x08
+#define AIC_OVERFLOW_M 0x04
+#define AIC_SPK_OVERCURRENT_M 0x02
+#define AIC_CP_SHORT_CIRCUIT_M 0x02
#endif
--
1.7.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/