![Linus Torvalds](/assets/img/avatar_default.png)
Infrastructural changes: - In struct gpio_chip, rename the .dev node to .parent to better reflect the fact that this is not the GPIO struct device abstraction. We will add that soon so this would be totallt confusing. - It was noted that the driver .get_value() callbacks was sometimes reporting negative -ERR values to the gpiolib core, expecting them to be propagated to consumer gpiod_get_value() and gpio_get_value() calls. This was not happening, so as there was a mess of drivers returning negative errors and some returning "anything else than zero" to indicate that a line was active. As some would have bit 31 set to indicate "line active" it clashed with negative error codes. This is fixed by the largeish series clamping values in all drivers with !!value to [0,1] and then augmenting the code to propagate error codes to consumers. (Includes some ACKed patches in other subsystems.) - Add a void *data pointer to struct gpio_chip. The container_of() design pattern is indeed very nice, but we want to reform the struct gpio_chip to be a non-volative, stateless business, and keep states internal to the gpiolib to be able to hold on to the state when adding a proper userspace ABI (character device) further down the road. To achieve this, drivers need a handle at the internal state that is not dependent on their struct gpio_chip() so we add gpiochip_add_data() and gpiochip_get_data() following the pattern of many other subsystems. All the "use gpiochip data pointer" patches transforms drivers to this scheme. - The Generic GPIO chip header has been merged into the general <linux/gpio/driver.h> header, and the custom header for that removed. Instead of having a separate mm_gpio_chip struct for these generic drivers, merge that into struct gpio_chip, simplifying the code and removing the need for separate and confusing includes. Misc improvements: - Stabilize the way GPIOs are looked up from the ACPI legacy specification. - Incremental driver features for PXA, PCA953X, Lantiq (patches from the OpenWRT community), RCAR, Zynq, PL061, 104-idi-48 New drivers: - Add a GPIO chip to the ALSA SoC AC97 driver. - Add a new Broadcom NSP SoC driver (this lands in the pinctrl dir, but the branch is merged here too to account for infrastructural changes). - The sx150x driver now supports the sx1502. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJWmsZhAAoJEEEQszewGV1ztq0QAJ1KbNOpmf/s3INkOH4r771Z WIrNEsmwwLIAryo8gKNOM0H1zCwhRUV7hIE5jYWgD6JvjuAN6vobMlZAq21j6YpB pKgqnI5DuoND450xjb8wSwGQ5NTYp1rFXNmwCrtyTjOle6AAW+Kp2cvVWxVr77Av uJinRuuBr9GOKW/yYM1Fw/6EPjkvvhVOb+LBguRyVvq0s5Peyw7ZVeY1tjgPHJLn oSZ9dmPUjHEn91oZQbtfro3plOObcxdgJ8vo//pgEmyhMeR8XjXES+aUfErxqWOU PimrZuMMy4cxnsqWwh3Dyxo7KSWfJKfSPRwnGwc/HgbHZEoWxOZI1ezRtGKrRQtj vubxp5dUBA5z66TMsOCeJtzKVSofkvgX2Wr/Y9jKp5oy9cHdAZv9+jEHV1pr6asz Tas97MmmO77XuRI/GPDqVHx8dfa15OIz9s92+Gu64KxNzVxTo4+NdoPSNxkbCILO FKn7EmU3D0OjmN2NJ9GAURoFaj3BBUgNhaxacG9j2bieyh+euuUHRtyh2k8zXR9y 8OnY1UOrTUYF8YIq9pXZxMQRD/lqwCNHvEjtI6BqMcNx4MptfTL+FKYUkn/SgCYk QTNV6Ui+ety5D5aEpp5q0ItGsrDJ2LYSItsS+cOtMy2ieOxbQav9NWwu7eI3l5ly gwYTZjG9p9joPXLW0E3g =63rR -----END PGP SIGNATURE----- Merge tag 'gpio-v4.5-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio Pull GPIO updates from Linus Walleij: "Here is the bulk of GPIO changes for v4.5. Notably there are big refactorings mostly by myself, aimed at getting the gpio_chip into a shape that makes me believe I can proceed to preserve state for a proper userspace ABI (character device) that has already been proposed once, but resulted in the feedback that I need to go back and restructure stuff. So I've been restructuring stuff. On the way I ran into brokenness (return code from the get_value() callback) and had to fix it. Also, refactored generic GPIO to be simpler. Some of that is still waiting to trickle down from the subsystems all over the kernel that provide random gpio_chips, I've touched every single GPIO driver in the kernel now, oh man I didn't know I was responsible for so much... Apart from that we're churning along as usual. I took some effort to test and retest so it should merge nicely and we shook out a couple of bugs in -next. Infrastructural changes: - In struct gpio_chip, rename the .dev node to .parent to better reflect the fact that this is not the GPIO struct device abstraction. We will add that soon so this would be totallt confusing. - It was noted that the driver .get_value() callbacks was sometimes reporting negative -ERR values to the gpiolib core, expecting them to be propagated to consumer gpiod_get_value() and gpio_get_value() calls. This was not happening, so as there was a mess of drivers returning negative errors and some returning "anything else than zero" to indicate that a line was active. As some would have bit 31 set to indicate "line active" it clashed with negative error codes. This is fixed by the largeish series clamping values in all drivers with !!value to [0,1] and then augmenting the code to propagate error codes to consumers. (Includes some ACKed patches in other subsystems.) - Add a void *data pointer to struct gpio_chip. The container_of() design pattern is indeed very nice, but we want to reform the struct gpio_chip to be a non-volative, stateless business, and keep states internal to the gpiolib to be able to hold on to the state when adding a proper userspace ABI (character device) further down the road. To achieve this, drivers need a handle at the internal state that is not dependent on their struct gpio_chip() so we add gpiochip_add_data() and gpiochip_get_data() following the pattern of many other subsystems. All the "use gpiochip data pointer" patches transforms drivers to this scheme. - The Generic GPIO chip header has been merged into the general <linux/gpio/driver.h> header, and the custom header for that removed. Instead of having a separate mm_gpio_chip struct for these generic drivers, merge that into struct gpio_chip, simplifying the code and removing the need for separate and confusing includes. Misc improvements: - Stabilize the way GPIOs are looked up from the ACPI legacy specification. - Incremental driver features for PXA, PCA953X, Lantiq (patches from the OpenWRT community), RCAR, Zynq, PL061, 104-idi-48 New drivers: - Add a GPIO chip to the ALSA SoC AC97 driver. - Add a new Broadcom NSP SoC driver (this lands in the pinctrl dir, but the branch is merged here too to account for infrastructural changes). - The sx150x driver now supports the sx1502" * tag 'gpio-v4.5-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (220 commits) gpio: generic: make bgpio_pdata always visible gpiolib: fix chip order in gpio list gpio: mpc8xxx: Do not use gpiochip_get_data() in mpc8xxx_gpio_save_regs() gpio: mm-lantiq: Do not use gpiochip_get_data() in ltq_mm_save_regs() gpio: brcmstb: Allow building driver for BMIPS_GENERIC gpio: brcmstb: Set endian flags for big-endian MIPS gpio: moxart: fix build regression gpio: xilinx: Do not use gpiochip_get_data() in xgpio_save_regs() leds: pca9532: use gpiochip data pointer leds: tca6507: use gpiochip data pointer hid: cp2112: use gpiochip data pointer bcma: gpio: use gpiochip data pointer avr32: gpio: use gpiochip data pointer video: fbdev: via: use gpiochip data pointer gpio: pch: Optimize pch_gpio_get() Revert "pinctrl: lantiq: Implement gpio_chip.to_irq" pinctrl: nsp-gpio: use gpiochip data pointer pinctrl: vt8500-wmt: use gpiochip data pointer pinctrl: exynos5440: use gpiochip data pointer pinctrl: at91-pio4: use gpiochip data pointer ...
930 lines
23 KiB
C
930 lines
23 KiB
C
/*
|
|
* 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
|
|
* @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;
|
|
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_dt_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);
|
|
|
|
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:
|
|
arg = pad->pullup == PMIC_MPP_PULL_UP_OPEN;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
switch (pad->pullup) {
|
|
case PMIC_MPP_PULL_UP_OPEN:
|
|
arg = 0;
|
|
break;
|
|
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:
|
|
arg = !pad->is_enabled;
|
|
break;
|
|
case PIN_CONFIG_POWER_SOURCE:
|
|
arg = pad->power_source;
|
|
break;
|
|
case PIN_CONFIG_INPUT_ENABLE:
|
|
arg = pad->input_enabled;
|
|
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:
|
|
arg = pad->paired;
|
|
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:
|
|
arg = pad->drive_strength;
|
|
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;
|
|
|
|
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;
|
|
|
|
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);
|
|
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;
|
|
|
|
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;
|
|
|
|
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 = pinctrl_register(pctrldesc, dev, 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");
|
|
goto err_chip;
|
|
}
|
|
|
|
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);
|
|
err_chip:
|
|
pinctrl_unregister(state->ctrl);
|
|
return ret;
|
|
}
|
|
|
|
static int pmic_mpp_remove(struct platform_device *pdev)
|
|
{
|
|
struct pmic_mpp_state *state = platform_get_drvdata(pdev);
|
|
|
|
gpiochip_remove(&state->chip);
|
|
pinctrl_unregister(state->ctrl);
|
|
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 */
|
|
{ },
|
|
};
|
|
|
|
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");
|