blob: 5619a88f0d288664d8cc3617b90a768bc07abcdc [file] [log] [blame]
/* Copyright (c) 2013-2014, The Linux Foundation. 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.
*/
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
#include "pinctrl-msm.h"
/* config translations */
#define drv_str_to_rval(drv) ((drv >> 1) - 1)
#define rval_to_drv_str(val) ((val + 1) << 1)
#define dir_to_inout_val(dir) (dir << 1)
#define inout_val_to_dir(val) (val >> 1)
#define rval_to_pull(val) ((val > 2) ? 1 : val)
#define TLMMV4_NO_PULL 0
#define TLMMV4_PULL_DOWN 1
#define TLMMV4_PULL_UP 3
/* GP PIN TYPE REG MASKS */
#define TLMMV4_GP_DRV_SHFT 6
#define TLMMV4_GP_DRV_MASK 0x7
#define TLMMV4_GP_PULL_SHFT 0
#define TLMMV4_GP_PULL_MASK 0x3
#define TLMMV4_GP_DIR_SHFT 9
#define TLMMV4_GP_DIR_MASK 1
#define TLMMV4_GP_FUNC_SHFT 2
#define TLMMV4_GP_FUNC_MASK 0xF
#define GPIO_OUT_BIT 1
#define GPIO_IN_BIT 0
#define GPIO_OE_BIT 9
/* SDC1 PIN TYPE REG MASKS */
#define TLMMV4_SDC1_CLK_DRV_SHFT 6
#define TLMMV4_SDC1_CLK_DRV_MASK 0x7
#define TLMMV4_SDC1_DATA_DRV_SHFT 0
#define TLMMV4_SDC1_DATA_DRV_MASK 0x7
#define TLMMV4_SDC1_CMD_DRV_SHFT 3
#define TLMMV4_SDC1_CMD_DRV_MASK 0x7
#define TLMMV4_SDC1_CLK_PULL_SHFT 13
#define TLMMV4_SDC1_CLK_PULL_MASK 0x3
#define TLMMV4_SDC1_DATA_PULL_SHFT 9
#define TLMMV4_SDC1_DATA_PULL_MASK 0x3
#define TLMMV4_SDC1_CMD_PULL_SHFT 11
#define TLMMV4_SDC1_CMD_PULL_MASK 0x3
/* SDC2 PIN TYPE REG MASKS */
#define TLMMV4_SDC2_CLK_DRV_SHFT 6
#define TLMMV4_SDC2_CLK_DRV_MASK 0x7
#define TLMMV4_SDC2_DATA_DRV_SHFT 0
#define TLMMV4_SDC2_DATA_DRV_MASK 0x7
#define TLMMV4_SDC2_CMD_DRV_SHFT 3
#define TLMMV4_SDC2_CMD_DRV_MASK 0x7
#define TLMMV4_SDC2_CLK_PULL_SHFT 14
#define TLMMV4_SDC2_CLK_PULL_MASK 0x3
#define TLMMV4_SDC2_DATA_PULL_SHFT 9
#define TLMMV4_SDC2_DATA_PULL_MASK 0x3
#define TLMMV4_SDC2_CMD_PULL_SHFT 11
#define TLMMV4_SDC2_CMD_PULL_MASK 0x3
/* TLMM V4 IRQ REG fields */
#define INTR_ENABLE_BIT 0
#define INTR_POL_CTL_BIT 1
#define INTR_DECT_CTL_BIT 2
#define INTR_RAW_STATUS_EN_BIT 4
#define INTR_TARGET_PROC_BIT 5
#define INTR_DIR_CONN_EN_BIT 8
#define INTR_STATUS_BIT 0
#define DC_POLARITY_BIT 8
/* Target processors for TLMM pin based interrupts */
#define INTR_TARGET_PROC_APPS(core_id) ((core_id) << INTR_TARGET_PROC_BIT)
#define TLMMV4_APPS_ID_DEFAULT 4
#define INTR_TARGET_PROC_NONE (7 << INTR_TARGET_PROC_BIT)
/* Interrupt flag bits */
#define DC_POLARITY_HI BIT(DC_POLARITY_BIT)
#define INTR_POL_CTL_HI BIT(INTR_POL_CTL_BIT)
#define INTR_DECT_CTL_LEVEL (0 << INTR_DECT_CTL_BIT)
#define INTR_DECT_CTL_POS_EDGE (1 << INTR_DECT_CTL_BIT)
#define INTR_DECT_CTL_NEG_EDGE (2 << INTR_DECT_CTL_BIT)
#define INTR_DECT_CTL_DUAL_EDGE (3 << INTR_DECT_CTL_BIT)
#define INTR_DECT_CTL_MASK (3 << INTR_DECT_CTL_BIT)
#define TLMMV4_GP_INOUT_BIT 1
#define TLMMV4_GP_OUT BIT(TLMMV4_GP_INOUT_BIT)
#define TLMMV4_GP_IN 0
#define gc_to_pintype(gc) \
container_of(gc, struct msm_pintype_info, gc)
#define ic_to_pintype(ic) \
((struct msm_pintype_info *)ic->pinfo)
#define pintype_get_gc(pinfo) (&pinfo->gc)
#define pintype_get_ic(pinfo) (pinfo->irq_chip)
/* SDC Pin type register offsets */
#define TLMMV4_SDC_OFFSET 0x0010A000
#define TLMMV4_SDC1_CFG(base) (base)
#define TLMMV4_SDC2_CFG(base) (TLMMV4_SDC1_CFG(base) - 0x1000)
/* GP pin type register offsets */
#define TLMMV4_GP_CFG(base, pin) (base + 0x0 + 0x1000 * (pin))
#define TLMMV4_GP_INOUT(base, pin) (base + 0x4 + 0x1000 * (pin))
#define TLMMV4_GP_INTR_CFG(base, pin) (base + 0x8 + 0x1000 * (pin))
#define TLMMV4_GP_INTR_STATUS(base, pin) (base + 0xc + 0x1000 * (pin))
/* QDSD Pin type register offsets */
#define TLMMV4_QDSD_OFFSET 0x0019C000
#define TLMMV4_QDSD_PULL_MASK 0x3
#define TLMMV4_QDSD_PULL_OFFSET 0x3
#define TLMMV4_QDSD_CONFIG_WIDTH 0x5
#define TLMMV4_QDSD_DRV_MASK 0x7
struct msm_sdc_regs {
unsigned int offset;
unsigned long pull_mask;
unsigned long pull_shft;
unsigned long drv_mask;
unsigned long drv_shft;
};
static struct msm_sdc_regs sdc_regs[] = {
/* SDC1 CLK */
{
.offset = 0,
.pull_mask = TLMMV4_SDC1_CLK_PULL_MASK,
.pull_shft = TLMMV4_SDC1_CLK_PULL_SHFT,
.drv_mask = TLMMV4_SDC1_CLK_DRV_MASK,
.drv_shft = TLMMV4_SDC1_CLK_DRV_SHFT,
},
/* SDC1 CMD */
{
.offset = 0,
.pull_mask = TLMMV4_SDC1_CMD_PULL_MASK,
.pull_shft = TLMMV4_SDC1_CMD_PULL_SHFT,
.drv_mask = TLMMV4_SDC1_CMD_DRV_MASK,
.drv_shft = TLMMV4_SDC1_CMD_DRV_SHFT,
},
/* SDC1 DATA */
{
.offset = 0,
.pull_mask = TLMMV4_SDC1_DATA_PULL_MASK,
.pull_shft = TLMMV4_SDC1_DATA_PULL_SHFT,
.drv_mask = TLMMV4_SDC1_DATA_DRV_MASK,
.drv_shft = TLMMV4_SDC1_DATA_DRV_SHFT,
},
/* SDC2 CLK */
{
.offset = 0x1000,
.pull_mask = TLMMV4_SDC2_CLK_PULL_MASK,
.pull_shft = TLMMV4_SDC2_CLK_PULL_SHFT,
.drv_mask = TLMMV4_SDC2_CLK_DRV_MASK,
.drv_shft = TLMMV4_SDC2_CLK_DRV_SHFT,
},
/* SDC2 CMD */
{
.offset = 0x1000,
.pull_mask = TLMMV4_SDC2_CMD_PULL_MASK,
.pull_shft = TLMMV4_SDC2_CMD_PULL_SHFT,
.drv_mask = TLMMV4_SDC2_CMD_DRV_MASK,
.drv_shft = TLMMV4_SDC2_CMD_DRV_SHFT,
},
/* SDC2 DATA */
{
.offset = 0x1000,
.pull_mask = TLMMV4_SDC2_DATA_PULL_MASK,
.pull_shft = TLMMV4_SDC2_DATA_PULL_SHFT,
.drv_mask = TLMMV4_SDC2_DATA_DRV_MASK,
.drv_shft = TLMMV4_SDC2_DATA_DRV_SHFT,
},
};
static int msm_tlmm_v4_sdc_cfg(uint pin_no, unsigned long *config,
void __iomem *reg_base,
bool write)
{
unsigned int val, id, data;
u32 mask, shft;
void __iomem *cfg_reg;
cfg_reg = reg_base - sdc_regs[pin_no].offset;
id = pinconf_to_config_param(*config);
val = readl_relaxed(cfg_reg);
/* Get mask and shft values for this config type */
switch (id) {
case PIN_CONFIG_BIAS_DISABLE:
mask = sdc_regs[pin_no].pull_mask;
shft = sdc_regs[pin_no].pull_shft;
data = TLMMV4_NO_PULL;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
mask = sdc_regs[pin_no].pull_mask;
shft = sdc_regs[pin_no].pull_shft;
data = TLMMV4_PULL_DOWN;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_UP:
mask = sdc_regs[pin_no].pull_mask;
shft = sdc_regs[pin_no].pull_shft;
data = TLMMV4_PULL_UP;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_DRIVE_STRENGTH:
mask = sdc_regs[pin_no].drv_mask;
shft = sdc_regs[pin_no].drv_shft;
if (write) {
data = pinconf_to_config_argument(*config);
data = drv_str_to_rval(data);
} else {
val >>= shft;
val &= mask;
data = rval_to_drv_str(val);
}
break;
default:
return -EINVAL;
};
if (write) {
val &= ~(mask << shft);
val |= (data << shft);
writel_relaxed(val, cfg_reg);
} else
*config = pinconf_to_config_packed(id, data);
return 0;
}
static void msm_tlmm_v4_sdc_set_reg_base(void __iomem **ptype_base,
void __iomem *tlmm_base)
{
*ptype_base = tlmm_base + TLMMV4_SDC_OFFSET;
}
static int msm_tlmm_v4_qdsd_cfg(uint pin_no, unsigned long *config,
void __iomem *reg_base,
bool write)
{
unsigned int val, id, data;
u32 mask, shft;
void __iomem *cfg_reg;
cfg_reg = reg_base;
id = pinconf_to_config_param(*config);
val = readl_relaxed(cfg_reg);
/* Get mask and shft values for this config type */
switch (id) {
case PIN_CONFIG_BIAS_DISABLE:
mask = TLMMV4_QDSD_PULL_MASK;
shft = id * TLMMV4_QDSD_CONFIG_WIDTH + TLMMV4_QDSD_PULL_OFFSET;
data = TLMMV4_NO_PULL;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
mask = TLMMV4_QDSD_PULL_MASK;
shft = id * TLMMV4_QDSD_CONFIG_WIDTH + TLMMV4_QDSD_PULL_OFFSET;
data = TLMMV4_PULL_DOWN;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_UP:
mask = TLMMV4_QDSD_PULL_MASK;
shft = id * TLMMV4_QDSD_CONFIG_WIDTH + TLMMV4_QDSD_PULL_OFFSET;
data = TLMMV4_PULL_UP;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_DRIVE_STRENGTH:
mask = TLMMV4_QDSD_DRV_MASK;
shft = id * TLMMV4_QDSD_CONFIG_WIDTH;
if (write) {
data = pinconf_to_config_argument(*config);
data = drv_str_to_rval(data);
} else {
val >>= shft;
val &= mask;
data = rval_to_drv_str(val);
}
break;
default:
return -EINVAL;
};
if (write) {
val &= ~(mask << shft);
val |= (data << shft);
writel_relaxed(val, cfg_reg);
} else
*config = pinconf_to_config_packed(id, data);
return 0;
}
static void msm_tlmm_v4_qdsd_set_reg_base(void __iomem **ptype_base,
void __iomem *tlmm_base)
{
*ptype_base = tlmm_base + TLMMV4_QDSD_OFFSET;
}
static int msm_tlmm_v4_gp_cfg(uint pin_no, unsigned long *config,
void *reg_base, bool write)
{
unsigned int val, id, data, inout_val;
u32 mask = 0, shft = 0;
void __iomem *inout_reg = NULL;
void __iomem *cfg_reg = TLMMV4_GP_CFG(reg_base, pin_no);
id = pinconf_to_config_param(*config);
val = readl_relaxed(cfg_reg);
/* Get mask and shft values for this config type */
switch (id) {
case PIN_CONFIG_BIAS_DISABLE:
mask = TLMMV4_GP_PULL_MASK;
shft = TLMMV4_GP_PULL_SHFT;
data = TLMMV4_NO_PULL;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
mask = TLMMV4_GP_PULL_MASK;
shft = TLMMV4_GP_PULL_SHFT;
data = TLMMV4_PULL_DOWN;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_UP:
mask = TLMMV4_GP_PULL_MASK;
shft = TLMMV4_GP_PULL_SHFT;
data = TLMMV4_PULL_UP;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_DRIVE_STRENGTH:
mask = TLMMV4_GP_DRV_MASK;
shft = TLMMV4_GP_DRV_SHFT;
if (write) {
data = pinconf_to_config_argument(*config);
data = drv_str_to_rval(data);
} else {
val >>= shft;
val &= mask;
data = rval_to_drv_str(val);
}
break;
case PIN_CONFIG_OUTPUT:
mask = TLMMV4_GP_DIR_MASK;
shft = TLMMV4_GP_DIR_SHFT;
inout_reg = TLMMV4_GP_INOUT(reg_base, pin_no);
if (write) {
data = pinconf_to_config_argument(*config);
inout_val = dir_to_inout_val(data);
writel_relaxed(inout_val, inout_reg);
data = mask;
} else {
inout_val = readl_relaxed(inout_reg);
data = inout_val_to_dir(inout_val);
}
break;
default:
return -EINVAL;
};
if (write) {
val &= ~(mask << shft);
val |= (data << shft);
writel_relaxed(val, cfg_reg);
} else
*config = pinconf_to_config_packed(id, data);
return 0;
}
static void msm_tlmm_v4_gp_fn(uint pin_no, u32 func, void *reg_base,
bool enable)
{
unsigned int val;
void __iomem *cfg_reg = TLMMV4_GP_CFG(reg_base, pin_no);
val = readl_relaxed(cfg_reg);
val &= ~(TLMMV4_GP_FUNC_MASK << TLMMV4_GP_FUNC_SHFT);
if (enable)
val |= (func << TLMMV4_GP_FUNC_SHFT);
writel_relaxed(val, cfg_reg);
}
static void msm_tlmm_v4_gp_set_reg_base(void __iomem **ptype_base,
void __iomem *tlmm_base)
{
*ptype_base = tlmm_base;
}
/* GPIO CHIP */
static int msm_tlmm_v4_gp_get(struct gpio_chip *gc, unsigned offset)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *inout_reg = TLMMV4_GP_INOUT(pinfo->reg_base, offset);
return readl_relaxed(inout_reg) & BIT(GPIO_IN_BIT);
}
static void msm_tlmm_v4_gp_set(struct gpio_chip *gc, unsigned offset, int val)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *inout_reg = TLMMV4_GP_INOUT(pinfo->reg_base, offset);
writel_relaxed(val ? BIT(GPIO_OUT_BIT) : 0, inout_reg);
}
static int msm_tlmm_v4_gp_dir_in(struct gpio_chip *gc, unsigned offset)
{
unsigned int val;
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *cfg_reg = TLMMV4_GP_CFG(pinfo->reg_base, offset);
val = readl_relaxed(cfg_reg);
val &= ~BIT(GPIO_OE_BIT);
writel_relaxed(val, cfg_reg);
return 0;
}
static int msm_tlmm_v4_gp_dir_out(struct gpio_chip *gc, unsigned offset,
int val)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *cfg_reg = TLMMV4_GP_CFG(pinfo->reg_base, offset);
msm_tlmm_v4_gp_set(gc, offset, val);
val = readl_relaxed(cfg_reg);
val |= BIT(GPIO_OE_BIT);
writel_relaxed(val, cfg_reg);
return 0;
}
static int msm_tlmm_v4_gp_to_irq(struct gpio_chip *gc, unsigned offset)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
struct msm_tlmm_irq_chip *ic = pintype_get_ic(pinfo);
return irq_create_mapping(ic->domain, offset);
}
/* Irq reg ops */
static void msm_tlmm_v4_set_intr_status(struct msm_tlmm_irq_chip *ic,
unsigned pin)
{
void __iomem *status_reg = TLMMV4_GP_INTR_STATUS(ic->chip_base, pin);
writel_relaxed(0, status_reg);
}
static int msm_tlmm_v4_get_intr_status(struct msm_tlmm_irq_chip *ic,
unsigned pin)
{
void __iomem *status_reg = TLMMV4_GP_INTR_STATUS(ic->chip_base, pin);
return readl_relaxed(status_reg) & BIT(INTR_STATUS_BIT);
}
static void msm_tlmm_v4_set_intr_cfg_enable(struct msm_tlmm_irq_chip *ic,
unsigned pin,
int enable)
{
unsigned int val;
void __iomem *cfg_reg = TLMMV4_GP_INTR_CFG(ic->chip_base, pin);
val = readl_relaxed(cfg_reg);
if (enable) {
val &= ~BIT(INTR_DIR_CONN_EN_BIT);
val |= BIT(INTR_ENABLE_BIT);
} else
val &= ~BIT(INTR_ENABLE_BIT);
writel_relaxed(val, cfg_reg);
}
static int msm_tlmm_v4_get_intr_cfg_enable(struct msm_tlmm_irq_chip *ic,
unsigned pin)
{
void __iomem *cfg_reg = TLMMV4_GP_INTR_CFG(ic->chip_base, pin);
return readl_relaxed(cfg_reg) & BIT(INTR_ENABLE_BIT);
}
static void msm_tlmm_v4_set_intr_cfg_type(struct msm_tlmm_irq_chip *ic,
struct irq_data *d,
unsigned int type)
{
unsigned cfg;
void __iomem *cfg_reg = TLMMV4_GP_INTR_CFG(ic->chip_base,
(irqd_to_hwirq(d)));
/*
* RAW_STATUS_EN is left on for all gpio irqs. Due to the
* internal circuitry of TLMM, toggling the RAW_STATUS
* could cause the INTR_STATUS to be set for EDGE interrupts.
*/
cfg = BIT(INTR_RAW_STATUS_EN_BIT) | INTR_TARGET_PROC_APPS(ic->apps_id);
writel_relaxed(cfg, cfg_reg);
cfg &= ~INTR_DECT_CTL_MASK;
if (type == IRQ_TYPE_EDGE_RISING)
cfg |= INTR_DECT_CTL_POS_EDGE;
else if (type == IRQ_TYPE_EDGE_FALLING)
cfg |= INTR_DECT_CTL_NEG_EDGE;
else if (type == IRQ_TYPE_EDGE_BOTH)
cfg |= INTR_DECT_CTL_DUAL_EDGE;
else
cfg |= INTR_DECT_CTL_LEVEL;
if (type & IRQ_TYPE_LEVEL_LOW)
cfg &= ~INTR_POL_CTL_HI;
else
cfg |= INTR_POL_CTL_HI;
writel_relaxed(cfg, cfg_reg);
/*
* Sometimes it might take a little while to update
* the interrupt status after the RAW_STATUS is enabled
* We clear the interrupt status before enabling the
* interrupt in the unmask call-back.
*/
udelay(5);
}
static irqreturn_t msm_tlmm_v4_gp_handle_irq(int irq,
struct msm_tlmm_irq_chip *ic)
{
unsigned long i;
unsigned int virq = 0;
struct irq_desc *desc = irq_to_desc(irq);
struct irq_chip *chip = irq_desc_get_chip(desc);
struct msm_pintype_info *pinfo = ic_to_pintype(ic);
struct gpio_chip *gc = pintype_get_gc(pinfo);
chained_irq_enter(chip, desc);
for_each_set_bit(i, ic->enabled_irqs, ic->num_irqs) {
dev_dbg(ic->dev, "hwirq in bit mask %d\n", (unsigned int)i);
if (msm_tlmm_v4_get_intr_status(ic, i)) {
dev_dbg(ic->dev, "hwirw %d fired\n", (unsigned int)i);
virq = msm_tlmm_v4_gp_to_irq(gc, i);
if (!virq) {
dev_dbg(ic->dev, "invalid virq\n");
return IRQ_NONE;
}
generic_handle_irq(virq);
}
}
chained_irq_exit(chip, desc);
return IRQ_HANDLED;
}
static void msm_tlmm_v4_irq_ack(struct irq_data *d)
{
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
msm_tlmm_v4_set_intr_status(ic, irqd_to_hwirq(d));
mb();
}
static void msm_tlmm_v4_irq_mask(struct irq_data *d)
{
unsigned long irq_flags;
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
spin_lock_irqsave(&ic->irq_lock, irq_flags);
msm_tlmm_v4_set_intr_cfg_enable(ic, irqd_to_hwirq(d), 0);
__clear_bit(irqd_to_hwirq(d), ic->enabled_irqs);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
if (ic->irq_chip_extn->irq_mask)
ic->irq_chip_extn->irq_mask(d);
}
static void msm_tlmm_v4_irq_unmask(struct irq_data *d)
{
unsigned long irq_flags;
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
spin_lock_irqsave(&ic->irq_lock, irq_flags);
__set_bit(irqd_to_hwirq(d), ic->enabled_irqs);
if (!msm_tlmm_v4_get_intr_cfg_enable(ic, irqd_to_hwirq(d))) {
msm_tlmm_v4_set_intr_status(ic, irqd_to_hwirq(d));
msm_tlmm_v4_set_intr_cfg_enable(ic, irqd_to_hwirq(d), 1);
mb();
}
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
if (ic->irq_chip_extn->irq_unmask)
ic->irq_chip_extn->irq_unmask(d);
}
static void msm_tlmm_v4_irq_disable(struct irq_data *d)
{
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
if (ic->irq_chip_extn->irq_disable)
ic->irq_chip_extn->irq_disable(d);
}
static int msm_tlmm_v4_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
unsigned long irq_flags;
unsigned int pin = irqd_to_hwirq(d);
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
spin_lock_irqsave(&ic->irq_lock, irq_flags);
if (flow_type & IRQ_TYPE_EDGE_BOTH) {
__irq_set_handler_locked(d->irq, handle_edge_irq);
if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
__set_bit(pin, ic->dual_edge_irqs);
else
__clear_bit(pin, ic->dual_edge_irqs);
} else {
__irq_set_handler_locked(d->irq, handle_level_irq);
__clear_bit(pin, ic->dual_edge_irqs);
}
msm_tlmm_v4_set_intr_cfg_type(ic, d, flow_type);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
if ((flow_type & IRQ_TYPE_EDGE_BOTH) != IRQ_TYPE_EDGE_BOTH) {
if (ic->irq_chip_extn->irq_set_type)
ic->irq_chip_extn->irq_set_type(d, flow_type);
}
return 0;
}
static int msm_tlmm_v4_irq_set_wake(struct irq_data *d, unsigned int on)
{
unsigned int pin = irqd_to_hwirq(d);
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
if (on) {
if (bitmap_empty(ic->wake_irqs, ic->num_irqs))
irq_set_irq_wake(ic->irq, 1);
set_bit(pin, ic->wake_irqs);
} else {
clear_bit(pin, ic->wake_irqs);
if (bitmap_empty(ic->wake_irqs, ic->num_irqs))
irq_set_irq_wake(ic->irq, 0);
}
if (ic->irq_chip_extn->irq_set_wake)
ic->irq_chip_extn->irq_set_wake(d, on);
return 0;
}
static struct lock_class_key msm_tlmm_irq_lock_class;
static int msm_tlmm_v4_irq_map(struct irq_domain *h, unsigned int virq,
irq_hw_number_t hw)
{
struct msm_tlmm_irq_chip *ic = h->host_data;
irq_set_lockdep_class(virq, &msm_tlmm_irq_lock_class);
irq_set_chip_data(virq, ic);
irq_set_chip_and_handler(virq, &ic->chip,
handle_level_irq);
set_irq_flags(virq, IRQF_VALID);
return 0;
}
/*
* irq domain callbacks for interrupt controller.
*/
static const struct irq_domain_ops msm_tlmm_v4_gp_irqd_ops = {
.map = msm_tlmm_v4_irq_map,
.xlate = irq_domain_xlate_twocell,
};
static struct irq_chip mpm_tlmm_irq_extn;
static struct msm_tlmm_irq_chip msm_tlmm_v4_gp_irq = {
.irq_chip_extn = &mpm_tlmm_irq_extn,
.chip = {
.name = "msm_tlmm_v4_irq",
.irq_mask = msm_tlmm_v4_irq_mask,
.irq_unmask = msm_tlmm_v4_irq_unmask,
.irq_ack = msm_tlmm_v4_irq_ack,
.irq_set_type = msm_tlmm_v4_irq_set_type,
.irq_set_wake = msm_tlmm_v4_irq_set_wake,
.irq_disable = msm_tlmm_v4_irq_disable,
},
.apps_id = TLMMV4_APPS_ID_DEFAULT,
.domain_ops = &msm_tlmm_v4_gp_irqd_ops,
.handler = msm_tlmm_v4_gp_handle_irq,
};
/* Power management core operations */
static int msm_tlmm_v4_gp_irq_suspend(void)
{
unsigned long irq_flags;
unsigned long i;
struct msm_tlmm_irq_chip *ic = &msm_tlmm_v4_gp_irq;
int num_irqs = ic->num_irqs;
spin_lock_irqsave(&ic->irq_lock, irq_flags);
for_each_set_bit(i, ic->enabled_irqs, num_irqs)
msm_tlmm_v4_set_intr_cfg_enable(ic, i, 0);
for_each_set_bit(i, ic->enabled_irqs, num_irqs)
msm_tlmm_v4_set_intr_cfg_enable(ic, i, 1);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
return 0;
}
static void msm_tlmm_v4_gp_irq_resume(void)
{
unsigned long irq_flags;
unsigned long i;
struct msm_tlmm_irq_chip *ic = &msm_tlmm_v4_gp_irq;
int num_irqs = ic->num_irqs;
spin_lock_irqsave(&ic->irq_lock, irq_flags);
for_each_set_bit(i, ic->wake_irqs, num_irqs)
msm_tlmm_v4_set_intr_cfg_enable(ic, i, 0);
for_each_set_bit(i, ic->enabled_irqs, num_irqs)
msm_tlmm_v4_set_intr_cfg_enable(ic, i, 1);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
}
static struct syscore_ops msm_tlmm_v4_irq_syscore_ops = {
.suspend = msm_tlmm_v4_gp_irq_suspend,
.resume = msm_tlmm_v4_gp_irq_resume,
};
#ifdef CONFIG_USE_PINCTRL_IRQ
int msm_tlmm_v4_of_irq_init(struct device_node *controller,
struct irq_chip *chip_extn)
{
int ret, num_irqs, apps_id;
struct msm_tlmm_irq_chip *ic = &msm_tlmm_v4_gp_irq;
ret = of_property_read_u32(controller, "num_irqs", &num_irqs);
if (ret) {
WARN(1, "Cannot get numirqs from device tree\n");
return ret;
}
ret = of_property_read_u32(controller, "apps_id", &apps_id);
if (!ret) {
pr_info("processor id specified, in device tree %d\n", apps_id);
ic->apps_id = apps_id;
}
ic->num_irqs = num_irqs;
ic->domain = irq_domain_add_linear(controller, ic->num_irqs,
ic->domain_ops,
ic);
if (IS_ERR(ic->domain))
return -ENOMEM;
ic->irq_chip_extn = chip_extn;
return 0;
}
#endif
static int msm_tlmm_v4_gp_irq_init(int irq, struct msm_pintype_info *pinfo,
struct device *tlmm_dev)
{
int num_irqs;
struct msm_tlmm_irq_chip *ic = pinfo->irq_chip;
num_irqs = ic->num_irqs;
ic->enabled_irqs = devm_kzalloc(tlmm_dev, sizeof(unsigned long)
* BITS_TO_LONGS(num_irqs), GFP_KERNEL);
if (IS_ERR(ic->enabled_irqs)) {
dev_err(tlmm_dev, "Unable to allocate enabled irqs bitmap\n");
return PTR_ERR(ic->enabled_irqs);
}
ic->dual_edge_irqs = devm_kzalloc(tlmm_dev, sizeof(unsigned long)
* BITS_TO_LONGS(num_irqs), GFP_KERNEL);
if (IS_ERR(ic->dual_edge_irqs)) {
dev_err(tlmm_dev, "Unable to allocate dual edge irqs bitmap\n");
return PTR_ERR(ic->dual_edge_irqs);
}
ic->wake_irqs = devm_kzalloc(tlmm_dev, sizeof(unsigned long)
* BITS_TO_LONGS(num_irqs), GFP_KERNEL);
if (IS_ERR(ic->wake_irqs)) {
dev_err(tlmm_dev, "Unable to allocate wake irqs bitmap\n");
return PTR_ERR(ic->wake_irqs);
}
spin_lock_init(&ic->irq_lock);
ic->chip_base = pinfo->reg_base;
ic->irq = irq;
ic->dev = tlmm_dev;
ic->num_irqs = pinfo->num_pins;
ic->pinfo = pinfo;
register_syscore_ops(&msm_tlmm_v4_irq_syscore_ops);
return 0;
}
static irqreturn_t msm_tlmm_v4_handle_irq(int irq, void *data)
{
int i, num_pintypes;
struct msm_pintype_info *pintypes, *pintype;
struct msm_tlmm_irq_chip *ic;
struct msm_tlmm_desc *tlmm_desc = (struct msm_tlmm_desc *)data;
irqreturn_t ret = IRQ_NONE;
pintypes = tlmm_desc->pintypes;
num_pintypes = tlmm_desc->num_pintypes;
for (i = 0; i < num_pintypes; i++) {
pintype = &pintypes[i];
if (!pintype->irq_chip)
continue;
ic = pintype->irq_chip;
if (!ic->node)
continue;
ret = ic->handler(irq, ic);
if (ret != IRQ_HANDLED)
break;
}
return ret;
}
static struct msm_pintype_info tlmm_v4_pininfo[] = {
{
.prg_cfg = msm_tlmm_v4_gp_cfg,
.prg_func = msm_tlmm_v4_gp_fn,
.set_reg_base = msm_tlmm_v4_gp_set_reg_base,
.reg_base = NULL,
.prop_name = "qcom,pin-type-gp",
.name = "gp",
.gc = {
.label = "msm_tlmm_v4_gpio",
.direction_input = msm_tlmm_v4_gp_dir_in,
.direction_output = msm_tlmm_v4_gp_dir_out,
.get = msm_tlmm_v4_gp_get,
.set = msm_tlmm_v4_gp_set,
.to_irq = msm_tlmm_v4_gp_to_irq,
},
.init_irq = msm_tlmm_v4_gp_irq_init,
.irq_chip = &msm_tlmm_v4_gp_irq,
},
{
.prg_cfg = msm_tlmm_v4_sdc_cfg,
.set_reg_base = msm_tlmm_v4_sdc_set_reg_base,
.reg_base = NULL,
.prop_name = "qcom,pin-type-sdc",
.name = "sdc",
},
{
.prg_cfg = msm_tlmm_v4_qdsd_cfg,
.set_reg_base = msm_tlmm_v4_qdsd_set_reg_base,
.reg_base = NULL,
.prop_name = "qcom,pin-type-qdsd",
.name = "qdsd",
}
};
struct msm_tlmm_pintype tlmm_v4_pintypes = {
.num_entries = ARRAY_SIZE(tlmm_v4_pininfo),
.pintype_info = tlmm_v4_pininfo,
};
static const struct of_device_id msm_tlmm_v4_dt_match[] = {
{ .compatible = "qcom,msm-tlmm-v4",
.data = &tlmm_v4_pintypes, },
{},
};
MODULE_DEVICE_TABLE(of, msm_tlmm_v4_dt_match);
static int msm_tlmm_v4_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
const struct msm_tlmm_pintype *pinfo;
struct msm_tlmm_desc *tlmm_desc;
int irq, ret;
struct resource *res;
struct device_node *node = pdev->dev.of_node;
match = of_match_node(msm_tlmm_v4_dt_match, node);
if (IS_ERR(match))
return PTR_ERR(match);
pinfo = match->data;
tlmm_desc = devm_kzalloc(&pdev->dev, sizeof(*tlmm_desc), GFP_KERNEL);
if (!tlmm_desc) {
dev_err(&pdev->dev, "Alloction failed for tlmm desc\n");
return -ENOMEM;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "cannot find IO resource\n");
return -ENOENT;
}
tlmm_desc->base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (IS_ERR(tlmm_desc->base))
return PTR_ERR(tlmm_desc->base);
tlmm_desc->irq = -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res) {
irq = res->start;
ret = devm_request_irq(&pdev->dev, irq, msm_tlmm_v4_handle_irq,
IRQF_TRIGGER_HIGH,
dev_name(&pdev->dev),
tlmm_desc);
if (ret) {
dev_err(&pdev->dev, "register for irq failed\n");
return ret;
}
tlmm_desc->irq = irq;
}
tlmm_desc->pintypes = pinfo->pintype_info;
tlmm_desc->num_pintypes = pinfo->num_entries;
return msm_pinctrl_probe(pdev, tlmm_desc);
}
static struct platform_driver msm_tlmm_v4_drv = {
.probe = msm_tlmm_v4_probe,
.driver = {
.name = "msm-tlmmv4-pinctrl",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(msm_tlmm_v4_dt_match),
},
};
static int __init msm_tlmm_v4_drv_register(void)
{
return platform_driver_register(&msm_tlmm_v4_drv);
}
postcore_initcall(msm_tlmm_v4_drv_register);
static void __exit msm_tlmm_v4_drv_unregister(void)
{
platform_driver_unregister(&msm_tlmm_v4_drv);
}
module_exit(msm_tlmm_v4_drv_unregister);
MODULE_LICENSE("GPLv2");