| /* 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/interrupt.h> |
| #include <linux/types.h> |
| #include <linux/spmi.h> |
| #include <linux/platform_device.h> |
| #include <linux/debugfs.h> |
| #include <linux/gpio.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_irq.h> |
| #include <linux/export.h> |
| #include <linux/module.h> |
| #include <linux/export.h> |
| #include <linux/qpnp/pin.h> |
| |
| #define Q_REG_ADDR(q_spec, reg_index) \ |
| ((q_spec)->offset + reg_index) |
| |
| #define Q_REG_STATUS1 0x8 |
| #define Q_NUM_CTL_REGS 0xD |
| |
| /* type registers base address offsets */ |
| #define Q_REG_TYPE 0x4 |
| #define Q_REG_SUBTYPE 0x5 |
| |
| /* gpio peripheral type and subtype values */ |
| #define Q_GPIO_TYPE 0x10 |
| #define Q_GPIO_SUBTYPE_GPIO_4CH 0x0 |
| #define Q_GPIO_SUBTYPE_GPIOC_4CH 0x2 |
| #define Q_GPIO_SUBTYPE_GPIO_8CH 0x4 |
| #define Q_GPIO_SUBTYPE_GPIOC_8CH 0x6 |
| |
| /* mpp peripheral type and subtype values */ |
| #define Q_MPP_TYPE 0x11 |
| #define Q_MPP_SUBTYPE_4CH_NO_ANA_OUT 0x3 |
| #define Q_MPP_SUBTYPE_4CH_NO_SINK 0x5 |
| #define Q_MPP_SUBTYPE_4CH_FULL_FUNC 0x2 |
| #define Q_MPP_SUBTYPE_8CH_FULL_FUNC 0x4 |
| |
| /* control register base address offsets */ |
| #define Q_REG_MODE_CTL 0x40 |
| #define Q_REG_DIG_VIN_CTL 0x41 |
| #define Q_REG_DIG_PULL_CTL 0x42 |
| #define Q_REG_DIG_IN_CTL 0x43 |
| #define Q_REG_DIG_OUT_CTL 0x45 |
| #define Q_REG_EN_CTL 0x46 |
| #define Q_REG_AOUT_CTL 0x48 |
| #define Q_REG_AIN_CTL 0x4A |
| #define Q_REG_SINK_CTL 0x4C |
| |
| /* control register regs array indices */ |
| #define Q_REG_I_MODE_CTL 0 |
| #define Q_REG_I_DIG_VIN_CTL 1 |
| #define Q_REG_I_DIG_PULL_CTL 2 |
| #define Q_REG_I_DIG_IN_CTL 3 |
| #define Q_REG_I_DIG_OUT_CTL 5 |
| #define Q_REG_I_EN_CTL 6 |
| #define Q_REG_I_AOUT_CTL 8 |
| #define Q_REG_I_AIN_CTL 10 |
| #define Q_REG_I_SINK_CTL 12 |
| |
| /* control reg: mode */ |
| #define Q_REG_OUT_INVERT_SHIFT 0 |
| #define Q_REG_OUT_INVERT_MASK 0x1 |
| #define Q_REG_SRC_SEL_SHIFT 1 |
| #define Q_REG_SRC_SEL_MASK 0xE |
| #define Q_REG_MODE_SEL_SHIFT 4 |
| #define Q_REG_MODE_SEL_MASK 0x70 |
| |
| /* control reg: dig_vin */ |
| #define Q_REG_VIN_SHIFT 0 |
| #define Q_REG_VIN_MASK 0x7 |
| |
| /* control reg: dig_pull */ |
| #define Q_REG_PULL_SHIFT 0 |
| #define Q_REG_PULL_MASK 0x7 |
| |
| /* control reg: dig_out */ |
| #define Q_REG_OUT_STRENGTH_SHIFT 0 |
| #define Q_REG_OUT_STRENGTH_MASK 0x3 |
| #define Q_REG_OUT_TYPE_SHIFT 4 |
| #define Q_REG_OUT_TYPE_MASK 0x30 |
| |
| /* control reg: en */ |
| #define Q_REG_MASTER_EN_SHIFT 7 |
| #define Q_REG_MASTER_EN_MASK 0x80 |
| |
| /* control reg: ana_out */ |
| #define Q_REG_AOUT_REF_SHIFT 0 |
| #define Q_REG_AOUT_REF_MASK 0x7 |
| |
| /* control reg: ana_in */ |
| #define Q_REG_AIN_ROUTE_SHIFT 0 |
| #define Q_REG_AIN_ROUTE_MASK 0x7 |
| |
| /* control reg: sink */ |
| #define Q_REG_CS_OUT_SHIFT 0 |
| #define Q_REG_CS_OUT_MASK 0x7 |
| |
| enum qpnp_pin_param_type { |
| Q_PIN_CFG_MODE, |
| Q_PIN_CFG_OUTPUT_TYPE, |
| Q_PIN_CFG_INVERT, |
| Q_PIN_CFG_PULL, |
| Q_PIN_CFG_VIN_SEL, |
| Q_PIN_CFG_OUT_STRENGTH, |
| Q_PIN_CFG_SELECT, |
| Q_PIN_CFG_MASTER_EN, |
| Q_PIN_CFG_AOUT_REF, |
| Q_PIN_CFG_AIN_ROUTE, |
| Q_PIN_CFG_CS_OUT, |
| Q_PIN_CFG_INVALID, |
| }; |
| |
| #define Q_NUM_PARAMS Q_PIN_CFG_INVALID |
| |
| /* param error checking */ |
| #define QPNP_PIN_MODE_INVALID 3 |
| #define QPNP_PIN_INVERT_INVALID 2 |
| #define QPNP_PIN_OUT_BUF_INVALID 3 |
| #define QPNP_PIN_VIN_4CH_INVALID 5 |
| #define QPNP_PIN_VIN_8CH_INVALID 8 |
| #define QPNP_PIN_GPIO_PULL_INVALID 6 |
| #define QPNP_PIN_MPP_PULL_INVALID 4 |
| #define QPNP_PIN_OUT_STRENGTH_INVALID 4 |
| #define QPNP_PIN_SRC_INVALID 8 |
| #define QPNP_PIN_MASTER_INVALID 2 |
| #define QPNP_PIN_AOUT_REF_INVALID 8 |
| #define QPNP_PIN_AIN_ROUTE_INVALID 8 |
| #define QPNP_PIN_CS_OUT_INVALID 8 |
| |
| struct qpnp_pin_spec { |
| uint8_t slave; /* 0-15 */ |
| uint16_t offset; /* 0-255 */ |
| uint32_t gpio_chip_idx; /* offset from gpio_chip base */ |
| uint32_t pmic_pin; /* PMIC pin number */ |
| int irq; /* logical IRQ number */ |
| u8 regs[Q_NUM_CTL_REGS]; /* Control regs */ |
| u8 num_ctl_regs; /* usable number on this pin */ |
| u8 type; /* peripheral type */ |
| u8 subtype; /* peripheral subtype */ |
| struct device_node *node; |
| enum qpnp_pin_param_type params[Q_NUM_PARAMS]; |
| struct qpnp_pin_chip *q_chip; |
| }; |
| |
| struct qpnp_pin_chip { |
| struct gpio_chip gpio_chip; |
| struct spmi_device *spmi; |
| struct qpnp_pin_spec **pmic_pins; |
| struct qpnp_pin_spec **chip_gpios; |
| uint32_t pmic_pin_lowest; |
| uint32_t pmic_pin_highest; |
| struct device_node *int_ctrl; |
| struct list_head chip_list; |
| struct dentry *dfs_dir; |
| }; |
| |
| static LIST_HEAD(qpnp_pin_chips); |
| static DEFINE_MUTEX(qpnp_pin_chips_lock); |
| |
| static inline void qpnp_pmic_pin_set_spec(struct qpnp_pin_chip *q_chip, |
| uint32_t pmic_pin, |
| struct qpnp_pin_spec *spec) |
| { |
| q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest] = spec; |
| } |
| |
| static inline struct qpnp_pin_spec *qpnp_pmic_pin_get_spec( |
| struct qpnp_pin_chip *q_chip, |
| uint32_t pmic_pin) |
| { |
| if (pmic_pin < q_chip->pmic_pin_lowest || |
| pmic_pin > q_chip->pmic_pin_highest) |
| return NULL; |
| |
| return q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest]; |
| } |
| |
| static inline struct qpnp_pin_spec *qpnp_chip_gpio_get_spec( |
| struct qpnp_pin_chip *q_chip, |
| uint32_t chip_gpio) |
| { |
| if (chip_gpio > q_chip->gpio_chip.ngpio) |
| return NULL; |
| |
| return q_chip->chip_gpios[chip_gpio]; |
| } |
| |
| static inline void qpnp_chip_gpio_set_spec(struct qpnp_pin_chip *q_chip, |
| uint32_t chip_gpio, |
| struct qpnp_pin_spec *spec) |
| { |
| q_chip->chip_gpios[chip_gpio] = spec; |
| } |
| |
| /* |
| * Determines whether a specified param's configuration is correct. |
| * This check is two tier. First a check is done whether the hardware |
| * supports this param and value requested. The second check validates |
| * that the configuration is correct, given the fact that the hardware |
| * supports it. |
| * |
| * Returns |
| * -ENXIO is the hardware does not support this param. |
| * -EINVAL if the the hardware does support this param, but the |
| * requested value is outside the supported range. |
| */ |
| static int qpnp_pin_check_config(enum qpnp_pin_param_type idx, |
| struct qpnp_pin_spec *q_spec, uint32_t val) |
| { |
| switch (idx) { |
| case Q_PIN_CFG_MODE: |
| if (val >= QPNP_PIN_MODE_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_OUTPUT_TYPE: |
| if (q_spec->type != Q_GPIO_TYPE) |
| return -ENXIO; |
| if ((val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_NMOS || |
| val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_PMOS) && |
| (q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_4CH || |
| (q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_8CH))) |
| return -EINVAL; |
| else if (val >= QPNP_PIN_OUT_BUF_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_INVERT: |
| if (val >= QPNP_PIN_INVERT_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_PULL: |
| if (q_spec->type == Q_GPIO_TYPE && |
| val >= QPNP_PIN_GPIO_PULL_INVALID) |
| return -EINVAL; |
| if (q_spec->type == Q_MPP_TYPE && |
| val >= QPNP_PIN_MPP_PULL_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_VIN_SEL: |
| if (val >= QPNP_PIN_VIN_8CH_INVALID) |
| return -EINVAL; |
| else if (val >= QPNP_PIN_VIN_4CH_INVALID) { |
| if (q_spec->type == Q_GPIO_TYPE && |
| (q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_4CH || |
| q_spec->subtype == Q_GPIO_SUBTYPE_GPIOC_4CH)) |
| return -EINVAL; |
| if (q_spec->type == Q_MPP_TYPE && |
| (q_spec->subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT || |
| q_spec->subtype == Q_MPP_SUBTYPE_4CH_NO_SINK || |
| q_spec->subtype == Q_MPP_SUBTYPE_4CH_FULL_FUNC)) |
| return -EINVAL; |
| } |
| break; |
| case Q_PIN_CFG_OUT_STRENGTH: |
| if (q_spec->type != Q_GPIO_TYPE) |
| return -ENXIO; |
| if (val >= QPNP_PIN_OUT_STRENGTH_INVALID || |
| val == 0) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_SELECT: |
| if (q_spec->type == Q_MPP_TYPE && |
| (val == QPNP_PIN_SEL_FUNC_1 || |
| val == QPNP_PIN_SEL_FUNC_2)) |
| return -EINVAL; |
| if (val >= QPNP_PIN_SRC_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_MASTER_EN: |
| if (val >= QPNP_PIN_MASTER_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_AOUT_REF: |
| if (q_spec->type != Q_MPP_TYPE) |
| return -ENXIO; |
| if (q_spec->subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT) |
| return -ENXIO; |
| if (val >= QPNP_PIN_AOUT_REF_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_AIN_ROUTE: |
| if (q_spec->type != Q_MPP_TYPE) |
| return -ENXIO; |
| if (val >= QPNP_PIN_AIN_ROUTE_INVALID) |
| return -EINVAL; |
| break; |
| case Q_PIN_CFG_CS_OUT: |
| if (q_spec->type != Q_MPP_TYPE) |
| return -ENXIO; |
| if (q_spec->subtype == Q_MPP_SUBTYPE_4CH_NO_SINK) |
| return -ENXIO; |
| if (val >= QPNP_PIN_CS_OUT_INVALID) |
| return -EINVAL; |
| break; |
| |
| default: |
| pr_err("invalid param type %u specified\n", idx); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| #define Q_CHK_INVALID(idx, q_spec, val) \ |
| (qpnp_pin_check_config(idx, q_spec, val) == -EINVAL) |
| |
| static int qpnp_pin_check_constraints(struct qpnp_pin_spec *q_spec, |
| struct qpnp_pin_cfg *param) |
| { |
| int pin = q_spec->pmic_pin; |
| const char *name; |
| |
| name = (q_spec->type == Q_GPIO_TYPE) ? "gpio" : "mpp"; |
| |
| if (Q_CHK_INVALID(Q_PIN_CFG_MODE, q_spec, param->mode)) |
| pr_err("invalid direction for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_INVERT, q_spec, param->invert)) |
| pr_err("invalid invert polarity for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_SELECT, q_spec, param->select)) |
| pr_err("invalid source select for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_OUT_STRENGTH, |
| q_spec, param->out_strength)) |
| pr_err("invalid out strength for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_OUTPUT_TYPE, |
| q_spec, param->output_type)) |
| pr_err("invalid out type for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel)) |
| pr_err("invalid vin select value for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_PULL, q_spec, param->pull)) |
| pr_err("invalid pull value for pin %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en)) |
| pr_err("invalid master_en value for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref)) |
| pr_err("invalid aout_reg value for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route)) |
| pr_err("invalid ain_route value for %s %d\n", name, pin); |
| else if (Q_CHK_INVALID(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out)) |
| pr_err("invalid cs_out value for %s %d\n", name, pin); |
| else |
| return 0; |
| |
| return -EINVAL; |
| } |
| |
| static inline u8 q_reg_get(u8 *reg, int shift, int mask) |
| { |
| return (*reg & mask) >> shift; |
| } |
| |
| static inline void q_reg_set(u8 *reg, int shift, int mask, int value) |
| { |
| *reg |= (value << shift) & mask; |
| } |
| |
| static inline void q_reg_clr_set(u8 *reg, int shift, int mask, int value) |
| { |
| *reg &= ~mask; |
| *reg |= (value << shift) & mask; |
| } |
| |
| /* |
| * Calculate the minimum number of registers that must be read / written |
| * in order to satisfy the full feature set of the given pin. |
| */ |
| static int qpnp_pin_ctl_regs_init(struct qpnp_pin_spec *q_spec) |
| { |
| if (q_spec->type == Q_GPIO_TYPE) |
| q_spec->num_ctl_regs = 7; |
| else if (q_spec->type == Q_MPP_TYPE) |
| switch (q_spec->subtype) { |
| case Q_MPP_SUBTYPE_4CH_NO_SINK: |
| q_spec->num_ctl_regs = 12; |
| break; |
| case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT: |
| case Q_MPP_SUBTYPE_4CH_FULL_FUNC: |
| case Q_MPP_SUBTYPE_8CH_FULL_FUNC: |
| q_spec->num_ctl_regs = 13; |
| break; |
| default: |
| pr_err("Invalid MPP subtype 0x%x\n", q_spec->subtype); |
| return -EINVAL; |
| } |
| else { |
| pr_err("Invalid type 0x%x\n", q_spec->type); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int qpnp_pin_read_regs(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec, u16 addr, u8 *buf) |
| { |
| int bytes_left = q_spec->num_ctl_regs; |
| int rc; |
| char *reg_p = &q_spec->regs[0]; |
| |
| while (bytes_left > 0) { |
| rc = spmi_ext_register_readl(q_chip->spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, Q_REG_MODE_CTL), |
| reg_p, bytes_left < 8 ? bytes_left : 8); |
| if (rc) |
| return rc; |
| bytes_left -= 8; |
| reg_p += 8; |
| } |
| return 0; |
| } |
| |
| static int qpnp_pin_write_regs(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec, u16 addr, u8 *buf) |
| { |
| int bytes_left = q_spec->num_ctl_regs; |
| int rc; |
| char *reg_p = &q_spec->regs[0]; |
| |
| while (bytes_left > 0) { |
| rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, Q_REG_MODE_CTL), |
| reg_p, bytes_left < 8 ? bytes_left : 8); |
| if (rc) |
| return rc; |
| bytes_left -= 8; |
| reg_p += 8; |
| } |
| return 0; |
| } |
| |
| static int qpnp_pin_cache_regs(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec) |
| { |
| int rc; |
| struct device *dev = &q_chip->spmi->dev; |
| |
| rc = qpnp_pin_read_regs(q_chip, q_spec, |
| Q_REG_ADDR(q_spec, Q_REG_MODE_CTL), |
| &q_spec->regs[Q_REG_I_MODE_CTL]); |
| if (rc) |
| dev_err(dev, "%s: unable to read control regs\n", __func__); |
| |
| return rc; |
| } |
| |
| #define Q_HAVE_HW_SP(idx, q_spec, val) \ |
| (qpnp_pin_check_config(idx, q_spec, val) == 0) |
| |
| static int _qpnp_pin_config(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec, |
| struct qpnp_pin_cfg *param) |
| { |
| struct device *dev = &q_chip->spmi->dev; |
| int rc; |
| |
| rc = qpnp_pin_check_constraints(q_spec, param); |
| if (rc) |
| goto gpio_cfg; |
| |
| /* set mode */ |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_MODE, q_spec, param->mode)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_MODE_SEL_SHIFT, Q_REG_MODE_SEL_MASK, |
| param->mode); |
| |
| /* output specific configuration */ |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_INVERT, q_spec, param->invert)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_OUT_INVERT_SHIFT, Q_REG_OUT_INVERT_MASK, |
| param->invert); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_SELECT, q_spec, param->select)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_SRC_SEL_SHIFT, Q_REG_SRC_SEL_MASK, |
| param->select); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_OUT_STRENGTH, q_spec, param->out_strength)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], |
| Q_REG_OUT_STRENGTH_SHIFT, Q_REG_OUT_STRENGTH_MASK, |
| param->out_strength); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_OUTPUT_TYPE, q_spec, param->output_type)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], |
| Q_REG_OUT_TYPE_SHIFT, Q_REG_OUT_TYPE_MASK, |
| param->output_type); |
| |
| /* config applicable for both input / output */ |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_VIN_CTL], |
| Q_REG_VIN_SHIFT, Q_REG_VIN_MASK, |
| param->vin_sel); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_PULL, q_spec, param->pull)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_PULL_CTL], |
| Q_REG_PULL_SHIFT, Q_REG_PULL_MASK, |
| param->pull); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_EN_CTL], |
| Q_REG_MASTER_EN_SHIFT, Q_REG_MASTER_EN_MASK, |
| param->master_en); |
| |
| /* mpp specific config */ |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_AOUT_CTL], |
| Q_REG_AOUT_REF_SHIFT, Q_REG_AOUT_REF_MASK, |
| param->aout_ref); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_AIN_CTL], |
| Q_REG_AIN_ROUTE_SHIFT, Q_REG_AIN_ROUTE_MASK, |
| param->ain_route); |
| if (Q_HAVE_HW_SP(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out)) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_SINK_CTL], |
| Q_REG_CS_OUT_SHIFT, Q_REG_CS_OUT_MASK, |
| param->cs_out); |
| |
| rc = qpnp_pin_write_regs(q_chip, q_spec, |
| Q_REG_ADDR(q_spec, Q_REG_MODE_CTL), |
| &q_spec->regs[Q_REG_I_MODE_CTL]); |
| if (rc) { |
| dev_err(&q_chip->spmi->dev, "%s: unable to write master enable\n", |
| __func__); |
| goto gpio_cfg; |
| } |
| |
| return 0; |
| |
| gpio_cfg: |
| dev_err(dev, "%s: unable to set default config for pmic gpio %d\n", |
| __func__, q_spec->pmic_pin); |
| |
| return rc; |
| } |
| |
| int qpnp_pin_config(int gpio, struct qpnp_pin_cfg *param) |
| { |
| int rc, chip_offset; |
| struct qpnp_pin_chip *q_chip; |
| struct qpnp_pin_spec *q_spec = NULL; |
| struct gpio_chip *gpio_chip; |
| |
| if (param == NULL) |
| return -EINVAL; |
| |
| mutex_lock(&qpnp_pin_chips_lock); |
| list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) { |
| gpio_chip = &q_chip->gpio_chip; |
| if (gpio >= gpio_chip->base |
| && gpio < gpio_chip->base + gpio_chip->ngpio) { |
| chip_offset = gpio - gpio_chip->base; |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset); |
| if (WARN_ON(!q_spec)) { |
| mutex_unlock(&qpnp_pin_chips_lock); |
| return -ENODEV; |
| } |
| break; |
| } |
| } |
| mutex_unlock(&qpnp_pin_chips_lock); |
| |
| rc = _qpnp_pin_config(q_chip, q_spec, param); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(qpnp_pin_config); |
| |
| #define Q_MAX_CHIP_NAME 128 |
| int qpnp_pin_map(const char *name, uint32_t pmic_pin) |
| { |
| struct qpnp_pin_chip *q_chip; |
| struct qpnp_pin_spec *q_spec = NULL; |
| |
| mutex_lock(&qpnp_pin_chips_lock); |
| list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) { |
| if (strncmp(q_chip->gpio_chip.label, name, |
| Q_MAX_CHIP_NAME) != 0) |
| continue; |
| if (q_chip->pmic_pin_lowest <= pmic_pin && |
| q_chip->pmic_pin_highest >= pmic_pin) { |
| q_spec = qpnp_pmic_pin_get_spec(q_chip, pmic_pin); |
| mutex_unlock(&qpnp_pin_chips_lock); |
| if (WARN_ON(!q_spec)) |
| return -ENODEV; |
| return q_chip->gpio_chip.base + q_spec->gpio_chip_idx; |
| } |
| } |
| mutex_unlock(&qpnp_pin_chips_lock); |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(qpnp_pin_map); |
| |
| static int qpnp_pin_to_irq(struct gpio_chip *gpio_chip, unsigned offset) |
| { |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev); |
| struct qpnp_pin_spec *q_spec; |
| |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); |
| if (!q_spec) |
| return -EINVAL; |
| |
| return q_spec->irq; |
| } |
| |
| static int qpnp_pin_get(struct gpio_chip *gpio_chip, unsigned offset) |
| { |
| int rc, ret_val; |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev); |
| struct qpnp_pin_spec *q_spec = NULL; |
| u8 buf[1]; |
| |
| if (WARN_ON(!q_chip)) |
| return -ENODEV; |
| |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); |
| if (WARN_ON(!q_spec)) |
| return -ENODEV; |
| |
| /* gpio val is from RT status iff input is enabled */ |
| if ((q_spec->regs[Q_REG_I_MODE_CTL] & Q_REG_MODE_SEL_MASK) |
| == QPNP_PIN_MODE_DIG_IN) { |
| /* INT_RT_STS */ |
| rc = spmi_ext_register_readl(q_chip->spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, Q_REG_STATUS1), |
| &buf[0], 1); |
| return buf[0]; |
| |
| } else { |
| ret_val = (q_spec->regs[Q_REG_I_MODE_CTL] & |
| Q_REG_OUT_INVERT_MASK) >> Q_REG_OUT_INVERT_SHIFT; |
| return ret_val; |
| } |
| |
| return 0; |
| } |
| |
| static int __qpnp_pin_set(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec, int value) |
| { |
| int rc; |
| |
| if (!q_chip || !q_spec) |
| return -EINVAL; |
| |
| if (value) |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_OUT_INVERT_SHIFT, Q_REG_OUT_INVERT_MASK, 1); |
| else |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_OUT_INVERT_SHIFT, Q_REG_OUT_INVERT_MASK, 0); |
| |
| rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, Q_REG_I_MODE_CTL), |
| &q_spec->regs[Q_REG_I_MODE_CTL], 1); |
| if (rc) |
| dev_err(&q_chip->spmi->dev, "%s: spmi write failed\n", |
| __func__); |
| return rc; |
| } |
| |
| |
| static void qpnp_pin_set(struct gpio_chip *gpio_chip, |
| unsigned offset, int value) |
| { |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev); |
| struct qpnp_pin_spec *q_spec; |
| |
| if (WARN_ON(!q_chip)) |
| return; |
| |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); |
| if (WARN_ON(!q_spec)) |
| return; |
| |
| __qpnp_pin_set(q_chip, q_spec, value); |
| } |
| |
| static int qpnp_pin_set_mode(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec, int mode) |
| { |
| int rc; |
| |
| if (!q_chip || !q_spec) |
| return -EINVAL; |
| |
| if (mode >= QPNP_PIN_MODE_INVALID) { |
| pr_err("invalid mode specification %d\n", mode); |
| return -EINVAL; |
| } |
| |
| q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_MODE_SEL_SHIFT, |
| Q_REG_MODE_SEL_MASK, |
| mode); |
| |
| rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, Q_REG_I_MODE_CTL), |
| &q_spec->regs[Q_REG_I_MODE_CTL], 1); |
| return rc; |
| } |
| |
| static int qpnp_pin_direction_input(struct gpio_chip *gpio_chip, |
| unsigned offset) |
| { |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev); |
| struct qpnp_pin_spec *q_spec; |
| |
| if (WARN_ON(!q_chip)) |
| return -ENODEV; |
| |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); |
| if (WARN_ON(!q_spec)) |
| return -ENODEV; |
| |
| return qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_IN); |
| } |
| |
| static int qpnp_pin_direction_output(struct gpio_chip *gpio_chip, |
| unsigned offset, |
| int val) |
| { |
| int rc; |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev); |
| struct qpnp_pin_spec *q_spec; |
| |
| if (WARN_ON(!q_chip)) |
| return -ENODEV; |
| |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, offset); |
| if (WARN_ON(!q_spec)) |
| return -ENODEV; |
| |
| rc = __qpnp_pin_set(q_chip, q_spec, val); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_OUT); |
| |
| return rc; |
| } |
| |
| static int qpnp_pin_of_gpio_xlate(struct gpio_chip *gpio_chip, |
| const struct of_phandle_args *gpio_spec, |
| u32 *flags) |
| { |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(gpio_chip->dev); |
| struct qpnp_pin_spec *q_spec; |
| |
| if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) { |
| pr_err("of_gpio_n_cells < 2\n"); |
| return -EINVAL; |
| } |
| |
| q_spec = qpnp_pmic_pin_get_spec(q_chip, gpio_spec->args[0]); |
| if (!q_spec) { |
| pr_err("no such PMIC gpio %u in device topology\n", |
| gpio_spec->args[0]); |
| return -EINVAL; |
| } |
| |
| if (flags) |
| *flags = gpio_spec->args[1]; |
| |
| return q_spec->gpio_chip_idx; |
| } |
| |
| static int qpnp_pin_apply_config(struct qpnp_pin_chip *q_chip, |
| struct qpnp_pin_spec *q_spec) |
| { |
| struct qpnp_pin_cfg param; |
| struct device_node *node = q_spec->node; |
| int rc; |
| |
| param.mode = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_MODE_SEL_SHIFT, |
| Q_REG_MODE_SEL_MASK); |
| param.output_type = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], |
| Q_REG_OUT_TYPE_SHIFT, |
| Q_REG_OUT_TYPE_MASK); |
| param.invert = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_OUT_INVERT_MASK, |
| Q_REG_OUT_INVERT_MASK); |
| param.pull = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_PULL_SHIFT, Q_REG_PULL_MASK); |
| param.vin_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_VIN_CTL], |
| Q_REG_VIN_SHIFT, Q_REG_VIN_MASK); |
| param.out_strength = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL], |
| Q_REG_OUT_STRENGTH_SHIFT, |
| Q_REG_OUT_STRENGTH_MASK); |
| param.select = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], |
| Q_REG_SRC_SEL_SHIFT, Q_REG_SRC_SEL_MASK); |
| param.master_en = q_reg_get(&q_spec->regs[Q_REG_I_EN_CTL], |
| Q_REG_MASTER_EN_SHIFT, |
| Q_REG_MASTER_EN_MASK); |
| param.aout_ref = q_reg_get(&q_spec->regs[Q_REG_I_AOUT_CTL], |
| Q_REG_AOUT_REF_SHIFT, |
| Q_REG_AOUT_REF_MASK); |
| param.ain_route = q_reg_get(&q_spec->regs[Q_REG_I_AIN_CTL], |
| Q_REG_AIN_ROUTE_SHIFT, |
| Q_REG_AIN_ROUTE_MASK); |
| param.cs_out = q_reg_get(&q_spec->regs[Q_REG_I_SINK_CTL], |
| Q_REG_CS_OUT_SHIFT, |
| Q_REG_CS_OUT_MASK); |
| |
| of_property_read_u32(node, "qcom,mode", |
| ¶m.mode); |
| of_property_read_u32(node, "qcom,output-type", |
| ¶m.output_type); |
| of_property_read_u32(node, "qcom,invert", |
| ¶m.invert); |
| of_property_read_u32(node, "qcom,pull", |
| ¶m.pull); |
| of_property_read_u32(node, "qcom,vin-sel", |
| ¶m.vin_sel); |
| of_property_read_u32(node, "qcom,out-strength", |
| ¶m.out_strength); |
| of_property_read_u32(node, "qcom,src-select", |
| ¶m.select); |
| of_property_read_u32(node, "qcom,master-en", |
| ¶m.master_en); |
| of_property_read_u32(node, "qcom,aout-ref", |
| ¶m.aout_ref); |
| of_property_read_u32(node, "qcom,ain-route", |
| ¶m.ain_route); |
| of_property_read_u32(node, "qcom,cs-out", |
| ¶m.cs_out); |
| rc = _qpnp_pin_config(q_chip, q_spec, ¶m); |
| |
| return rc; |
| } |
| |
| static int qpnp_pin_free_chip(struct qpnp_pin_chip *q_chip) |
| { |
| struct spmi_device *spmi = q_chip->spmi; |
| int rc, i; |
| |
| if (q_chip->chip_gpios) |
| for (i = 0; i < spmi->num_dev_node; i++) |
| kfree(q_chip->chip_gpios[i]); |
| |
| mutex_lock(&qpnp_pin_chips_lock); |
| list_del(&q_chip->chip_list); |
| mutex_unlock(&qpnp_pin_chips_lock); |
| rc = gpiochip_remove(&q_chip->gpio_chip); |
| if (rc) |
| dev_err(&q_chip->spmi->dev, "%s: unable to remove gpio\n", |
| __func__); |
| kfree(q_chip->chip_gpios); |
| kfree(q_chip->pmic_pins); |
| kfree(q_chip); |
| return rc; |
| } |
| |
| #ifdef CONFIG_GPIO_QPNP_PIN_DEBUG |
| struct qpnp_pin_reg { |
| uint32_t addr; |
| uint32_t idx; |
| uint32_t shift; |
| uint32_t mask; |
| }; |
| |
| static struct dentry *driver_dfs_dir; |
| |
| static int qpnp_pin_reg_attr(enum qpnp_pin_param_type type, |
| struct qpnp_pin_reg *cfg) |
| { |
| switch (type) { |
| case Q_PIN_CFG_MODE: |
| cfg->addr = Q_REG_MODE_CTL; |
| cfg->idx = Q_REG_I_MODE_CTL; |
| cfg->shift = Q_REG_MODE_SEL_SHIFT; |
| cfg->mask = Q_REG_MODE_SEL_MASK; |
| break; |
| case Q_PIN_CFG_OUTPUT_TYPE: |
| cfg->addr = Q_REG_DIG_OUT_CTL; |
| cfg->idx = Q_REG_I_DIG_OUT_CTL; |
| cfg->shift = Q_REG_OUT_TYPE_SHIFT; |
| cfg->mask = Q_REG_OUT_TYPE_MASK; |
| break; |
| case Q_PIN_CFG_INVERT: |
| cfg->addr = Q_REG_MODE_CTL; |
| cfg->idx = Q_REG_I_MODE_CTL; |
| cfg->shift = Q_REG_OUT_INVERT_SHIFT; |
| cfg->mask = Q_REG_OUT_INVERT_MASK; |
| break; |
| case Q_PIN_CFG_PULL: |
| cfg->addr = Q_REG_DIG_PULL_CTL; |
| cfg->idx = Q_REG_I_DIG_PULL_CTL; |
| cfg->shift = Q_REG_PULL_SHIFT; |
| cfg->mask = Q_REG_PULL_MASK; |
| break; |
| case Q_PIN_CFG_VIN_SEL: |
| cfg->addr = Q_REG_DIG_VIN_CTL; |
| cfg->idx = Q_REG_I_DIG_VIN_CTL; |
| cfg->shift = Q_REG_VIN_SHIFT; |
| cfg->mask = Q_REG_VIN_MASK; |
| break; |
| case Q_PIN_CFG_OUT_STRENGTH: |
| cfg->addr = Q_REG_DIG_OUT_CTL; |
| cfg->idx = Q_REG_I_DIG_OUT_CTL; |
| cfg->shift = Q_REG_OUT_STRENGTH_SHIFT; |
| cfg->mask = Q_REG_OUT_STRENGTH_MASK; |
| break; |
| case Q_PIN_CFG_SELECT: |
| cfg->addr = Q_REG_MODE_CTL; |
| cfg->idx = Q_REG_I_MODE_CTL; |
| cfg->shift = Q_REG_SRC_SEL_SHIFT; |
| cfg->mask = Q_REG_SRC_SEL_MASK; |
| break; |
| case Q_PIN_CFG_MASTER_EN: |
| cfg->addr = Q_REG_EN_CTL; |
| cfg->idx = Q_REG_I_EN_CTL; |
| cfg->shift = Q_REG_MASTER_EN_SHIFT; |
| cfg->mask = Q_REG_MASTER_EN_MASK; |
| break; |
| case Q_PIN_CFG_AOUT_REF: |
| cfg->addr = Q_REG_AOUT_CTL; |
| cfg->idx = Q_REG_I_AOUT_CTL; |
| cfg->shift = Q_REG_AOUT_REF_SHIFT; |
| cfg->mask = Q_REG_AOUT_REF_MASK; |
| break; |
| case Q_PIN_CFG_AIN_ROUTE: |
| cfg->addr = Q_REG_AIN_CTL; |
| cfg->idx = Q_REG_I_AIN_CTL; |
| cfg->shift = Q_REG_AIN_ROUTE_SHIFT; |
| cfg->mask = Q_REG_AIN_ROUTE_MASK; |
| break; |
| case Q_PIN_CFG_CS_OUT: |
| cfg->addr = Q_REG_SINK_CTL; |
| cfg->idx = Q_REG_I_SINK_CTL; |
| cfg->shift = Q_REG_CS_OUT_SHIFT; |
| cfg->mask = Q_REG_CS_OUT_MASK; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pin_debugfs_get(void *data, u64 *val) |
| { |
| enum qpnp_pin_param_type *idx = data; |
| struct qpnp_pin_spec *q_spec; |
| struct qpnp_pin_reg cfg = {}; |
| int rc; |
| |
| rc = qpnp_pin_reg_attr(*idx, &cfg); |
| if (rc) |
| return rc; |
| q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]); |
| *val = q_reg_get(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask); |
| return 0; |
| } |
| |
| static int qpnp_pin_debugfs_set(void *data, u64 val) |
| { |
| enum qpnp_pin_param_type *idx = data; |
| struct qpnp_pin_spec *q_spec; |
| struct qpnp_pin_chip *q_chip; |
| struct qpnp_pin_reg cfg = {}; |
| int rc; |
| |
| q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]); |
| q_chip = q_spec->q_chip; |
| |
| rc = qpnp_pin_check_config(*idx, q_spec, val); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pin_reg_attr(*idx, &cfg); |
| if (rc) |
| return rc; |
| q_reg_clr_set(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask, val); |
| rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, cfg.addr), |
| &q_spec->regs[cfg.idx], 1); |
| |
| return rc; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(qpnp_pin_fops, qpnp_pin_debugfs_get, |
| qpnp_pin_debugfs_set, "%llu\n"); |
| |
| #define DEBUGFS_BUF_SIZE 11 /* supports 2^32 in decimal */ |
| |
| struct qpnp_pin_debugfs_args { |
| enum qpnp_pin_param_type type; |
| const char *filename; |
| }; |
| |
| static struct qpnp_pin_debugfs_args dfs_args[] = { |
| { Q_PIN_CFG_MODE, "mode" }, |
| { Q_PIN_CFG_OUTPUT_TYPE, "output_type" }, |
| { Q_PIN_CFG_INVERT, "invert" }, |
| { Q_PIN_CFG_PULL, "pull" }, |
| { Q_PIN_CFG_VIN_SEL, "vin_sel" }, |
| { Q_PIN_CFG_OUT_STRENGTH, "out_strength" }, |
| { Q_PIN_CFG_SELECT, "select" }, |
| { Q_PIN_CFG_MASTER_EN, "master_en" }, |
| { Q_PIN_CFG_AOUT_REF, "aout_ref" }, |
| { Q_PIN_CFG_AIN_ROUTE, "ain_route" }, |
| { Q_PIN_CFG_CS_OUT, "cs_out" }, |
| }; |
| |
| static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip) |
| { |
| struct spmi_device *spmi = q_chip->spmi; |
| struct device *dev = &spmi->dev; |
| struct qpnp_pin_spec *q_spec; |
| enum qpnp_pin_param_type *params; |
| enum qpnp_pin_param_type type; |
| char pmic_pin[DEBUGFS_BUF_SIZE]; |
| const char *filename; |
| struct dentry *dfs, *dfs_io_dir; |
| int i, j, rc; |
| |
| BUG_ON(Q_NUM_PARAMS != ARRAY_SIZE(dfs_args)); |
| |
| q_chip->dfs_dir = debugfs_create_dir(q_chip->gpio_chip.label, |
| driver_dfs_dir); |
| if (q_chip->dfs_dir == NULL) { |
| dev_err(dev, "%s: cannot register chip debugfs directory %s\n", |
| __func__, dev->of_node->name); |
| return -ENODEV; |
| } |
| |
| for (i = 0; i < spmi->num_dev_node; i++) { |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, i); |
| params = q_spec->params; |
| snprintf(pmic_pin, DEBUGFS_BUF_SIZE, "%u", q_spec->pmic_pin); |
| dfs_io_dir = debugfs_create_dir(pmic_pin, q_chip->dfs_dir); |
| if (dfs_io_dir == NULL) |
| goto dfs_err; |
| |
| for (j = 0; j < Q_NUM_PARAMS; j++) { |
| type = dfs_args[j].type; |
| filename = dfs_args[j].filename; |
| |
| /* |
| * Use a value of '0' to see if the pin has even basic |
| * support for a function. Do not create a file if |
| * it doesn't. |
| */ |
| rc = qpnp_pin_check_config(type, q_spec, 0); |
| if (rc == -ENXIO) |
| continue; |
| |
| params[type] = type; |
| dfs = debugfs_create_file( |
| filename, |
| S_IRUGO | S_IWUSR, |
| dfs_io_dir, |
| &q_spec->params[type], |
| &qpnp_pin_fops); |
| if (dfs == NULL) |
| goto dfs_err; |
| } |
| } |
| return 0; |
| dfs_err: |
| dev_err(dev, "%s: cannot register debugfs for pmic gpio %u on chip %s\n", |
| __func__, q_spec->pmic_pin, dev->of_node->name); |
| debugfs_remove_recursive(q_chip->dfs_dir); |
| return -ENFILE; |
| } |
| #else |
| static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip) |
| { |
| return 0; |
| } |
| #endif |
| |
| static int qpnp_pin_probe(struct spmi_device *spmi) |
| { |
| struct qpnp_pin_chip *q_chip; |
| struct qpnp_pin_spec *q_spec; |
| struct resource *res; |
| struct spmi_resource *d_node; |
| int i, rc; |
| int lowest_gpio = UINT_MAX, highest_gpio = 0; |
| u32 intspec[3], gpio; |
| char buf[2]; |
| const char *dev_name; |
| |
| dev_name = spmi_get_primary_dev_name(spmi); |
| if (!dev_name) { |
| dev_err(&spmi->dev, "%s: label binding undefined for node %s\n", |
| __func__, spmi->dev.of_node->full_name); |
| return -EINVAL; |
| } |
| |
| q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL); |
| if (!q_chip) { |
| dev_err(&spmi->dev, "%s: Can't allocate gpio_chip\n", |
| __func__); |
| return -ENOMEM; |
| } |
| q_chip->spmi = spmi; |
| dev_set_drvdata(&spmi->dev, q_chip); |
| |
| mutex_lock(&qpnp_pin_chips_lock); |
| list_add(&q_chip->chip_list, &qpnp_pin_chips); |
| mutex_unlock(&qpnp_pin_chips_lock); |
| |
| /* first scan through nodes to find the range required for allocation */ |
| for (i = 0; i < spmi->num_dev_node; i++) { |
| rc = of_property_read_u32(spmi->dev_node[i].of_node, |
| "qcom,pin-num", &gpio); |
| if (rc) { |
| dev_err(&spmi->dev, "%s: unable to get qcom,pin-num property\n", |
| __func__); |
| goto err_probe; |
| } |
| |
| if (gpio < lowest_gpio) |
| lowest_gpio = gpio; |
| if (gpio > highest_gpio) |
| highest_gpio = gpio; |
| } |
| |
| if (highest_gpio < lowest_gpio) { |
| dev_err(&spmi->dev, "%s: no device nodes specified in topology\n", |
| __func__); |
| rc = -EINVAL; |
| goto err_probe; |
| } else if (lowest_gpio == 0) { |
| dev_err(&spmi->dev, "%s: 0 is not a valid PMIC GPIO\n", |
| __func__); |
| rc = -EINVAL; |
| goto err_probe; |
| } |
| |
| q_chip->pmic_pin_lowest = lowest_gpio; |
| q_chip->pmic_pin_highest = highest_gpio; |
| |
| /* allocate gpio lookup tables */ |
| q_chip->pmic_pins = kzalloc(sizeof(struct qpnp_pin_spec *) * |
| highest_gpio - lowest_gpio + 1, |
| GFP_KERNEL); |
| q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_pin_spec *) * |
| spmi->num_dev_node, GFP_KERNEL); |
| if (!q_chip->pmic_pins || !q_chip->chip_gpios) { |
| dev_err(&spmi->dev, "%s: unable to allocate memory\n", |
| __func__); |
| rc = -ENOMEM; |
| goto err_probe; |
| } |
| |
| /* get interrupt controller device_node */ |
| q_chip->int_ctrl = of_irq_find_parent(spmi->dev.of_node); |
| if (!q_chip->int_ctrl) { |
| dev_err(&spmi->dev, "%s: Can't find interrupt parent\n", |
| __func__); |
| rc = -EINVAL; |
| goto err_probe; |
| } |
| |
| /* now scan through again and populate the lookup table */ |
| for (i = 0; i < spmi->num_dev_node; i++) { |
| d_node = &spmi->dev_node[i]; |
| res = spmi_get_resource(spmi, d_node, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&spmi->dev, "%s: node %s is missing has no base address definition\n", |
| __func__, d_node->of_node->full_name); |
| } |
| |
| rc = of_property_read_u32(d_node->of_node, |
| "qcom,pin-num", &gpio); |
| if (rc) { |
| dev_err(&spmi->dev, "%s: unable to get qcom,pin-num property\n", |
| __func__); |
| goto err_probe; |
| } |
| |
| q_spec = kzalloc(sizeof(struct qpnp_pin_spec), |
| GFP_KERNEL); |
| if (!q_spec) { |
| dev_err(&spmi->dev, "%s: unable to allocate memory\n", |
| __func__); |
| rc = -ENOMEM; |
| goto err_probe; |
| } |
| |
| q_spec->slave = spmi->sid; |
| q_spec->offset = res->start; |
| q_spec->gpio_chip_idx = i; |
| q_spec->pmic_pin = gpio; |
| q_spec->node = d_node->of_node; |
| q_spec->q_chip = q_chip; |
| |
| rc = spmi_ext_register_readl(spmi->ctrl, q_spec->slave, |
| Q_REG_ADDR(q_spec, Q_REG_TYPE), &buf[0], 2); |
| if (rc) { |
| dev_err(&spmi->dev, "%s: unable to read type regs\n", |
| __func__); |
| goto err_probe; |
| } |
| q_spec->type = buf[0]; |
| q_spec->subtype = buf[1]; |
| |
| rc = qpnp_pin_ctl_regs_init(q_spec); |
| if (rc) |
| goto err_probe; |
| |
| /* call into irq_domain to get irq mapping */ |
| intspec[0] = q_chip->spmi->sid; |
| intspec[1] = (q_spec->offset >> 8) & 0xFF; |
| intspec[2] = 0; |
| q_spec->irq = irq_create_of_mapping(q_chip->int_ctrl, |
| intspec, 3); |
| if (!q_spec->irq) { |
| dev_err(&spmi->dev, "%s: invalid irq for gpio %u\n", |
| __func__, gpio); |
| rc = -EINVAL; |
| goto err_probe; |
| } |
| /* initialize lookup table params */ |
| qpnp_pmic_pin_set_spec(q_chip, gpio, q_spec); |
| qpnp_chip_gpio_set_spec(q_chip, i, q_spec); |
| } |
| |
| q_chip->gpio_chip.base = -1; |
| q_chip->gpio_chip.ngpio = spmi->num_dev_node; |
| q_chip->gpio_chip.label = dev_name; |
| q_chip->gpio_chip.direction_input = qpnp_pin_direction_input; |
| q_chip->gpio_chip.direction_output = qpnp_pin_direction_output; |
| q_chip->gpio_chip.to_irq = qpnp_pin_to_irq; |
| q_chip->gpio_chip.get = qpnp_pin_get; |
| q_chip->gpio_chip.set = qpnp_pin_set; |
| q_chip->gpio_chip.dev = &spmi->dev; |
| q_chip->gpio_chip.of_xlate = qpnp_pin_of_gpio_xlate; |
| q_chip->gpio_chip.of_gpio_n_cells = 2; |
| q_chip->gpio_chip.can_sleep = 0; |
| |
| rc = gpiochip_add(&q_chip->gpio_chip); |
| if (rc) { |
| dev_err(&spmi->dev, "%s: Can't add gpio chip, rc = %d\n", |
| __func__, rc); |
| goto err_probe; |
| } |
| |
| /* now configure gpio config defaults if they exist */ |
| for (i = 0; i < spmi->num_dev_node; i++) { |
| q_spec = qpnp_chip_gpio_get_spec(q_chip, i); |
| if (WARN_ON(!q_spec)) { |
| rc = -ENODEV; |
| goto err_probe; |
| } |
| |
| rc = qpnp_pin_cache_regs(q_chip, q_spec); |
| if (rc) |
| goto err_probe; |
| |
| rc = qpnp_pin_apply_config(q_chip, q_spec); |
| if (rc) |
| goto err_probe; |
| } |
| |
| dev_dbg(&spmi->dev, "%s: gpio_chip registered between %d-%u\n", |
| __func__, q_chip->gpio_chip.base, |
| (q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1); |
| |
| rc = qpnp_pin_debugfs_create(q_chip); |
| if (rc) { |
| dev_err(&spmi->dev, "%s: debugfs creation failed\n", __func__); |
| goto err_probe; |
| } |
| |
| return 0; |
| |
| err_probe: |
| qpnp_pin_free_chip(q_chip); |
| return rc; |
| } |
| |
| static int qpnp_pin_remove(struct spmi_device *spmi) |
| { |
| struct qpnp_pin_chip *q_chip = dev_get_drvdata(&spmi->dev); |
| |
| debugfs_remove_recursive(q_chip->dfs_dir); |
| |
| return qpnp_pin_free_chip(q_chip); |
| } |
| |
| static struct of_device_id spmi_match_table[] = { |
| { .compatible = "qcom,qpnp-pin", |
| }, |
| {} |
| }; |
| |
| static const struct spmi_device_id qpnp_pin_id[] = { |
| { "qcom,qpnp-pin", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(spmi, qpnp_pin_id); |
| |
| static struct spmi_driver qpnp_pin_driver = { |
| .driver = { |
| .name = "qcom,qpnp-pin", |
| .of_match_table = spmi_match_table, |
| }, |
| .probe = qpnp_pin_probe, |
| .remove = qpnp_pin_remove, |
| .id_table = qpnp_pin_id, |
| }; |
| |
| static int __init qpnp_pin_init(void) |
| { |
| #ifdef CONFIG_GPIO_QPNP_PIN_DEBUG |
| driver_dfs_dir = debugfs_create_dir("qpnp_pin", NULL); |
| if (driver_dfs_dir == NULL) |
| pr_err("Cannot register top level debugfs directory\n"); |
| #endif |
| |
| return spmi_driver_register(&qpnp_pin_driver); |
| } |
| |
| static void __exit qpnp_pin_exit(void) |
| { |
| #ifdef CONFIG_GPIO_QPNP_PIN_DEBUG |
| debugfs_remove_recursive(driver_dfs_dir); |
| #endif |
| spmi_driver_unregister(&qpnp_pin_driver); |
| } |
| |
| MODULE_DESCRIPTION("QPNP PMIC gpio driver"); |
| MODULE_LICENSE("GPL v2"); |
| |
| module_init(qpnp_pin_init); |
| module_exit(qpnp_pin_exit); |