Merge remote branch 'origin/msm-3.0' into msm-3.4
* origin/msm-3.0: (87 commits)
Revert "msm: kgsl: Add VBIF error detection"
tspp: 8960: adding TSPP driver for 8960
board-8960: Merge secure and non secure firmware heaps.
msm: msm_dsps: Move to the new clk_prepare/unprepare API.
diag: Protect SMD channel from getting NULL value
camera: Mercury hardware JPEG decoder driver support.
msm: 8064-regulator: Remove 5V FRC gpio external regulator
diag: Respond to Get Subsystem Mask request
Revert "msm_fb: display: Attach and detach MDP IOMMU on suspend/resume"
Revert "msm_fb: display: Add MDP IOMMU detach support for DTV"
msm: rpm-8930: Fix incorrect RPM enumeration and DMM
msm: board-8930: Configure GPU turbo clock to 400MHz
usb: mdm_bridge: Fix bug in handling error condition
msm: vidc: Invalidate the cache before processing metadata.
video: msm: wfd: Add turbo mode support
tty: n_smux: Add Dedicated Power Control Queue
defconfig: msm-copper: Enable SPI ethernet support
msm: acpuclock-8960: Add PVS support on 8064
ASoC: mdm9615: Set correct GPIOs for AUX PCM
msm: 9615: Add auxpcm support over secondary audio interface
...
Conflicts:
arch/arm/configs/msm-copper_defconfig
drivers/char/diag/diagchar_core.c
drivers/char/diag/diagfwd_hsic.h
drivers/media/video/msm/msm_camera.c
drivers/media/video/msm/msm_mctl.c
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/wcd9xxx-slimslave.c
drivers/spmi/spmi.c
drivers/tty/n_smux.c
drivers/usb/otg/msm_otg.c
sound/soc/msm/msm-pcm-routing.h
Signed-off-by: Steve Muckle <smuckle@codeaurora.org>
Change-Id: I49d4ceff17714a7ba51243de63f27b7e78647bda
diff --git a/drivers/mfd/pm8821-irq.c b/drivers/mfd/pm8821-irq.c
new file mode 100644
index 0000000..2dcc792
--- /dev/null
+++ b/drivers/mfd/pm8821-irq.c
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/export.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pm8xxx/pm8821-irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define PM8821_TOTAL_IRQ_MASTERS 2
+#define PM8821_BLOCKS_PER_MASTER 7
+#define PM8821_IRQ_MASTER1_SET 0x01
+#define PM8821_IRQ_CLEAR_OFFSET 0x01
+#define PM8821_IRQ_RT_STATUS_OFFSET 0x0F
+#define PM8821_IRQ_MASK_REG_OFFSET 0x08
+#define SSBI_REG_ADDR_IRQ_MASTER0 0x30
+#define SSBI_REG_ADDR_IRQ_MASTER1 0xB0
+
+#define SSBI_REG_ADDR_IRQ_IT_STATUS(master_base, block) (master_base + block)
+
+/*
+ * Block 0 does not exist in PM8821 IRQ SSBI address space,
+ * IRQ0 is assigned to bit0 of block1.
+ */
+#define SSBI_REG_ADDR_IRQ_IT_CLEAR(master_base, block) \
+ (master_base + PM8821_IRQ_CLEAR_OFFSET + block)
+
+#define SSBI_REG_ADDR_IRQ_RT_STATUS(master_base, block) \
+ (master_base + PM8821_IRQ_RT_STATUS_OFFSET + block)
+
+#define SSBI_REG_ADDR_IRQ_MASK(master_base, block) \
+ (master_base + PM8821_IRQ_MASK_REG_OFFSET + block)
+
+struct pm_irq_chip {
+ struct device *dev;
+ spinlock_t pm_irq_lock;
+ unsigned int base_addr;
+ unsigned int devirq;
+ unsigned int irq_base;
+ unsigned int num_irqs;
+ int masters[PM8821_TOTAL_IRQ_MASTERS];
+};
+
+static int pm8821_irq_masked_write(struct pm_irq_chip *chip, u16 addr,
+ u8 mask, u8 val)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8xxx_readb(chip->dev, addr, ®);
+ if (rc) {
+ pr_err("read failed addr = %03X, rc = %d\n", addr, rc);
+ return rc;
+ }
+
+ reg &= ~mask;
+ reg |= val & mask;
+
+ rc = pm8xxx_writeb(chip->dev, addr, reg);
+ if (rc) {
+ pr_err("write failed addr = %03X, rc = %d\n", addr, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int pm8821_read_master_irq(const struct pm_irq_chip *chip,
+ int m, u8 *master)
+{
+ return pm8xxx_readb(chip->dev, chip->masters[m], master);
+}
+
+static int pm8821_read_block_irq(struct pm_irq_chip *chip, int master,
+ u8 block, u8 *bits)
+{
+ int rc;
+
+ spin_lock(&chip->pm_irq_lock);
+
+ rc = pm8xxx_readb(chip->dev,
+ SSBI_REG_ADDR_IRQ_IT_STATUS(chip->masters[master], block), bits);
+ if (rc)
+ pr_err("Failed Reading Status rc=%d\n", rc);
+
+ spin_unlock(&chip->pm_irq_lock);
+ return rc;
+}
+
+
+static int pm8821_irq_block_handler(struct pm_irq_chip *chip,
+ int master_number, int block)
+{
+ int pmirq, irq, i, ret;
+ u8 bits;
+
+ ret = pm8821_read_block_irq(chip, master_number, block, &bits);
+ if (ret) {
+ pr_err("Failed reading %d block ret=%d", block, ret);
+ return ret;
+ }
+ if (!bits) {
+ pr_err("block bit set in master but no irqs: %d", block);
+ return 0;
+ }
+
+ /* Convert block offset to global block number */
+ block += (master_number * PM8821_BLOCKS_PER_MASTER) - 1;
+
+ /* Check IRQ bits */
+ for (i = 0; i < 8; i++) {
+ if (bits & BIT(i)) {
+ pmirq = (block << 3) + i;
+ irq = pmirq + chip->irq_base;
+ generic_handle_irq(irq);
+ }
+ }
+ return 0;
+}
+
+static int pm8821_irq_read_master(struct pm_irq_chip *chip,
+ int master_number, u8 master_val)
+{
+ int ret = 0;
+ int block;
+
+ for (block = 1; block < 8; block++) {
+ if (master_val & BIT(block)) {
+ ret |= pm8821_irq_block_handler(chip,
+ master_number, block);
+ }
+ }
+
+ return ret;
+}
+
+static irqreturn_t pm8821_irq_handler(int irq, void *data)
+{
+ struct pm_irq_chip *chip = data;
+ int ret;
+ u8 master;
+
+ ret = pm8821_read_master_irq(chip, 0, &master);
+ if (ret) {
+ pr_err("Failed to read master 0 ret=%d\n", ret);
+ return ret;
+ }
+
+ if (master & ~PM8821_IRQ_MASTER1_SET)
+ pm8821_irq_read_master(chip, 0, master);
+
+ if (!(master & PM8821_IRQ_MASTER1_SET))
+ goto done;
+
+ ret = pm8821_read_master_irq(chip, 1, &master);
+ if (ret) {
+ pr_err("Failed to read master 1 ret=%d\n", ret);
+ return ret;
+ }
+
+ pm8821_irq_read_master(chip, 1, master);
+
+done:
+ return IRQ_HANDLED;
+}
+
+static void pm8821_irq_mask(struct irq_data *d)
+{
+ struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d);
+ unsigned int pmirq = d->irq - chip->irq_base;
+ int irq_bit, rc;
+ u8 block, master;
+
+ block = pmirq >> 3;
+ master = block / PM8821_BLOCKS_PER_MASTER;
+ irq_bit = pmirq % 8;
+ block %= PM8821_BLOCKS_PER_MASTER;
+
+ spin_lock(&chip->pm_irq_lock);
+
+ rc = pm8821_irq_masked_write(chip,
+ SSBI_REG_ADDR_IRQ_MASK(chip->masters[master], block),
+ BIT(irq_bit), BIT(irq_bit));
+
+ if (rc)
+ pr_err("Failed to read/write mask IRQ:%d rc=%d\n", pmirq, rc);
+
+ spin_unlock(&chip->pm_irq_lock);
+}
+
+static void pm8821_irq_mask_ack(struct irq_data *d)
+{
+ struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d);
+ unsigned int pmirq = d->irq - chip->irq_base;
+ int irq_bit, rc;
+ u8 block, master;
+
+ block = pmirq >> 3;
+ master = block / PM8821_BLOCKS_PER_MASTER;
+ irq_bit = pmirq % 8;
+ block %= PM8821_BLOCKS_PER_MASTER;
+
+ spin_lock(&chip->pm_irq_lock);
+
+ rc = pm8821_irq_masked_write(chip,
+ SSBI_REG_ADDR_IRQ_MASK(chip->masters[master], block),
+ BIT(irq_bit), BIT(irq_bit));
+
+ if (rc) {
+ pr_err("Failed to read/write mask IRQ:%d rc=%d\n", pmirq, rc);
+ goto fail;
+ }
+
+ rc = pm8821_irq_masked_write(chip,
+ SSBI_REG_ADDR_IRQ_IT_CLEAR(chip->masters[master], block),
+ BIT(irq_bit), BIT(irq_bit));
+
+ if (rc) {
+ pr_err("Failed to read/write IT_CLEAR IRQ:%d rc=%d\n",
+ pmirq, rc);
+ }
+
+fail:
+ spin_unlock(&chip->pm_irq_lock);
+}
+
+static void pm8821_irq_unmask(struct irq_data *d)
+{
+ struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d);
+ unsigned int pmirq = d->irq - chip->irq_base;
+ int irq_bit, rc;
+ u8 block, master;
+
+ block = pmirq >> 3;
+ master = block / PM8821_BLOCKS_PER_MASTER;
+ irq_bit = pmirq % 8;
+ block %= PM8821_BLOCKS_PER_MASTER;
+
+ spin_lock(&chip->pm_irq_lock);
+
+ rc = pm8821_irq_masked_write(chip,
+ SSBI_REG_ADDR_IRQ_MASK(chip->masters[master], block),
+ BIT(irq_bit), ~BIT(irq_bit));
+
+ if (rc)
+ pr_err("Failed to read/write unmask IRQ:%d rc=%d\n", pmirq, rc);
+
+ spin_unlock(&chip->pm_irq_lock);
+}
+
+static int pm8821_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ /*
+ * PM8821 IRQ controller does not have explicit software support for
+ * IRQ flow type.
+ */
+ return 0;
+}
+
+static int pm8821_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ return 0;
+}
+
+static int pm8821_irq_read_line(struct irq_data *d)
+{
+ struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d);
+
+ return pm8821_get_irq_stat(chip, d->irq);
+}
+
+static struct irq_chip pm_irq_chip = {
+ .name = "pm8821-irq",
+ .irq_mask = pm8821_irq_mask,
+ .irq_mask_ack = pm8821_irq_mask_ack,
+ .irq_unmask = pm8821_irq_unmask,
+ .irq_set_type = pm8821_irq_set_type,
+ .irq_set_wake = pm8821_irq_set_wake,
+ .irq_read_line = pm8821_irq_read_line,
+ .flags = IRQCHIP_MASK_ON_SUSPEND,
+};
+
+/**
+ * pm8821_get_irq_stat - get the status of the irq line
+ * @chip: pointer to identify a pmic irq controller
+ * @irq: the irq number
+ *
+ * The pm8821 gpio and mpp rely on the interrupt block to read
+ * the values on their pins. This function is to facilitate reading
+ * the status of a gpio or an mpp line. The caller has to convert the
+ * gpio number to irq number.
+ *
+ * RETURNS:
+ * an int indicating the value read on that line
+ */
+int pm8821_get_irq_stat(struct pm_irq_chip *chip, int irq)
+{
+ int pmirq, rc;
+ u8 block, bits, bit, master;
+ unsigned long flags;
+
+ if (chip == NULL || irq < chip->irq_base
+ || irq >= chip->irq_base + chip->num_irqs)
+ return -EINVAL;
+
+ pmirq = irq - chip->irq_base;
+
+ block = pmirq >> 3;
+ master = block / PM8821_BLOCKS_PER_MASTER;
+ bit = pmirq % 8;
+ block %= PM8821_BLOCKS_PER_MASTER;
+
+ spin_lock_irqsave(&chip->pm_irq_lock, flags);
+
+ rc = pm8xxx_readb(chip->dev,
+ SSBI_REG_ADDR_IRQ_RT_STATUS(chip->masters[master], block),
+ &bits);
+ if (rc) {
+ pr_err("Failed Configuring irq=%d pmirq=%d blk=%d rc=%d\n",
+ irq, pmirq, block, rc);
+ goto bail_out;
+ }
+
+ rc = (bits & BIT(bit)) ? 1 : 0;
+
+bail_out:
+ spin_unlock_irqrestore(&chip->pm_irq_lock, flags);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(pm8821_get_irq_stat);
+
+struct pm_irq_chip * __devinit pm8821_irq_init(struct device *dev,
+ const struct pm8xxx_irq_platform_data *pdata)
+{
+ struct pm_irq_chip *chip;
+ int devirq, rc, blocks, masters;
+ unsigned int pmirq;
+
+ if (!pdata) {
+ pr_err("No platform data\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ devirq = pdata->devirq;
+ if (devirq < 0) {
+ pr_err("missing devirq\n");
+ rc = devirq;
+ return ERR_PTR(-EINVAL);
+ }
+
+ chip = kzalloc(sizeof(struct pm_irq_chip)
+ + sizeof(u8) * pdata->irq_cdata.nirqs, GFP_KERNEL);
+ if (!chip) {
+ pr_err("Cannot alloc pm_irq_chip struct\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ chip->dev = dev;
+ chip->devirq = devirq;
+ chip->irq_base = pdata->irq_base;
+ chip->num_irqs = pdata->irq_cdata.nirqs;
+ chip->base_addr = pdata->irq_cdata.base_addr;
+ blocks = DIV_ROUND_UP(pdata->irq_cdata.nirqs, 8);
+ masters = DIV_ROUND_UP(blocks, PM8821_BLOCKS_PER_MASTER);
+ chip->masters[0] = chip->base_addr + SSBI_REG_ADDR_IRQ_MASTER0;
+ chip->masters[1] = chip->base_addr + SSBI_REG_ADDR_IRQ_MASTER1;
+
+ if (masters != PM8821_TOTAL_IRQ_MASTERS) {
+ pr_err("Unequal number of masters, passed: %d, "
+ "should have been: %d\n", masters, PM8821_TOTAL_IRQ_MASTERS);
+ kfree(chip);
+ return ERR_PTR(-EINVAL);
+ }
+
+ spin_lock_init(&chip->pm_irq_lock);
+
+ for (pmirq = 0; pmirq < chip->num_irqs; pmirq++) {
+ irq_set_chip_and_handler(chip->irq_base + pmirq,
+ &pm_irq_chip, handle_level_irq);
+ irq_set_chip_data(chip->irq_base + pmirq, chip);
+#ifdef CONFIG_ARM
+ set_irq_flags(chip->irq_base + pmirq, IRQF_VALID);
+#else
+ irq_set_noprobe(chip->irq_base + pmirq);
+#endif
+ }
+
+ if (devirq != 0) {
+ rc = request_irq(devirq, pm8821_irq_handler,
+ pdata->irq_trigger_flag, "pm8821_sec_irq", chip);
+ if (rc) {
+ pr_err("failed to request_irq for %d rc=%d\n",
+ devirq, rc);
+ kfree(chip);
+ return ERR_PTR(rc);
+ } else
+ irq_set_irq_wake(devirq, 1);
+ }
+
+ return chip;
+}
+
+int pm8821_irq_exit(struct pm_irq_chip *chip)
+{
+ irq_set_chained_handler(chip->devirq, NULL);
+ kfree(chip);
+ return 0;
+}