|  | /* | 
|  | * Copyright (c) 2012-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/gpio.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_irq.h> | 
|  | #include <linux/pinctrl/pinconf-generic.h> | 
|  | #include <linux/pinctrl/pinconf.h> | 
|  | #include <linux/pinctrl/pinmux.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/types.h> | 
|  |  | 
|  | #include <dt-bindings/pinctrl/qcom,pmic-mpp.h> | 
|  |  | 
|  | #include "../core.h" | 
|  | #include "../pinctrl-utils.h" | 
|  |  | 
|  | #define PMIC_MPP_ADDRESS_RANGE			0x100 | 
|  |  | 
|  | /* | 
|  | * Pull Up Values - it indicates whether a pull-up should be | 
|  | * applied for bidirectional mode only. The hardware ignores the | 
|  | * configuration when operating in other modes. | 
|  | */ | 
|  | #define PMIC_MPP_PULL_UP_0P6KOHM		0 | 
|  | #define PMIC_MPP_PULL_UP_10KOHM			1 | 
|  | #define PMIC_MPP_PULL_UP_30KOHM			2 | 
|  | #define PMIC_MPP_PULL_UP_OPEN			3 | 
|  |  | 
|  | /* type registers base address bases */ | 
|  | #define PMIC_MPP_REG_TYPE			0x4 | 
|  | #define PMIC_MPP_REG_SUBTYPE			0x5 | 
|  |  | 
|  | /* mpp peripheral type and subtype values */ | 
|  | #define PMIC_MPP_TYPE				0x11 | 
|  | #define PMIC_MPP_SUBTYPE_4CH_NO_ANA_OUT		0x3 | 
|  | #define PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT	0x4 | 
|  | #define PMIC_MPP_SUBTYPE_4CH_NO_SINK		0x5 | 
|  | #define PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK	0x6 | 
|  | #define PMIC_MPP_SUBTYPE_4CH_FULL_FUNC		0x7 | 
|  | #define PMIC_MPP_SUBTYPE_8CH_FULL_FUNC		0xf | 
|  |  | 
|  | #define PMIC_MPP_REG_RT_STS			0x10 | 
|  | #define PMIC_MPP_REG_RT_STS_VAL_MASK		0x1 | 
|  |  | 
|  | /* control register base address bases */ | 
|  | #define PMIC_MPP_REG_MODE_CTL			0x40 | 
|  | #define PMIC_MPP_REG_DIG_VIN_CTL		0x41 | 
|  | #define PMIC_MPP_REG_DIG_PULL_CTL		0x42 | 
|  | #define PMIC_MPP_REG_DIG_IN_CTL			0x43 | 
|  | #define PMIC_MPP_REG_EN_CTL			0x46 | 
|  | #define PMIC_MPP_REG_AOUT_CTL			0x48 | 
|  | #define PMIC_MPP_REG_AIN_CTL			0x4a | 
|  | #define PMIC_MPP_REG_SINK_CTL			0x4c | 
|  |  | 
|  | /* PMIC_MPP_REG_MODE_CTL */ | 
|  | #define PMIC_MPP_REG_MODE_VALUE_MASK		0x1 | 
|  | #define PMIC_MPP_REG_MODE_FUNCTION_SHIFT	1 | 
|  | #define PMIC_MPP_REG_MODE_FUNCTION_MASK		0x7 | 
|  | #define PMIC_MPP_REG_MODE_DIR_SHIFT		4 | 
|  | #define PMIC_MPP_REG_MODE_DIR_MASK		0x7 | 
|  |  | 
|  | /* PMIC_MPP_REG_DIG_VIN_CTL */ | 
|  | #define PMIC_MPP_REG_VIN_SHIFT			0 | 
|  | #define PMIC_MPP_REG_VIN_MASK			0x7 | 
|  |  | 
|  | /* PMIC_MPP_REG_DIG_PULL_CTL */ | 
|  | #define PMIC_MPP_REG_PULL_SHIFT			0 | 
|  | #define PMIC_MPP_REG_PULL_MASK			0x7 | 
|  |  | 
|  | /* PMIC_MPP_REG_EN_CTL */ | 
|  | #define PMIC_MPP_REG_MASTER_EN_SHIFT		7 | 
|  |  | 
|  | /* PMIC_MPP_REG_AIN_CTL */ | 
|  | #define PMIC_MPP_REG_AIN_ROUTE_SHIFT		0 | 
|  | #define PMIC_MPP_REG_AIN_ROUTE_MASK		0x7 | 
|  |  | 
|  | #define PMIC_MPP_MODE_DIGITAL_INPUT		0 | 
|  | #define PMIC_MPP_MODE_DIGITAL_OUTPUT		1 | 
|  | #define PMIC_MPP_MODE_DIGITAL_BIDIR		2 | 
|  | #define PMIC_MPP_MODE_ANALOG_BIDIR		3 | 
|  | #define PMIC_MPP_MODE_ANALOG_INPUT		4 | 
|  | #define PMIC_MPP_MODE_ANALOG_OUTPUT		5 | 
|  | #define PMIC_MPP_MODE_CURRENT_SINK		6 | 
|  |  | 
|  | #define PMIC_MPP_SELECTOR_NORMAL		0 | 
|  | #define PMIC_MPP_SELECTOR_PAIRED		1 | 
|  | #define PMIC_MPP_SELECTOR_DTEST_FIRST		4 | 
|  |  | 
|  | #define PMIC_MPP_PHYSICAL_OFFSET		1 | 
|  |  | 
|  | /* Qualcomm specific pin configurations */ | 
|  | #define PMIC_MPP_CONF_AMUX_ROUTE		(PIN_CONFIG_END + 1) | 
|  | #define PMIC_MPP_CONF_ANALOG_LEVEL		(PIN_CONFIG_END + 2) | 
|  | #define PMIC_MPP_CONF_DTEST_SELECTOR		(PIN_CONFIG_END + 3) | 
|  | #define PMIC_MPP_CONF_PAIRED			(PIN_CONFIG_END + 4) | 
|  |  | 
|  | /** | 
|  | * struct pmic_mpp_pad - keep current MPP settings | 
|  | * @base: Address base in SPMI device. | 
|  | * @irq: IRQ number which this MPP generate. | 
|  | * @is_enabled: Set to false when MPP should be put in high Z state. | 
|  | * @out_value: Cached pin output value. | 
|  | * @output_enabled: Set to true if MPP output logic is enabled. | 
|  | * @input_enabled: Set to true if MPP input buffer logic is enabled. | 
|  | * @paired: Pin operates in paired mode | 
|  | * @has_pullup: Pin has support to configure pullup | 
|  | * @num_sources: Number of power-sources supported by this MPP. | 
|  | * @power_source: Current power-source used. | 
|  | * @amux_input: Set the source for analog input. | 
|  | * @aout_level: Analog output level | 
|  | * @pullup: Pullup resistor value. Valid in Bidirectional mode only. | 
|  | * @function: See pmic_mpp_functions[]. | 
|  | * @drive_strength: Amount of current in sink mode | 
|  | * @dtest: DTEST route selector | 
|  | */ | 
|  | struct pmic_mpp_pad { | 
|  | u16		base; | 
|  | int		irq; | 
|  | bool		is_enabled; | 
|  | bool		out_value; | 
|  | bool		output_enabled; | 
|  | bool		input_enabled; | 
|  | bool		paired; | 
|  | bool		has_pullup; | 
|  | unsigned int	num_sources; | 
|  | unsigned int	power_source; | 
|  | unsigned int	amux_input; | 
|  | unsigned int	aout_level; | 
|  | unsigned int	pullup; | 
|  | unsigned int	function; | 
|  | unsigned int	drive_strength; | 
|  | unsigned int	dtest; | 
|  | }; | 
|  |  | 
|  | struct pmic_mpp_state { | 
|  | struct device	*dev; | 
|  | struct regmap	*map; | 
|  | struct pinctrl_dev *ctrl; | 
|  | struct gpio_chip chip; | 
|  | }; | 
|  |  | 
|  | static const struct pinconf_generic_params pmic_mpp_bindings[] = { | 
|  | {"qcom,amux-route",	PMIC_MPP_CONF_AMUX_ROUTE,	0}, | 
|  | {"qcom,analog-level",	PMIC_MPP_CONF_ANALOG_LEVEL,	0}, | 
|  | {"qcom,dtest",		PMIC_MPP_CONF_DTEST_SELECTOR,	0}, | 
|  | {"qcom,paired",		PMIC_MPP_CONF_PAIRED,		0}, | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_DEBUG_FS | 
|  | static const struct pin_config_item pmic_conf_items[] = { | 
|  | PCONFDUMP(PMIC_MPP_CONF_AMUX_ROUTE, "analog mux", NULL, true), | 
|  | PCONFDUMP(PMIC_MPP_CONF_ANALOG_LEVEL, "analog level", NULL, true), | 
|  | PCONFDUMP(PMIC_MPP_CONF_DTEST_SELECTOR, "dtest", NULL, true), | 
|  | PCONFDUMP(PMIC_MPP_CONF_PAIRED, "paired", NULL, false), | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | static const char *const pmic_mpp_groups[] = { | 
|  | "mpp1", "mpp2", "mpp3", "mpp4", "mpp5", "mpp6", "mpp7", "mpp8", | 
|  | }; | 
|  |  | 
|  | #define PMIC_MPP_DIGITAL	0 | 
|  | #define PMIC_MPP_ANALOG		1 | 
|  | #define PMIC_MPP_SINK		2 | 
|  |  | 
|  | static const char *const pmic_mpp_functions[] = { | 
|  | "digital", "analog", "sink" | 
|  | }; | 
|  |  | 
|  | static int pmic_mpp_read(struct pmic_mpp_state *state, | 
|  | struct pmic_mpp_pad *pad, unsigned int addr) | 
|  | { | 
|  | unsigned int val; | 
|  | int ret; | 
|  |  | 
|  | ret = regmap_read(state->map, pad->base + addr, &val); | 
|  | if (ret < 0) | 
|  | dev_err(state->dev, "read 0x%x failed\n", addr); | 
|  | else | 
|  | ret = val; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_write(struct pmic_mpp_state *state, | 
|  | struct pmic_mpp_pad *pad, unsigned int addr, | 
|  | unsigned int val) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = regmap_write(state->map, pad->base + addr, val); | 
|  | if (ret < 0) | 
|  | dev_err(state->dev, "write 0x%x failed\n", addr); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_get_groups_count(struct pinctrl_dev *pctldev) | 
|  | { | 
|  | /* Every PIN is a group */ | 
|  | return pctldev->desc->npins; | 
|  | } | 
|  |  | 
|  | static const char *pmic_mpp_get_group_name(struct pinctrl_dev *pctldev, | 
|  | unsigned pin) | 
|  | { | 
|  | return pctldev->desc->pins[pin].name; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_get_group_pins(struct pinctrl_dev *pctldev, | 
|  | unsigned pin, | 
|  | const unsigned **pins, unsigned *num_pins) | 
|  | { | 
|  | *pins = &pctldev->desc->pins[pin].number; | 
|  | *num_pins = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct pinctrl_ops pmic_mpp_pinctrl_ops = { | 
|  | .get_groups_count	= pmic_mpp_get_groups_count, | 
|  | .get_group_name		= pmic_mpp_get_group_name, | 
|  | .get_group_pins		= pmic_mpp_get_group_pins, | 
|  | .dt_node_to_map		= pinconf_generic_dt_node_to_map_group, | 
|  | .dt_free_map		= pinctrl_utils_free_map, | 
|  | }; | 
|  |  | 
|  | static int pmic_mpp_get_functions_count(struct pinctrl_dev *pctldev) | 
|  | { | 
|  | return ARRAY_SIZE(pmic_mpp_functions); | 
|  | } | 
|  |  | 
|  | static const char *pmic_mpp_get_function_name(struct pinctrl_dev *pctldev, | 
|  | unsigned function) | 
|  | { | 
|  | return pmic_mpp_functions[function]; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_get_function_groups(struct pinctrl_dev *pctldev, | 
|  | unsigned function, | 
|  | const char *const **groups, | 
|  | unsigned *const num_qgroups) | 
|  | { | 
|  | *groups = pmic_mpp_groups; | 
|  | *num_qgroups = pctldev->desc->npins; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_write_mode_ctl(struct pmic_mpp_state *state, | 
|  | struct pmic_mpp_pad *pad) | 
|  | { | 
|  | unsigned int mode; | 
|  | unsigned int sel; | 
|  | unsigned int val; | 
|  | unsigned int en; | 
|  |  | 
|  | switch (pad->function) { | 
|  | case PMIC_MPP_ANALOG: | 
|  | if (pad->input_enabled && pad->output_enabled) | 
|  | mode = PMIC_MPP_MODE_ANALOG_BIDIR; | 
|  | else if (pad->input_enabled) | 
|  | mode = PMIC_MPP_MODE_ANALOG_INPUT; | 
|  | else | 
|  | mode = PMIC_MPP_MODE_ANALOG_OUTPUT; | 
|  | break; | 
|  | case PMIC_MPP_DIGITAL: | 
|  | if (pad->input_enabled && pad->output_enabled) | 
|  | mode = PMIC_MPP_MODE_DIGITAL_BIDIR; | 
|  | else if (pad->input_enabled) | 
|  | mode = PMIC_MPP_MODE_DIGITAL_INPUT; | 
|  | else | 
|  | mode = PMIC_MPP_MODE_DIGITAL_OUTPUT; | 
|  | break; | 
|  | case PMIC_MPP_SINK: | 
|  | default: | 
|  | mode = PMIC_MPP_MODE_CURRENT_SINK; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (pad->dtest) | 
|  | sel = PMIC_MPP_SELECTOR_DTEST_FIRST + pad->dtest - 1; | 
|  | else if (pad->paired) | 
|  | sel = PMIC_MPP_SELECTOR_PAIRED; | 
|  | else | 
|  | sel = PMIC_MPP_SELECTOR_NORMAL; | 
|  |  | 
|  | en = !!pad->out_value; | 
|  |  | 
|  | val = mode << PMIC_MPP_REG_MODE_DIR_SHIFT | | 
|  | sel << PMIC_MPP_REG_MODE_FUNCTION_SHIFT | | 
|  | en; | 
|  |  | 
|  | return pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val); | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_set_mux(struct pinctrl_dev *pctldev, unsigned function, | 
|  | unsigned pin) | 
|  | { | 
|  | struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); | 
|  | struct pmic_mpp_pad *pad; | 
|  | unsigned int val; | 
|  | int ret; | 
|  |  | 
|  | pad = pctldev->desc->pins[pin].drv_data; | 
|  |  | 
|  | pad->function = function; | 
|  |  | 
|  | ret = pmic_mpp_write_mode_ctl(state, pad); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | val = pad->is_enabled << PMIC_MPP_REG_MASTER_EN_SHIFT; | 
|  |  | 
|  | return pmic_mpp_write(state, pad, PMIC_MPP_REG_EN_CTL, val); | 
|  | } | 
|  |  | 
|  | static const struct pinmux_ops pmic_mpp_pinmux_ops = { | 
|  | .get_functions_count	= pmic_mpp_get_functions_count, | 
|  | .get_function_name	= pmic_mpp_get_function_name, | 
|  | .get_function_groups	= pmic_mpp_get_function_groups, | 
|  | .set_mux		= pmic_mpp_set_mux, | 
|  | }; | 
|  |  | 
|  | static int pmic_mpp_config_get(struct pinctrl_dev *pctldev, | 
|  | unsigned int pin, unsigned long *config) | 
|  | { | 
|  | unsigned param = pinconf_to_config_param(*config); | 
|  | struct pmic_mpp_pad *pad; | 
|  | unsigned arg = 0; | 
|  |  | 
|  | pad = pctldev->desc->pins[pin].drv_data; | 
|  |  | 
|  | switch (param) { | 
|  | case PIN_CONFIG_BIAS_DISABLE: | 
|  | if (pad->pullup != PMIC_MPP_PULL_UP_OPEN) | 
|  | return -EINVAL; | 
|  | arg = 1; | 
|  | break; | 
|  | case PIN_CONFIG_BIAS_PULL_UP: | 
|  | switch (pad->pullup) { | 
|  | case PMIC_MPP_PULL_UP_0P6KOHM: | 
|  | arg = 600; | 
|  | break; | 
|  | case PMIC_MPP_PULL_UP_10KOHM: | 
|  | arg = 10000; | 
|  | break; | 
|  | case PMIC_MPP_PULL_UP_30KOHM: | 
|  | arg = 30000; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | break; | 
|  | case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: | 
|  | if (pad->is_enabled) | 
|  | return -EINVAL; | 
|  | arg = 1; | 
|  | break; | 
|  | case PIN_CONFIG_POWER_SOURCE: | 
|  | arg = pad->power_source; | 
|  | break; | 
|  | case PIN_CONFIG_INPUT_ENABLE: | 
|  | if (!pad->input_enabled) | 
|  | return -EINVAL; | 
|  | arg = 1; | 
|  | break; | 
|  | case PIN_CONFIG_OUTPUT: | 
|  | arg = pad->out_value; | 
|  | break; | 
|  | case PMIC_MPP_CONF_DTEST_SELECTOR: | 
|  | arg = pad->dtest; | 
|  | break; | 
|  | case PMIC_MPP_CONF_AMUX_ROUTE: | 
|  | arg = pad->amux_input; | 
|  | break; | 
|  | case PMIC_MPP_CONF_PAIRED: | 
|  | if (!pad->paired) | 
|  | return -EINVAL; | 
|  | arg = 1; | 
|  | break; | 
|  | case PIN_CONFIG_DRIVE_STRENGTH: | 
|  | arg = pad->drive_strength; | 
|  | break; | 
|  | case PMIC_MPP_CONF_ANALOG_LEVEL: | 
|  | arg = pad->aout_level; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Convert register value to pinconf value */ | 
|  | *config = pinconf_to_config_packed(param, arg); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_config_set(struct pinctrl_dev *pctldev, unsigned int pin, | 
|  | unsigned long *configs, unsigned nconfs) | 
|  | { | 
|  | struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); | 
|  | struct pmic_mpp_pad *pad; | 
|  | unsigned param, arg; | 
|  | unsigned int val; | 
|  | int i, ret; | 
|  |  | 
|  | pad = pctldev->desc->pins[pin].drv_data; | 
|  |  | 
|  | /* Make it possible to enable the pin, by not setting high impedance */ | 
|  | pad->is_enabled = true; | 
|  |  | 
|  | for (i = 0; i < nconfs; i++) { | 
|  | param = pinconf_to_config_param(configs[i]); | 
|  | arg = pinconf_to_config_argument(configs[i]); | 
|  |  | 
|  | switch (param) { | 
|  | case PIN_CONFIG_BIAS_DISABLE: | 
|  | pad->pullup = PMIC_MPP_PULL_UP_OPEN; | 
|  | break; | 
|  | case PIN_CONFIG_BIAS_PULL_UP: | 
|  | switch (arg) { | 
|  | case 600: | 
|  | pad->pullup = PMIC_MPP_PULL_UP_0P6KOHM; | 
|  | break; | 
|  | case 10000: | 
|  | pad->pullup = PMIC_MPP_PULL_UP_10KOHM; | 
|  | break; | 
|  | case 30000: | 
|  | pad->pullup = PMIC_MPP_PULL_UP_30KOHM; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | break; | 
|  | case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: | 
|  | pad->is_enabled = false; | 
|  | break; | 
|  | case PIN_CONFIG_POWER_SOURCE: | 
|  | if (arg >= pad->num_sources) | 
|  | return -EINVAL; | 
|  | pad->power_source = arg; | 
|  | break; | 
|  | case PIN_CONFIG_INPUT_ENABLE: | 
|  | pad->input_enabled = arg ? true : false; | 
|  | break; | 
|  | case PIN_CONFIG_OUTPUT: | 
|  | pad->output_enabled = true; | 
|  | pad->out_value = arg; | 
|  | break; | 
|  | case PMIC_MPP_CONF_DTEST_SELECTOR: | 
|  | pad->dtest = arg; | 
|  | break; | 
|  | case PIN_CONFIG_DRIVE_STRENGTH: | 
|  | pad->drive_strength = arg; | 
|  | break; | 
|  | case PMIC_MPP_CONF_AMUX_ROUTE: | 
|  | if (arg >= PMIC_MPP_AMUX_ROUTE_ABUS4) | 
|  | return -EINVAL; | 
|  | pad->amux_input = arg; | 
|  | break; | 
|  | case PMIC_MPP_CONF_ANALOG_LEVEL: | 
|  | pad->aout_level = arg; | 
|  | break; | 
|  | case PMIC_MPP_CONF_PAIRED: | 
|  | pad->paired = !!arg; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | val = pad->power_source << PMIC_MPP_REG_VIN_SHIFT; | 
|  |  | 
|  | ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_DIG_VIN_CTL, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | if (pad->has_pullup) { | 
|  | val = pad->pullup << PMIC_MPP_REG_PULL_SHIFT; | 
|  |  | 
|  | ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_DIG_PULL_CTL, | 
|  | val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | val = pad->amux_input & PMIC_MPP_REG_AIN_ROUTE_MASK; | 
|  |  | 
|  | ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_AIN_CTL, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_AOUT_CTL, pad->aout_level); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = pmic_mpp_write_mode_ctl(state, pad); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_SINK_CTL, pad->drive_strength); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | val = pad->is_enabled << PMIC_MPP_REG_MASTER_EN_SHIFT; | 
|  |  | 
|  | return pmic_mpp_write(state, pad, PMIC_MPP_REG_EN_CTL, val); | 
|  | } | 
|  |  | 
|  | static void pmic_mpp_config_dbg_show(struct pinctrl_dev *pctldev, | 
|  | struct seq_file *s, unsigned pin) | 
|  | { | 
|  | struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); | 
|  | struct pmic_mpp_pad *pad; | 
|  | int ret; | 
|  |  | 
|  | static const char *const biases[] = { | 
|  | "0.6kOhm", "10kOhm", "30kOhm", "Disabled" | 
|  | }; | 
|  |  | 
|  | pad = pctldev->desc->pins[pin].drv_data; | 
|  |  | 
|  | seq_printf(s, " mpp%-2d:", pin + PMIC_MPP_PHYSICAL_OFFSET); | 
|  |  | 
|  | if (!pad->is_enabled) { | 
|  | seq_puts(s, " ---"); | 
|  | } else { | 
|  |  | 
|  | if (pad->input_enabled) { | 
|  | ret = pmic_mpp_read(state, pad, PMIC_MPP_REG_RT_STS); | 
|  | if (ret < 0) | 
|  | return; | 
|  |  | 
|  | ret &= PMIC_MPP_REG_RT_STS_VAL_MASK; | 
|  | pad->out_value = ret; | 
|  | } | 
|  |  | 
|  | seq_printf(s, " %-4s", pad->output_enabled ? "out" : "in"); | 
|  | seq_printf(s, " %-7s", pmic_mpp_functions[pad->function]); | 
|  | seq_printf(s, " vin-%d", pad->power_source); | 
|  | seq_printf(s, " %d", pad->aout_level); | 
|  | if (pad->has_pullup) | 
|  | seq_printf(s, " %-8s", biases[pad->pullup]); | 
|  | seq_printf(s, " %-4s", pad->out_value ? "high" : "low"); | 
|  | if (pad->dtest) | 
|  | seq_printf(s, " dtest%d", pad->dtest); | 
|  | if (pad->paired) | 
|  | seq_puts(s, " paired"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static const struct pinconf_ops pmic_mpp_pinconf_ops = { | 
|  | .is_generic = true, | 
|  | .pin_config_group_get		= pmic_mpp_config_get, | 
|  | .pin_config_group_set		= pmic_mpp_config_set, | 
|  | .pin_config_group_dbg_show	= pmic_mpp_config_dbg_show, | 
|  | }; | 
|  |  | 
|  | static int pmic_mpp_direction_input(struct gpio_chip *chip, unsigned pin) | 
|  | { | 
|  | struct pmic_mpp_state *state = gpiochip_get_data(chip); | 
|  | unsigned long config; | 
|  |  | 
|  | config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1); | 
|  |  | 
|  | return pmic_mpp_config_set(state->ctrl, pin, &config, 1); | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_direction_output(struct gpio_chip *chip, | 
|  | unsigned pin, int val) | 
|  | { | 
|  | struct pmic_mpp_state *state = gpiochip_get_data(chip); | 
|  | unsigned long config; | 
|  |  | 
|  | config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val); | 
|  |  | 
|  | return pmic_mpp_config_set(state->ctrl, pin, &config, 1); | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_get(struct gpio_chip *chip, unsigned pin) | 
|  | { | 
|  | struct pmic_mpp_state *state = gpiochip_get_data(chip); | 
|  | struct pmic_mpp_pad *pad; | 
|  | int ret; | 
|  |  | 
|  | pad = state->ctrl->desc->pins[pin].drv_data; | 
|  |  | 
|  | if (pad->input_enabled) { | 
|  | ret = pmic_mpp_read(state, pad, PMIC_MPP_REG_RT_STS); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | pad->out_value = ret & PMIC_MPP_REG_RT_STS_VAL_MASK; | 
|  | } | 
|  |  | 
|  | return !!pad->out_value; | 
|  | } | 
|  |  | 
|  | static void pmic_mpp_set(struct gpio_chip *chip, unsigned pin, int value) | 
|  | { | 
|  | struct pmic_mpp_state *state = gpiochip_get_data(chip); | 
|  | unsigned long config; | 
|  |  | 
|  | config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value); | 
|  |  | 
|  | pmic_mpp_config_set(state->ctrl, pin, &config, 1); | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_of_xlate(struct gpio_chip *chip, | 
|  | const struct of_phandle_args *gpio_desc, | 
|  | u32 *flags) | 
|  | { | 
|  | if (chip->of_gpio_n_cells < 2) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (flags) | 
|  | *flags = gpio_desc->args[1]; | 
|  |  | 
|  | return gpio_desc->args[0] - PMIC_MPP_PHYSICAL_OFFSET; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_to_irq(struct gpio_chip *chip, unsigned pin) | 
|  | { | 
|  | struct pmic_mpp_state *state = gpiochip_get_data(chip); | 
|  | struct pmic_mpp_pad *pad; | 
|  |  | 
|  | pad = state->ctrl->desc->pins[pin].drv_data; | 
|  |  | 
|  | return pad->irq; | 
|  | } | 
|  |  | 
|  | static void pmic_mpp_dbg_show(struct seq_file *s, struct gpio_chip *chip) | 
|  | { | 
|  | struct pmic_mpp_state *state = gpiochip_get_data(chip); | 
|  | unsigned i; | 
|  |  | 
|  | for (i = 0; i < chip->ngpio; i++) { | 
|  | pmic_mpp_config_dbg_show(state->ctrl, s, i); | 
|  | seq_puts(s, "\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static const struct gpio_chip pmic_mpp_gpio_template = { | 
|  | .direction_input	= pmic_mpp_direction_input, | 
|  | .direction_output	= pmic_mpp_direction_output, | 
|  | .get			= pmic_mpp_get, | 
|  | .set			= pmic_mpp_set, | 
|  | .request		= gpiochip_generic_request, | 
|  | .free			= gpiochip_generic_free, | 
|  | .of_xlate		= pmic_mpp_of_xlate, | 
|  | .to_irq			= pmic_mpp_to_irq, | 
|  | .dbg_show		= pmic_mpp_dbg_show, | 
|  | }; | 
|  |  | 
|  | static int pmic_mpp_populate(struct pmic_mpp_state *state, | 
|  | struct pmic_mpp_pad *pad) | 
|  | { | 
|  | int type, subtype, val, dir; | 
|  | unsigned int sel; | 
|  |  | 
|  | type = pmic_mpp_read(state, pad, PMIC_MPP_REG_TYPE); | 
|  | if (type < 0) | 
|  | return type; | 
|  |  | 
|  | if (type != PMIC_MPP_TYPE) { | 
|  | dev_err(state->dev, "incorrect block type 0x%x at 0x%x\n", | 
|  | type, pad->base); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | subtype = pmic_mpp_read(state, pad, PMIC_MPP_REG_SUBTYPE); | 
|  | if (subtype < 0) | 
|  | return subtype; | 
|  |  | 
|  | switch (subtype) { | 
|  | case PMIC_MPP_SUBTYPE_4CH_NO_ANA_OUT: | 
|  | case PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT: | 
|  | case PMIC_MPP_SUBTYPE_4CH_NO_SINK: | 
|  | case PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK: | 
|  | case PMIC_MPP_SUBTYPE_4CH_FULL_FUNC: | 
|  | pad->num_sources = 4; | 
|  | break; | 
|  | case PMIC_MPP_SUBTYPE_8CH_FULL_FUNC: | 
|  | pad->num_sources = 8; | 
|  | break; | 
|  | default: | 
|  | dev_err(state->dev, "unknown MPP type 0x%x at 0x%x\n", | 
|  | subtype, pad->base); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_MODE_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->out_value = val & PMIC_MPP_REG_MODE_VALUE_MASK; | 
|  |  | 
|  | dir = val >> PMIC_MPP_REG_MODE_DIR_SHIFT; | 
|  | dir &= PMIC_MPP_REG_MODE_DIR_MASK; | 
|  |  | 
|  | switch (dir) { | 
|  | case PMIC_MPP_MODE_DIGITAL_INPUT: | 
|  | pad->input_enabled = true; | 
|  | pad->output_enabled = false; | 
|  | pad->function = PMIC_MPP_DIGITAL; | 
|  | break; | 
|  | case PMIC_MPP_MODE_DIGITAL_OUTPUT: | 
|  | pad->input_enabled = false; | 
|  | pad->output_enabled = true; | 
|  | pad->function = PMIC_MPP_DIGITAL; | 
|  | break; | 
|  | case PMIC_MPP_MODE_DIGITAL_BIDIR: | 
|  | pad->input_enabled = true; | 
|  | pad->output_enabled = true; | 
|  | pad->function = PMIC_MPP_DIGITAL; | 
|  | break; | 
|  | case PMIC_MPP_MODE_ANALOG_BIDIR: | 
|  | pad->input_enabled = true; | 
|  | pad->output_enabled = true; | 
|  | pad->function = PMIC_MPP_ANALOG; | 
|  | break; | 
|  | case PMIC_MPP_MODE_ANALOG_INPUT: | 
|  | pad->input_enabled = true; | 
|  | pad->output_enabled = false; | 
|  | pad->function = PMIC_MPP_ANALOG; | 
|  | break; | 
|  | case PMIC_MPP_MODE_ANALOG_OUTPUT: | 
|  | pad->input_enabled = false; | 
|  | pad->output_enabled = true; | 
|  | pad->function = PMIC_MPP_ANALOG; | 
|  | break; | 
|  | case PMIC_MPP_MODE_CURRENT_SINK: | 
|  | pad->input_enabled = false; | 
|  | pad->output_enabled = true; | 
|  | pad->function = PMIC_MPP_SINK; | 
|  | break; | 
|  | default: | 
|  | dev_err(state->dev, "unknown MPP direction\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | sel = val >> PMIC_MPP_REG_MODE_FUNCTION_SHIFT; | 
|  | sel &= PMIC_MPP_REG_MODE_FUNCTION_MASK; | 
|  |  | 
|  | if (sel >= PMIC_MPP_SELECTOR_DTEST_FIRST) | 
|  | pad->dtest = sel + 1; | 
|  | else if (sel == PMIC_MPP_SELECTOR_PAIRED) | 
|  | pad->paired = true; | 
|  |  | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_VIN_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->power_source = val >> PMIC_MPP_REG_VIN_SHIFT; | 
|  | pad->power_source &= PMIC_MPP_REG_VIN_MASK; | 
|  |  | 
|  | if (subtype != PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT && | 
|  | subtype != PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK) { | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_PULL_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->pullup = val >> PMIC_MPP_REG_PULL_SHIFT; | 
|  | pad->pullup &= PMIC_MPP_REG_PULL_MASK; | 
|  | pad->has_pullup = true; | 
|  | } | 
|  |  | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_AIN_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->amux_input = val >> PMIC_MPP_REG_AIN_ROUTE_SHIFT; | 
|  | pad->amux_input &= PMIC_MPP_REG_AIN_ROUTE_MASK; | 
|  |  | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_SINK_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->drive_strength = val; | 
|  |  | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_AOUT_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->aout_level = val; | 
|  |  | 
|  | val = pmic_mpp_read(state, pad, PMIC_MPP_REG_EN_CTL); | 
|  | if (val < 0) | 
|  | return val; | 
|  |  | 
|  | pad->is_enabled = !!val; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct pinctrl_pin_desc *pindesc; | 
|  | struct pinctrl_desc *pctrldesc; | 
|  | struct pmic_mpp_pad *pad, *pads; | 
|  | struct pmic_mpp_state *state; | 
|  | int ret, npins, i; | 
|  | u32 reg; | 
|  |  | 
|  | ret = of_property_read_u32(dev->of_node, "reg", ®); | 
|  | if (ret < 0) { | 
|  | dev_err(dev, "missing base address"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | npins = platform_irq_count(pdev); | 
|  | if (!npins) | 
|  | return -EINVAL; | 
|  | if (npins < 0) | 
|  | return npins; | 
|  |  | 
|  | BUG_ON(npins > ARRAY_SIZE(pmic_mpp_groups)); | 
|  |  | 
|  | state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); | 
|  | if (!state) | 
|  | return -ENOMEM; | 
|  |  | 
|  | platform_set_drvdata(pdev, state); | 
|  |  | 
|  | state->dev = &pdev->dev; | 
|  | state->map = dev_get_regmap(dev->parent, NULL); | 
|  |  | 
|  | pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL); | 
|  | if (!pindesc) | 
|  | return -ENOMEM; | 
|  |  | 
|  | pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL); | 
|  | if (!pads) | 
|  | return -ENOMEM; | 
|  |  | 
|  | pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL); | 
|  | if (!pctrldesc) | 
|  | return -ENOMEM; | 
|  |  | 
|  | pctrldesc->pctlops = &pmic_mpp_pinctrl_ops; | 
|  | pctrldesc->pmxops = &pmic_mpp_pinmux_ops; | 
|  | pctrldesc->confops = &pmic_mpp_pinconf_ops; | 
|  | pctrldesc->owner = THIS_MODULE; | 
|  | pctrldesc->name = dev_name(dev); | 
|  | pctrldesc->pins = pindesc; | 
|  | pctrldesc->npins = npins; | 
|  |  | 
|  | pctrldesc->num_custom_params = ARRAY_SIZE(pmic_mpp_bindings); | 
|  | pctrldesc->custom_params = pmic_mpp_bindings; | 
|  | #ifdef CONFIG_DEBUG_FS | 
|  | pctrldesc->custom_conf_items = pmic_conf_items; | 
|  | #endif | 
|  |  | 
|  | for (i = 0; i < npins; i++, pindesc++) { | 
|  | pad = &pads[i]; | 
|  | pindesc->drv_data = pad; | 
|  | pindesc->number = i; | 
|  | pindesc->name = pmic_mpp_groups[i]; | 
|  |  | 
|  | pad->irq = platform_get_irq(pdev, i); | 
|  | if (pad->irq < 0) | 
|  | return pad->irq; | 
|  |  | 
|  | pad->base = reg + i * PMIC_MPP_ADDRESS_RANGE; | 
|  |  | 
|  | ret = pmic_mpp_populate(state, pad); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | state->chip = pmic_mpp_gpio_template; | 
|  | state->chip.parent = dev; | 
|  | state->chip.base = -1; | 
|  | state->chip.ngpio = npins; | 
|  | state->chip.label = dev_name(dev); | 
|  | state->chip.of_gpio_n_cells = 2; | 
|  | state->chip.can_sleep = false; | 
|  |  | 
|  | state->ctrl = devm_pinctrl_register(dev, pctrldesc, state); | 
|  | if (IS_ERR(state->ctrl)) | 
|  | return PTR_ERR(state->ctrl); | 
|  |  | 
|  | ret = gpiochip_add_data(&state->chip, state); | 
|  | if (ret) { | 
|  | dev_err(state->dev, "can't add gpio chip\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins); | 
|  | if (ret) { | 
|  | dev_err(dev, "failed to add pin range\n"); | 
|  | goto err_range; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_range: | 
|  | gpiochip_remove(&state->chip); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int pmic_mpp_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct pmic_mpp_state *state = platform_get_drvdata(pdev); | 
|  |  | 
|  | gpiochip_remove(&state->chip); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id pmic_mpp_of_match[] = { | 
|  | { .compatible = "qcom,pm8841-mpp" },	/* 4 MPP's */ | 
|  | { .compatible = "qcom,pm8916-mpp" },	/* 4 MPP's */ | 
|  | { .compatible = "qcom,pm8941-mpp" },	/* 8 MPP's */ | 
|  | { .compatible = "qcom,pm8994-mpp" },	/* 8 MPP's */ | 
|  | { .compatible = "qcom,pma8084-mpp" },	/* 8 MPP's */ | 
|  | { .compatible = "qcom,spmi-mpp" },	/* Generic */ | 
|  | { }, | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(of, pmic_mpp_of_match); | 
|  |  | 
|  | static struct platform_driver pmic_mpp_driver = { | 
|  | .driver = { | 
|  | .name = "qcom-spmi-mpp", | 
|  | .of_match_table = pmic_mpp_of_match, | 
|  | }, | 
|  | .probe	= pmic_mpp_probe, | 
|  | .remove = pmic_mpp_remove, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(pmic_mpp_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); | 
|  | MODULE_DESCRIPTION("Qualcomm SPMI PMIC MPP pin control driver"); | 
|  | MODULE_ALIAS("platform:qcom-spmi-mpp"); | 
|  | MODULE_LICENSE("GPL v2"); |