From d39d45ead6cda4bb4eec38c39e58965a87678464 Mon Sep 17 00:00:00 2001 From: "xingyu.chen" Date: Wed, 7 Jun 2017 16:02:31 +0800 Subject: [PATCH] pinctrl&gpio: add new interface to set pull-down/up PD#142470: pinctrl&gpio: add new interface to set pull-down/up 1. add property file 'pull' in directory /sys/class/gpio/gpioX/ pull-up: echo up > pull pull-down: echo down > pull pull-disable: echo disable > pull 2. add new functions, as follows: + gpiod_set_pull(struct gpio_desc *desc, int value) + gpiod_set_pull_cansleep(struct gpio_desc *desc, int value) 'value' can be set: GPIOD_PULL_DIS GPIOD_PULL_DOWN GPIOD_PULL_UP Change-Id: Iba750729288651a897ebac827093ebc6c143f16b Signed-off-by: xingyu.chen --- drivers/amlogic/pinctrl/pinctrl-meson.c | 36 ++++++++++++++++ drivers/gpio/gpiolib-sysfs.c | 27 ++++++++++++ drivers/gpio/gpiolib.c | 56 +++++++++++++++++++++++++ include/linux/gpio/consumer.h | 32 ++++++++++++++ include/linux/gpio/driver.h | 6 ++- 5 files changed, 156 insertions(+), 1 deletion(-) diff --git a/drivers/amlogic/pinctrl/pinctrl-meson.c b/drivers/amlogic/pinctrl/pinctrl-meson.c index 5eb5ac16d9f8..b4d5eb08df6f 100644 --- a/drivers/amlogic/pinctrl/pinctrl-meson.c +++ b/drivers/amlogic/pinctrl/pinctrl-meson.c @@ -658,6 +658,41 @@ static void meson_gpio_set(struct gpio_chip *chip, unsigned int gpio, value ? BIT(bit) : 0); } +static int meson_gpio_pull_set(struct gpio_chip *chip, unsigned int gpio, + int value) +{ + struct meson_domain *domain = to_meson_domain(chip); + unsigned int reg, bit, pin; + struct meson_bank *bank; + int ret; + + if ((value != GPIOD_PULL_DIS) && (value != GPIOD_PULL_DOWN) + && (value != GPIOD_PULL_UP)) + return -EINVAL; + + pin = domain->data->pin_base + gpio; + ret = meson_get_bank(domain, pin, &bank); + if (ret) + return ret; + + meson_calc_reg_and_bit(bank, pin, REG_PULLEN, + ®, &bit); + ret = regmap_update_bits(domain->reg_pullen, reg, + BIT(bit), + (value == GPIOD_PULL_DIS) ? 0 : BIT(bit)); + if (ret) + return ret; + + meson_calc_reg_and_bit(bank, pin, REG_PULL, ®, &bit); + ret = regmap_update_bits(domain->reg_pull, reg, + BIT(bit), + (value == GPIOD_PULL_DOWN) ? 0 : BIT(bit)); + if (ret) + return ret; + + return 0; +} + static int meson_gpio_get(struct gpio_chip *chip, unsigned int gpio) { struct meson_domain *domain = to_meson_domain(chip); @@ -1088,6 +1123,7 @@ static int meson_gpiolib_register(struct meson_pinctrl *pc) domain->chip.direction_output = meson_gpio_direction_output; domain->chip.get = meson_gpio_get; domain->chip.set = meson_gpio_set; + domain->chip.set_pull = meson_gpio_pull_set; domain->chip.base = domain->data->pin_base; domain->chip.ngpio = domain->data->num_pins; domain->chip.can_sleep = false; diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 4b44dd97c07f..05b4cf307a71 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -350,6 +350,30 @@ static ssize_t active_low_store(struct device *dev, } static DEVICE_ATTR_RW(active_low); +#ifdef CONFIG_AMLOGIC_PINCTRL +static ssize_t pull_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct gpiod_data *data = dev_get_drvdata(dev); + struct gpio_desc *desc = data->desc; + ssize_t status; + + mutex_lock(&data->mutex); + if (sysfs_streq(buf, "disable")) + status = gpiod_set_pull(desc, GPIOD_PULL_DIS); + else if (sysfs_streq(buf, "down")) + status = gpiod_set_pull(desc, GPIOD_PULL_DOWN); + else if (sysfs_streq(buf, "up")) + status = gpiod_set_pull(desc, GPIOD_PULL_UP); + else + status = -EINVAL; + mutex_unlock(&data->mutex); + + return status ? : size; +} +static DEVICE_ATTR_WO(pull); +#endif + static umode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr, int n) { @@ -377,6 +401,9 @@ static struct attribute *gpio_attrs[] = { &dev_attr_edge.attr, &dev_attr_value.attr, &dev_attr_active_low.attr, +#ifdef CONFIG_AMLOGIC_PINCTRL + &dev_attr_pull.attr, +#endif NULL, }; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 92159313361b..3013ae31b529 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2509,6 +2509,26 @@ static void _gpiod_set_raw_value(struct gpio_desc *desc, bool value) chip->set(chip, gpio_chip_hwgpio(desc), value); } +#ifdef CONFIG_AMLOGIC_PINCTRL +static int _gpiod_set_pull(struct gpio_desc *desc, int value) +{ + struct gpio_chip *chip; + int status = -EINVAL; + + chip = desc->gdev->chip; + if (!chip || !chip->set_pull) { + gpiod_warn(desc, + "%s: missing set_pull() operations\n", + __func__); + return -EIO; + } + if (test_bit(FLAG_REQUESTED, &desc->flags)) + status = chip->set_pull(chip, gpio_chip_hwgpio(desc), value); + + return status; +} +#endif + /* * set multiple outputs on the same chip; * use the chip's set_multiple function if available; @@ -2631,6 +2651,25 @@ void gpiod_set_value(struct gpio_desc *desc, int value) } EXPORT_SYMBOL_GPL(gpiod_set_value); +/** + * gpiod_set_pull() - enable pull-down/up for the gpio, or disable. + * @desc: gpio whose value will be set + * @value: value to set + * + * This function should be called from contexts where we cannot sleep, and will + * complain if the GPIO chip functions potentially sleep. + */ +#ifdef CONFIG_AMLOGIC_PINCTRL +int gpiod_set_pull(struct gpio_desc *desc, int value) +{ + VALIDATE_DESC(desc); + /* Should be using gpiod_set_pull_cansleep() */ + WARN_ON(desc->gdev->chip->can_sleep); + return _gpiod_set_pull(desc, value); +} +EXPORT_SYMBOL_GPL(gpiod_set_pull); +#endif + /** * gpiod_set_raw_array_value() - assign values to an array of GPIOs * @array_size: number of elements in the descriptor / value arrays @@ -2888,6 +2927,23 @@ void gpiod_set_value_cansleep(struct gpio_desc *desc, int value) } EXPORT_SYMBOL_GPL(gpiod_set_value_cansleep); +/** + * gpiod_set_pull_cansleep() - enable pull-down/up for the gpio, or disable. + * @desc: gpio whose value will be set + * @value: value to set + * + * This function is to be called from contexts that can sleep. + */ +#ifdef CONFIG_AMLOGIC_PINCTRL +int gpiod_set_pull_cansleep(struct gpio_desc *desc, int value) +{ + might_sleep_if(extra_checks); + VALIDATE_DESC(desc); + return _gpiod_set_pull(desc, value); +} +EXPORT_SYMBOL_GPL(gpiod_set_pull_cansleep); +#endif + /** * gpiod_set_raw_array_value_cansleep() - assign values to an array of GPIOs * @array_size: number of elements in the descriptor / value arrays diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index fb0fde686cb1..8dcd6d8c86b4 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -41,6 +41,14 @@ enum gpiod_flags { GPIOD_FLAGS_BIT_DIR_VAL, }; +#ifdef CONFIG_AMLOGIC_PINCTRL +enum gpiod_pull_type { + GPIOD_PULL_DIS = 0, + GPIOD_PULL_DOWN = 1, + GPIOD_PULL_UP = 2, +}; +#endif + #ifdef CONFIG_GPIOLIB /* Return the number of GPIOs associated with a device / function */ @@ -100,6 +108,9 @@ int gpiod_direction_output_raw(struct gpio_desc *desc, int value); /* Value get/set from non-sleeping context */ int gpiod_get_value(const struct gpio_desc *desc); void gpiod_set_value(struct gpio_desc *desc, int value); +#ifdef CONFIG_AMLOGIC_PINCTRL +int gpiod_set_pull(struct gpio_desc *desc, int value); +#endif void gpiod_set_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); int gpiod_get_raw_value(const struct gpio_desc *desc); @@ -111,6 +122,9 @@ void gpiod_set_raw_array_value(unsigned int array_size, /* Value get/set from sleeping context */ int gpiod_get_value_cansleep(const struct gpio_desc *desc); void gpiod_set_value_cansleep(struct gpio_desc *desc, int value); +#ifdef CONFIG_AMLOGIC_PINCTRL +int gpiod_set_pull_scansleep(struct gpio_desc *desc, int value); +#endif void gpiod_set_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); @@ -323,6 +337,15 @@ static inline void gpiod_set_raw_value(struct gpio_desc *desc, int value) /* GPIO can never have been requested */ WARN_ON(1); } + +#ifdef CONFIG_AMLOGIC_PINCTRL +static inline int gpiod_set_pull(struct gpio_desc *desc, int value) +{ + WARN_ON(1); + return -EINVAL; +} +#endif + static inline void gpiod_set_raw_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array) @@ -342,6 +365,15 @@ static inline void gpiod_set_value_cansleep(struct gpio_desc *desc, int value) /* GPIO can never have been requested */ WARN_ON(1); } + +#ifdef CONFIG_AMLOGIC_PINCTRL +static inline int gpiod_set_pull_cansleep(struct gpio_desc *desc, int value) +{ + WARN_ON(1); + return -EINVAL; +} +#endif + static inline void gpiod_set_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, int *value_array) diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 24e2cc56beb1..81e99dc79987 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -58,6 +58,7 @@ enum single_ended_mode { * if a consumer request this. The driver may return -ENOTSUPP if e.g. * it supports just open drain but not open source and is called * with LINE_MODE_OPEN_SOURCE as mode argument. + * @set_pull: set the current pull configuration for the GPIO. * @to_irq: optional hook supporting non-static gpio_to_irq() mappings; * implementation may not sleep * @dbg_show: optional routine to show contents in debugfs; default code @@ -156,7 +157,10 @@ struct gpio_chip { int (*set_single_ended)(struct gpio_chip *chip, unsigned offset, enum single_ended_mode mode); - +#ifdef CONFIG_AMLOGIC_PINCTRL + int (*set_pull)(struct gpio_chip *chip, + unsigned int offset, int value); +#endif int (*to_irq)(struct gpio_chip *chip, unsigned offset);