blob: 8274bd2d8736c5b23dcbbf4f21708e836f38ac37 [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 <linux/wakeup_reason.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 TLMM_NO_PULL 0
#define TLMM_PULL_DOWN 1
#define TLMM_PULL_UP 3
/* GP PIN TYPE REG MASKS */
#define TLMM_GP_DRV_SHFT 6
#define TLMM_GP_DRV_MASK 0x7
#define TLMM_GP_PULL_SHFT 0
#define TLMM_GP_PULL_MASK 0x3
#define TLMM_GP_DIR_SHFT 9
#define TLMM_GP_DIR_MASK 1
#define TLMM_GP_FUNC_SHFT 2
#define TLMM_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 TLMM_SDC1_CLK_DRV_SHFT 6
#define TLMM_SDC1_CLK_DRV_MASK 0x7
#define TLMM_SDC1_DATA_DRV_SHFT 0
#define TLMM_SDC1_DATA_DRV_MASK 0x7
#define TLMM_SDC1_CMD_DRV_SHFT 3
#define TLMM_SDC1_CMD_DRV_MASK 0x7
#define TLMM_SDC1_CLK_PULL_SHFT 13
#define TLMM_SDC1_CLK_PULL_MASK 0x3
#define TLMM_SDC1_DATA_PULL_SHFT 9
#define TLMM_SDC1_DATA_PULL_MASK 0x3
#define TLMM_SDC1_CMD_PULL_SHFT 11
#define TLMM_SDC1_CMD_PULL_MASK 0x3
#define TLMM_SDC1_RCLK_PULL_SHFT 15
#define TLMM_SDC1_RCLK_PULL_MASK 0x3
/* SDC2 PIN TYPE REG MASKS */
#define TLMM_SDC2_CLK_DRV_SHFT 6
#define TLMM_SDC2_CLK_DRV_MASK 0x7
#define TLMM_SDC2_DATA_DRV_SHFT 0
#define TLMM_SDC2_DATA_DRV_MASK 0x7
#define TLMM_SDC2_CMD_DRV_SHFT 3
#define TLMM_SDC2_CMD_DRV_MASK 0x7
#define TLMM_SDC2_CLK_PULL_SHFT 14
#define TLMM_SDC2_CLK_PULL_MASK 0x3
#define TLMM_SDC2_DATA_PULL_SHFT 9
#define TLMM_SDC2_DATA_PULL_MASK 0x3
#define TLMM_SDC2_CMD_PULL_SHFT 11
#define TLMM_SDC2_CMD_PULL_MASK 0x3
/* SDC 3 PIN TYPE REG MASKS */
#define TLMMV3_SDC3_CLK_DRV_SHFT 6
#define TLMMV3_SDC3_CLK_DRV_MASK 0x7
#define TLMMV3_SDC3_DATA_DRV_SHFT 0
#define TLMMV3_SDC3_DATA_DRV_MASK 0x7
#define TLMMV3_SDC3_CMD_DRV_SHFT 3
#define TLMMV3_SDC3_CMD_DRV_MASK 0x7
#define TLMMV3_SDC3_CLK_PULL_SHFT 14
#define TLMMV3_SDC3_CLK_PULL_MASK 0x3
#define TLMMV3_SDC3_DATA_PULL_SHFT 9
#define TLMMV3_SDC3_DATA_PULL_MASK 0x3
#define TLMMV3_SDC3_CMD_PULL_SHFT 11
#define TLMMV3_SDC3_CMD_PULL_MASK 0x3
/* EBI2 PIN TYPE REG MASKS */
#define TLMM_EBI2_BOOT_SELECT_BIT 0
#define TLMM_EMMC_BOOT_SELECT_BIT 1
#define TLMM_EBI2_CS_PULL_SHFT 2
#define TLMM_EBI2_CS_PULL_MASK 0x3
#define TLMM_EBI2_CS_DRV_SHFT 4
#define TLMM_EBI2_CS_DRV_MASK 0x7
#define TLMM_EBI2_OE_PULL_SHFT 7
#define TLMM_EBI2_OE_PULL_MASK 0x3
#define TLMM_EBI2_OE_DRV_SHFT 9
#define TLMM_EBI2_OE_DRV_MASK 0x7
#define TLMM_EBI2_ALE_PULL_SHFT 12
#define TLMM_EBI2_ALE_PULL_MASK 0x3
#define TLMM_EBI2_ALE_DRV_SHFT 14
#define TLMM_EBI2_ALE_DRV_MASK 0x7
#define TLMM_EBI2_CLE_PULL_SHFT 17
#define TLMM_EBI2_CLE_PULL_MASK 0x3
#define TLMM_EBI2_CLE_DRV_SHFT 19
#define TLMM_EBI2_CLE_DRV_MASK 0x7
#define TLMM_EBI2_WE_PULL_SHFT 22
#define TLMM_EBI2_WE_PULL_MASK 0x3
#define TLMM_EBI2_WE_DRV_SHFT 24
#define TLMM_EBI2_WE_DRV_MASK 0x7
#define TLMM_EBI2_BUSY_PULL_SHFT 27
#define TLMM_EBI2_BUSY_PULL_MASK 0x3
#define TLMM_EBI2_BUSY_DRV_SHFT 29
#define TLMM_EBI2_BUSY_DRV_MASK 0x7
#define TLMM_EBI2_DATA_PULL_SHFT 15
#define TLMM_EBI2_DATA_PULL_MASK 0x3
#define TLMM_EBI2_DATA_DRV_SHFT 17
#define TLMM_EBI2_DATA_DRV_MASK 0x7
/* TLMM 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 TLMM_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 TLMM_GP_INOUT_BIT 1
#define TLMM_GP_OUT BIT(TLMM_GP_INOUT_BIT)
#define TLMM_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)
/* GP pin type register offsets */
#define TLMM_GP_CFG(pi, pin) (pi->reg_base + 0x0 + \
pi->pintype_data->gp_reg_size * (pin))
#define TLMM_GP_INOUT(pi, pin) (pi->reg_base + 0x4 + \
pi->pintype_data->gp_reg_size * (pin))
#define TLMM_GP_INTR_CFG(pi, pin) (pi->reg_base + 0x8 + \
pi->pintype_data->gp_reg_size * (pin))
#define TLMM_GP_INTR_STATUS(pi, pin) (pi->reg_base + 0x0c + \
pi->pintype_data->gp_reg_size * (pin))
/* QDSD Pin type register offsets */
#define TLMMV_QDSD_PULL_MASK 0x3
#define TLMMV4_QDSD_PULL_OFFSET 0x3
#define TLMMV4_QDSD_CONFIG_WIDTH 0x5
#define TLMMV4_QDSD_DRV_MASK 0x7
extern int msm_show_resume_irq_mask;
struct msm_sdc_regs {
unsigned long pull_mask;
unsigned long pull_shft;
unsigned long drv_mask;
unsigned long drv_shft;
};
struct msm_ebi_regs {
unsigned long pull_mask;
unsigned long pull_shft;
unsigned long drv_mask;
unsigned long drv_shft;
};
static const struct msm_sdc_regs sdc_regs[MSM_PINTYPE_SDC_REGS_MAX] = {
/* SDC1 CLK */
{
.pull_mask = TLMM_SDC1_CLK_PULL_MASK,
.pull_shft = TLMM_SDC1_CLK_PULL_SHFT,
.drv_mask = TLMM_SDC1_CLK_DRV_MASK,
.drv_shft = TLMM_SDC1_CLK_DRV_SHFT,
},
/* SDC1 CMD */
{
.pull_mask = TLMM_SDC1_CMD_PULL_MASK,
.pull_shft = TLMM_SDC1_CMD_PULL_SHFT,
.drv_mask = TLMM_SDC1_CMD_DRV_MASK,
.drv_shft = TLMM_SDC1_CMD_DRV_SHFT,
},
/* SDC1 DATA */
{
.pull_mask = TLMM_SDC1_DATA_PULL_MASK,
.pull_shft = TLMM_SDC1_DATA_PULL_SHFT,
.drv_mask = TLMM_SDC1_DATA_DRV_MASK,
.drv_shft = TLMM_SDC1_DATA_DRV_SHFT,
},
/* SDC1 RCLK */
{
.pull_mask = TLMM_SDC1_RCLK_PULL_MASK,
.pull_shft = TLMM_SDC1_RCLK_PULL_SHFT,
},
/* SDC2 CLK */
{
.pull_mask = TLMM_SDC2_CLK_PULL_MASK,
.pull_shft = TLMM_SDC2_CLK_PULL_SHFT,
.drv_mask = TLMM_SDC2_CLK_DRV_MASK,
.drv_shft = TLMM_SDC2_CLK_DRV_SHFT,
},
/* SDC2 CMD */
{
.pull_mask = TLMM_SDC2_CMD_PULL_MASK,
.pull_shft = TLMM_SDC2_CMD_PULL_SHFT,
.drv_mask = TLMM_SDC2_CMD_DRV_MASK,
.drv_shft = TLMM_SDC2_CMD_DRV_SHFT,
},
/* SDC2 DATA */
{
.pull_mask = TLMM_SDC2_DATA_PULL_MASK,
.pull_shft = TLMM_SDC2_DATA_PULL_SHFT,
.drv_mask = TLMM_SDC2_DATA_DRV_MASK,
.drv_shft = TLMM_SDC2_DATA_DRV_SHFT,
},
/* SDC3 CLK */
{
.pull_mask = TLMMV3_SDC3_CLK_PULL_MASK,
.pull_shft = TLMMV3_SDC3_CLK_PULL_SHFT,
.drv_mask = TLMMV3_SDC3_CLK_DRV_MASK,
.drv_shft = TLMMV3_SDC3_CLK_DRV_SHFT,
},
/* SDC3 CMD */
{
.pull_mask = TLMMV3_SDC3_CMD_PULL_MASK,
.pull_shft = TLMMV3_SDC3_CMD_PULL_SHFT,
.drv_mask = TLMMV3_SDC3_CMD_DRV_MASK,
.drv_shft = TLMMV3_SDC3_CMD_DRV_SHFT,
},
/* SDC3 DATA */
{
.pull_mask = TLMMV3_SDC3_DATA_PULL_MASK,
.pull_shft = TLMMV3_SDC3_DATA_PULL_SHFT,
.drv_mask = TLMMV3_SDC3_DATA_DRV_MASK,
.drv_shft = TLMMV3_SDC3_DATA_DRV_SHFT,
},
};
static int msm_tlmm_sdc_cfg(uint pin_no, unsigned long *config,
bool write, const struct msm_pintype_info *pinfo)
{
unsigned int val, id, data;
u32 mask, shft;
void __iomem *cfg_reg;
void __iomem *reg_base = pinfo->reg_base;
const struct msm_pintype_data *sdc_info = pinfo->pintype_data;
s32 offset = sdc_info->sdc_reg_offsets[pin_no];
if (pin_no >= ARRAY_SIZE(sdc_regs))
return -EINVAL;
cfg_reg = reg_base + 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 = TLMM_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 = TLMM_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 = TLMM_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 int msm_tlmm_qdsd_cfg(uint pin_no, unsigned long *config,
bool write, const struct msm_pintype_info *pinfo)
{
unsigned int val, id, data;
u32 mask, shft;
void __iomem *cfg_reg;
cfg_reg = pinfo->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 = TLMMV_QDSD_PULL_MASK;
shft = pin_no * TLMMV4_QDSD_CONFIG_WIDTH
+ TLMMV4_QDSD_PULL_OFFSET;
data = TLMM_NO_PULL;
if (!write) {
val >>= shft;
data = val & mask;
}
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
mask = TLMMV_QDSD_PULL_MASK;
shft = pin_no * TLMMV4_QDSD_CONFIG_WIDTH
+ TLMMV4_QDSD_PULL_OFFSET;
data = TLMM_PULL_DOWN;
if (!write) {
val >>= shft;
data = val & mask;
}
break;
case PIN_CONFIG_BIAS_PULL_UP:
mask = TLMMV_QDSD_PULL_MASK;
shft = pin_no * TLMMV4_QDSD_CONFIG_WIDTH
+ TLMMV4_QDSD_PULL_OFFSET;
data = TLMM_PULL_UP;
if (!write) {
val >>= shft;
data = val & mask;
}
break;
case PIN_CONFIG_DRIVE_STRENGTH:
mask = TLMMV4_QDSD_DRV_MASK;
shft = pin_no * TLMMV4_QDSD_CONFIG_WIDTH;
if (write) {
data = pinconf_to_config_argument(*config);
} else {
val >>= shft;
data = val & mask;
}
break;
default:
return -EINVAL;
};
if (write) {
val &= ~(mask << shft);
/* QDSD software override bit */
val |= ((data << shft) | BIT(31));
writel_relaxed(val, cfg_reg);
} else {
*config = pinconf_to_config_packed(id, data);
}
return 0;
}
static int msm_tlmm_gp_cfg(uint pin_no, unsigned long *config,
bool write, const struct msm_pintype_info *pinfo)
{
unsigned int val, id, data, inout_val;
u32 mask = 0, shft = 0;
void __iomem *inout_reg = NULL;
void __iomem *cfg_reg = TLMM_GP_CFG(pinfo, 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 = TLMM_GP_PULL_MASK;
shft = TLMM_GP_PULL_SHFT;
data = TLMM_NO_PULL;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
mask = TLMM_GP_PULL_MASK;
shft = TLMM_GP_PULL_SHFT;
data = TLMM_PULL_DOWN;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_UP:
mask = TLMM_GP_PULL_MASK;
shft = TLMM_GP_PULL_SHFT;
data = TLMM_PULL_UP;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_DRIVE_STRENGTH:
mask = TLMM_GP_DRV_MASK;
shft = TLMM_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 = TLMM_GP_DIR_MASK;
shft = TLMM_GP_DIR_SHFT;
inout_reg = TLMM_GP_INOUT(pinfo, 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;
case PIN_CONFIG_INPUT_ENABLE:
mask = TLMM_GP_DIR_MASK;
shft = TLMM_GP_DIR_SHFT;
inout_reg = TLMM_GP_INOUT(pinfo, pin_no);
if (write) {
/* GPIO_IN (b0) of TLMM_GPIO_IN_OUT is read-only */
data = 0;
} else {
inout_val = readl_relaxed(inout_reg);
data = 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 const struct msm_ebi_regs ebi_regs[MSM_PINTYPE_EBI_REGS_MAX] = {
/* EBI2 CS*/
{
.pull_mask = TLMM_EBI2_CS_PULL_MASK,
.pull_shft = TLMM_EBI2_CS_PULL_SHFT,
.drv_mask = TLMM_EBI2_CS_DRV_MASK,
.drv_shft = TLMM_EBI2_CS_DRV_SHFT,
},
/* EBI2 OE */
{
.pull_mask = TLMM_EBI2_OE_PULL_MASK,
.pull_shft = TLMM_EBI2_OE_PULL_SHFT,
.drv_mask = TLMM_EBI2_OE_DRV_MASK,
.drv_shft = TLMM_EBI2_OE_DRV_SHFT,
},
/* EBI2 ALE*/
{
.pull_mask = TLMM_EBI2_ALE_PULL_MASK,
.pull_shft = TLMM_EBI2_ALE_PULL_SHFT,
.drv_mask = TLMM_EBI2_ALE_DRV_MASK,
.drv_shft = TLMM_EBI2_ALE_DRV_SHFT,
},
/* EBI2 CLE */
{
.pull_mask = TLMM_EBI2_CLE_PULL_MASK,
.pull_shft = TLMM_EBI2_CLE_PULL_SHFT,
.drv_mask = TLMM_EBI2_CLE_DRV_MASK,
.drv_shft = TLMM_EBI2_CLE_DRV_SHFT,
},
/* EBI2 WE*/
{
.pull_mask = TLMM_EBI2_WE_PULL_MASK,
.pull_shft = TLMM_EBI2_WE_PULL_SHFT,
.drv_mask = TLMM_EBI2_WE_DRV_MASK,
.drv_shft = TLMM_EBI2_WE_DRV_SHFT,
},
/* EBI2 BUSY */
{
.pull_mask = TLMM_EBI2_BUSY_PULL_MASK,
.pull_shft = TLMM_EBI2_BUSY_PULL_SHFT,
.drv_mask = TLMM_EBI2_BUSY_DRV_MASK,
.drv_shft = TLMM_EBI2_BUSY_DRV_SHFT,
},
/* EBI2 DATA */
{
.pull_mask = TLMM_EBI2_DATA_PULL_MASK,
.pull_shft = TLMM_EBI2_DATA_PULL_SHFT,
.drv_mask = TLMM_EBI2_DATA_DRV_MASK,
.drv_shft = TLMM_EBI2_DATA_DRV_SHFT,
},
};
static int msm_tlmm_ebi_cfg(uint pin_no, unsigned long *config,
bool write, const struct msm_pintype_info *pinfo)
{
unsigned int val, id, data;
u32 mask, shft;
void __iomem *cfg_reg;
void __iomem *reg_base = pinfo->reg_base;
const struct msm_pintype_data *ebi_info = pinfo->pintype_data;
s32 offset = ebi_info->ebi_reg_offsets[pin_no];
if (pin_no >= ARRAY_SIZE(ebi_regs))
return -EINVAL;
cfg_reg = reg_base + 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 = ebi_regs[pin_no].pull_mask;
shft = ebi_regs[pin_no].pull_shft;
data = TLMM_NO_PULL;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
mask = ebi_regs[pin_no].pull_mask;
shft = ebi_regs[pin_no].pull_shft;
data = TLMM_PULL_DOWN;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_BIAS_PULL_UP:
mask = ebi_regs[pin_no].pull_mask;
shft = ebi_regs[pin_no].pull_shft;
data = TLMM_PULL_UP;
if (!write) {
val >>= shft;
val &= mask;
data = rval_to_pull(val);
}
break;
case PIN_CONFIG_DRIVE_STRENGTH:
mask = ebi_regs[pin_no].drv_mask;
shft = ebi_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_set_reg_base(void __iomem *tlmm_base,
struct msm_pintype_info *pinfo)
{
pinfo->reg_base = tlmm_base + pinfo->pintype_data->reg_base_offset;
}
static void msm_tlmm_gp_fn(uint pin_no, u32 func, bool enable,
const struct msm_pintype_info *pinfo)
{
unsigned int val;
void __iomem *cfg_reg = TLMM_GP_CFG(pinfo, pin_no);
val = readl_relaxed(cfg_reg);
val &= ~(TLMM_GP_FUNC_MASK << TLMM_GP_FUNC_SHFT);
if (enable)
val |= (func << TLMM_GP_FUNC_SHFT);
writel_relaxed(val, cfg_reg);
}
/* GPIO CHIP */
static int msm_tlmm_gp_get(struct gpio_chip *gc, unsigned offset)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *inout_reg = TLMM_GP_INOUT(pinfo, offset);
return readl_relaxed(inout_reg) & BIT(GPIO_IN_BIT);
}
static void msm_tlmm_gp_set(struct gpio_chip *gc, unsigned offset, int val)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *inout_reg = TLMM_GP_INOUT(pinfo, offset);
writel_relaxed(val ? BIT(GPIO_OUT_BIT) : 0, inout_reg);
}
static int msm_tlmm_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 = TLMM_GP_CFG(pinfo, offset);
val = readl_relaxed(cfg_reg);
val &= ~BIT(GPIO_OE_BIT);
writel_relaxed(val, cfg_reg);
return 0;
}
static int msm_tlmm_gp_dir_out(struct gpio_chip *gc, unsigned offset, int val)
{
struct msm_pintype_info *pinfo = gc_to_pintype(gc);
void __iomem *cfg_reg = TLMM_GP_CFG(pinfo, offset);
msm_tlmm_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_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_set_intr_status(struct msm_tlmm_irq_chip *ic, unsigned pin)
{
const struct msm_pintype_info *pinfo = ic->pinfo;
void __iomem *status_reg = TLMM_GP_INTR_STATUS(pinfo, pin);
writel_relaxed(0, status_reg);
}
static int msm_tlmm_get_intr_status(struct msm_tlmm_irq_chip *ic, unsigned pin)
{
const struct msm_pintype_info *pinfo = ic->pinfo;
void __iomem *status_reg = TLMM_GP_INTR_STATUS(pinfo, pin);
return readl_relaxed(status_reg) & BIT(INTR_STATUS_BIT);
}
static void msm_tlmm_set_intr_cfg_enable(struct msm_tlmm_irq_chip *ic,
unsigned pin, int enable)
{
unsigned int val;
const struct msm_pintype_info *pinfo = ic->pinfo;
void __iomem *cfg_reg = TLMM_GP_INTR_CFG(pinfo, 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_get_intr_cfg_enable(struct msm_tlmm_irq_chip *ic,
unsigned pin)
{
const struct msm_pintype_info *pinfo = ic->pinfo;
void __iomem *cfg_reg = TLMM_GP_INTR_CFG(pinfo, pin);
return readl_relaxed(cfg_reg) & BIT(INTR_ENABLE_BIT);
}
static void msm_tlmm_set_intr_cfg_type(struct msm_tlmm_irq_chip *ic,
struct irq_data *d, unsigned int type)
{
unsigned cfg;
const struct msm_pintype_info *pinfo = ic->pinfo;
void __iomem *cfg_reg = TLMM_GP_INTR_CFG(pinfo, (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_gp_handle_irq(int irq, struct msm_tlmm_irq_chip *ic)
{
unsigned long i;
unsigned int virq = 0;
struct irq_chip *chip;
struct irq_desc *desc = irq_to_desc(irq);
struct msm_pintype_info *pinfo = ic_to_pintype(ic);
struct gpio_chip *gc = pintype_get_gc(pinfo);
if (unlikely(!desc))
return IRQ_HANDLED;
chip = irq_desc_get_chip(desc);
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_get_intr_status(ic, i)) {
dev_dbg(ic->dev, "hwirw %d fired\n", (unsigned int)i);
virq = msm_tlmm_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_irq_ack(struct irq_data *d)
{
struct msm_tlmm_irq_chip *ic = irq_data_get_irq_chip_data(d);
msm_tlmm_set_intr_status(ic, irqd_to_hwirq(d));
mb();
}
static void msm_tlmm_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_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_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_get_intr_cfg_enable(ic, irqd_to_hwirq(d))) {
msm_tlmm_set_intr_status(ic, irqd_to_hwirq(d));
msm_tlmm_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_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_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_set_intr_cfg_type(ic, d, flow_type);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
if (ic->irq_chip_extn->irq_set_type)
ic->irq_chip_extn->irq_set_type(d, flow_type);
return 0;
}
static int msm_tlmm_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_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_gp_irqd_ops = {
.map = msm_tlmm_irq_map,
.xlate = irq_domain_xlate_twocell,
};
static struct irq_chip mpm_tlmm_irq_extn;
static struct msm_tlmm_irq_chip msm_tlmm_gp_irq = {
.irq_chip_extn = &mpm_tlmm_irq_extn,
.chip = {
.name = "msm_tlmm_irq",
.irq_mask = msm_tlmm_irq_mask,
.irq_unmask = msm_tlmm_irq_unmask,
.irq_ack = msm_tlmm_irq_ack,
.irq_set_type = msm_tlmm_irq_set_type,
.irq_set_wake = msm_tlmm_irq_set_wake,
.irq_disable = msm_tlmm_irq_disable,
},
.apps_id = TLMM_APPS_ID_DEFAULT,
.domain_ops = &msm_tlmm_gp_irqd_ops,
.handler = msm_tlmm_gp_handle_irq,
};
/* Power management core operations */
static int msm_tlmm_gp_irq_suspend(void)
{
unsigned long irq_flags;
unsigned long i;
struct msm_tlmm_irq_chip *ic = &msm_tlmm_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_set_intr_cfg_enable(ic, i, 0);
for_each_set_bit(i, ic->wake_irqs, num_irqs)
msm_tlmm_set_intr_cfg_enable(ic, i, 1);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
return 0;
}
void msm_tlmm_show_gp_irq_resume(void)
{
unsigned long irq_flags;
int i, irq, intstat;
struct msm_tlmm_irq_chip *ic = &msm_tlmm_gp_irq;
int num_irqs = ic->num_irqs;
if (!msm_show_resume_irq_mask)
return;
spin_lock_irqsave(&ic->irq_lock, irq_flags);
for_each_set_bit(i, ic->wake_irqs, num_irqs) {
intstat = msm_tlmm_get_intr_status(ic, i);
if (intstat) {
struct irq_desc *desc;
const char *name = "null";
irq = irq_create_mapping(ic->domain, i);
log_base_wakeup_reason(irq);
desc = irq_to_desc(irq);
if (desc != NULL &&
desc->action && desc->action->name) {
name = desc->action->name;
pr_warning("%s: %d triggered %s\n",
__func__, irq, name);
}
}
}
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
}
static void msm_tlmm_gp_irq_resume(void)
{
unsigned long irq_flags;
unsigned long i;
struct msm_tlmm_irq_chip *ic = &msm_tlmm_gp_irq;
int num_irqs = ic->num_irqs;
msm_tlmm_show_gp_irq_resume();
spin_lock_irqsave(&ic->irq_lock, irq_flags);
for_each_set_bit(i, ic->wake_irqs, num_irqs)
msm_tlmm_set_intr_cfg_enable(ic, i, 0);
for_each_set_bit(i, ic->enabled_irqs, num_irqs)
msm_tlmm_set_intr_cfg_enable(ic, i, 1);
mb();
spin_unlock_irqrestore(&ic->irq_lock, irq_flags);
}
static struct syscore_ops msm_tlmm_irq_syscore_ops = {
.suspend = msm_tlmm_gp_irq_suspend,
.resume = msm_tlmm_gp_irq_resume,
};
#ifdef CONFIG_USE_PINCTRL_IRQ
int msm_tlmm_of_gp_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_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_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;
if (!ic->domain)
return 0;
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 dual edge 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_irq_syscore_ops);
return 0;
}
static irqreturn_t msm_tlmm_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_pininfo[] = {
{
.prg_cfg = msm_tlmm_gp_cfg,
.prg_func = msm_tlmm_gp_fn,
.set_reg_base = msm_tlmm_set_reg_base,
.gc = {
.label = "msm_tlmm_gpio",
.direction_input = msm_tlmm_gp_dir_in,
.direction_output = msm_tlmm_gp_dir_out,
.get = msm_tlmm_gp_get,
.set = msm_tlmm_gp_set,
.to_irq = msm_tlmm_gp_to_irq,
},
.init_irq = msm_tlmm_gp_irq_init,
.irq_chip = &msm_tlmm_gp_irq,
.name = "gp",
},
{
.prg_cfg = msm_tlmm_sdc_cfg,
.set_reg_base = msm_tlmm_set_reg_base,
.name = "sdc",
},
{
.prg_cfg = msm_tlmm_qdsd_cfg,
.set_reg_base = msm_tlmm_set_reg_base,
.name = "qdsd",
},
{
.prg_cfg = msm_tlmm_ebi_cfg,
.set_reg_base = msm_tlmm_set_reg_base,
.name = "ebi",
}
};
#define DECLARE_PINTYPE_DATA_GP(name, offset, regsize) \
static const struct msm_pintype_data name = { \
.reg_base_offset = offset, \
.gp_reg_size = regsize, \
}
#define DECLARE_PINTYPE_DATA_SDC(name, offset, offsets) \
static const struct msm_pintype_data name = { \
.reg_base_offset = offset, \
.sdc_reg_offsets = offsets, \
}
#define DECLARE_PINTYPE_DATA_QDSD(name, offset) \
static const struct msm_pintype_data name = { \
.reg_base_offset = offset, \
}
#define DECLARE_PINTYPE_DATA_EBI(name, offset, offsets) \
static const struct msm_pintype_data name = { \
.reg_base_offset = offset, \
.ebi_reg_offsets = offsets, \
}
#define ARG_PROTECT(...) __VA_ARGS__
DECLARE_PINTYPE_DATA_GP(gp_data_8974, 0x1000, 0x10);
DECLARE_PINTYPE_DATA_GP(gp_data_8916, 0x0, 0x1000);
DECLARE_PINTYPE_DATA_SDC(sdc_data_8974, 0x2044,
ARG_PROTECT({0, 0, 0, 0, 0x4, 0x4, 0x4}));
DECLARE_PINTYPE_DATA_SDC(sdc_data_8994, 0x2044,
ARG_PROTECT({0, 0, 0, 0, 0x4, 0x4, 0x4, 0x28, 0x28, 0x28}));
DECLARE_PINTYPE_DATA_SDC(sdc_data_8916, 0x109000,
ARG_PROTECT({0x1000, 0x1000, 0x1000, 0x1000, 0, 0, 0})
);
DECLARE_PINTYPE_DATA_QDSD(qdsd_data, 0x19C000);
DECLARE_PINTYPE_DATA_EBI(ebi_data, 0x10A000,
ARG_PROTECT({0x7000, 0x7000, 0x7000, 0x7000,
0x7000, 0x7000, 0}));
#undef ARG_PROTECT
static const struct msm_pintype_data *pintype_data_8974[MSM_PINTYPE_MAX] = {
&gp_data_8974, &sdc_data_8974, &qdsd_data,
};
static const struct msm_pintype_data *pintype_data_8916[MSM_PINTYPE_MAX] = {
&gp_data_8916, &sdc_data_8916, &qdsd_data, &ebi_data,
};
static const struct msm_pintype_data *pintype_data_8994[MSM_PINTYPE_MAX] = {
&gp_data_8974, &sdc_data_8994, &qdsd_data,
};
static const struct of_device_id msm_tlmm_dt_match[] = {
{ .compatible = "qcom,msm-tlmm-8994", .data = &pintype_data_8994, },
{ .compatible = "qcom,msm-tlmm-8974", .data = &pintype_data_8974, },
{ .compatible = "qcom,msm-tlmm-8916", .data = &pintype_data_8916, },
{ },
};
MODULE_DEVICE_TABLE(of, msm_tlmm_dt_match);
static int msm_tlmm_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct msm_tlmm_desc *tlmm_desc;
int irq, ret;
struct resource *res;
int i;
const struct msm_pintype_data **pintype_data;
struct device_node *node = pdev->dev.of_node;
match = of_match_node(msm_tlmm_dt_match, node);
if (IS_ERR(match))
return PTR_ERR(match);
else if (!match)
return -ENODEV;
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_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;
}
pintype_data = (const struct msm_pintype_data **)match->data;
for (i = 0; i < MSM_PINTYPE_MAX; i++)
tlmm_pininfo[i].pintype_data = pintype_data[i];
tlmm_desc->pintypes = tlmm_pininfo;
tlmm_desc->num_pintypes = ARRAY_SIZE(tlmm_pininfo);
return msm_pinctrl_probe(pdev, tlmm_desc);
}
static struct platform_driver msm_tlmm_drv = {
.probe = msm_tlmm_probe,
.driver = {
.name = "msm-tlmm-pinctrl",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(msm_tlmm_dt_match),
},
};
static int __init msm_tlmm_drv_register(void)
{
return platform_driver_register(&msm_tlmm_drv);
}
postcore_initcall(msm_tlmm_drv_register);
static void __exit msm_tlmm_drv_unregister(void)
{
platform_driver_unregister(&msm_tlmm_drv);
}
module_exit(msm_tlmm_drv_unregister);
MODULE_LICENSE("GPL v2");