diff options
author | Dong Aisheng <aisheng.dong@nxp.com> | 2021-11-30 15:00:21 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2021-11-30 15:00:21 +0800 |
commit | 853a3c8e3bbae9d7011e4591996d667a931eb68d (patch) | |
tree | 42c796c87e3b2da7108b692c226d0c6ab5c605ad | |
parent | 8e33e9a47296b6a3afd7da3c950b77eaacfc42b3 (diff) | |
parent | 450fb3f8adf69d0a7e1a9b4d61d2dd726ca94e12 (diff) |
Merge branch 'gpio/next' into next
* gpio/next: (25 commits)
LF-4558-3 gpio: gpio-imx-rpmsg: do not alloc msg dynamically
MLK-25649-2 gpio: mxc: Add support for identifying SCU wakeup source from sysfs
MLK-25166-1 gpio: gpio-scu: gpio driver over scu fw misc api
LF-4273-2 gpio: vf610: clarify the content of GPIO labels
LF-4273-1 gpio: imx-rpmsg: clarify the content of GPIO labels
...
-rw-r--r-- | Documentation/devicetree/bindings/gpio/gpio-imx-rpmsg.txt | 57 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml | 4 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 21 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-74x164.c | 3 | ||||
-rw-r--r-- | drivers/gpio/gpio-imx-rpmsg.c | 535 | ||||
-rw-r--r-- | drivers/gpio/gpio-max732x.c | 22 | ||||
-rw-r--r-- | drivers/gpio/gpio-mpc8xxx.c | 11 | ||||
-rw-r--r-- | drivers/gpio/gpio-mxc.c | 296 | ||||
-rw-r--r-- | drivers/gpio/gpio-pca953x.c | 46 | ||||
-rw-r--r-- | drivers/gpio/gpio-scu.c | 137 | ||||
-rw-r--r-- | drivers/gpio/gpio-vf610.c | 2 |
12 files changed, 1115 insertions, 21 deletions
diff --git a/Documentation/devicetree/bindings/gpio/gpio-imx-rpmsg.txt b/Documentation/devicetree/bindings/gpio/gpio-imx-rpmsg.txt new file mode 100644 index 000000000000..25001da92da5 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-imx-rpmsg.txt @@ -0,0 +1,57 @@ +Device-Tree bindings for drivers/gpio/gpio-imx-rpmsg.c gpio driver over +rpmsg. On i.mx7ULP PTA PTB are connected on M4 side, so rpmsg gpio driver +needed to get/set gpio status from M4 side by rpmsg. + +Required properties: +- compatible : Should be "fsl,imx-rpmsg-gpio". +- port_idx : Specify the GPIO PORT index, PTA:0, PTB:1. +- gpio-controller : Mark the device node as a gpio controller. +- #gpio-cells : Should be two. The first cell is the pin number and + the second cell is used to specify the gpio polarity: + 0 = active high + 1 = active low +- interrupt-controller: Marks the device node as an interrupt controller. +- #interrupt-cells : Should be 2. The first cell is the GPIO number. + The second cell bits[3:0] is used to specify trigger type and level flags: + 1 = low-to-high edge triggered. + 2 = high-to-low edge triggered. + 4 = active high level-sensitive. + 8 = active low level-sensitive. + +Note: Each GPIO port should have an alias correctly numbered in "aliases" +node. + +Examples: + +aliases { + gpio4 = &rpmsg_gpio0; + gpio5 = &rpmsg_gpio1; +}; + +rpmsg_gpio0: rpmsg-gpio0 { + compatible = "fsl,imx-rpmsg-gpio"; + port_idx = <0>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&rpmsg_gpio0>; + status = "okay"; +}; + +rpmsg_gpio1: rpmsg-gpio1 { + compatible = "fsl,imx-rpmsg-gpio"; + port_idx = <1>; + gpio-controller; + #gpio-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&rpmsg_gpio1>; + status = "okay"; +}; + +&skeleton_node { + interrupt-parent = <&rpmsg_gpio1>; + interrupts = <7 2>; + wakeup-gpios = <&rpmsg_gpio1 7 GPIO_ACTIVE_LOW>; +}; diff --git a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml index b6a6e742b66d..ba30fb1d6d6f 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml @@ -94,6 +94,10 @@ properties: wakeup-source: $ref: /schemas/types.yaml#/definitions/flag + out-default: + description: + set the output IO default voltage. Exp: out-default = /bits/ 16 <mask val>; + patternProperties: "^(hog-[0-9]+|.+-hog(-[0-9]+)?)$": type: object diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index fae5141251e5..aad7feaf5255 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -439,6 +439,12 @@ config GPIO_MXC select GPIO_GENERIC select GENERIC_IRQ_CHIP +config GPIO_SCU + def_bool y + depends on IMX_SCU + help + Say Y here to enable the imx8 gpio over SCFW MISC API + config GPIO_MXS bool "Freescale MXS GPIO support" if COMPILE_TEST depends on ARCH_MXS || COMPILE_TEST @@ -446,6 +452,13 @@ config GPIO_MXS select GPIO_GENERIC select GENERIC_IRQ_CHIP +config GPIO_MXC_PAD_WAKEUP + def_bool y + depends on IMX_SCU + select GPIO_MXC + help + Say Y here to enable the imx8 gpio pad wakeup + config GPIO_OCTEON tristate "Cavium OCTEON GPIO" depends on CAVIUM_OCTEON_SOC @@ -663,7 +676,7 @@ config GPIO_UNIPHIER config GPIO_VF610 def_bool y - depends on ARCH_MXC && SOC_VF610 + depends on ARCH_MXC select GPIOLIB_IRQCHIP help Say yes here to support Vybrid vf610 GPIOs. @@ -678,6 +691,12 @@ config GPIO_VISCONTI help Say yes here to support GPIO on Tohisba Visconti. +config GPIO_IMX_RPMSG + bool "NXP i.MX7ULP RPMSG GPIO support" + depends on ARCH_MXC && RPMSG && GPIOLIB + help + This driver support i.MX7ULP RPMSG virtual GPIOs. + config GPIO_VR41XX tristate "NEC VR4100 series General-purpose I/O Uint support" depends on CPU_VR41XX diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fbcda637d5e1..5740cd358832 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -132,6 +132,7 @@ obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o obj-$(CONFIG_GPIO_SCH) += gpio-sch.o +obj-$(CONFIG_GPIO_SCU) += gpio-scu.o obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o @@ -164,6 +165,7 @@ obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_UCB1400) += gpio-ucb1400.o obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o +obj-$(CONFIG_GPIO_IMX_RPMSG) += gpio-imx-rpmsg.o obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o obj-$(CONFIG_GPIO_VIRTIO) += gpio-virtio.o obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o diff --git a/drivers/gpio/gpio-74x164.c b/drivers/gpio/gpio-74x164.c index 4a55cdf089d6..bf66a8d93912 100644 --- a/drivers/gpio/gpio-74x164.c +++ b/drivers/gpio/gpio-74x164.c @@ -141,6 +141,9 @@ static int gen_74x164_probe(struct spi_device *spi) chip->registers = nregs; chip->gpio_chip.ngpio = GEN_74X164_NUMBER_GPIOS * chip->registers; + of_property_read_u8_array(spi->dev.of_node, "registers-default", + chip->buffer, chip->registers); + chip->gpio_chip.can_sleep = true; chip->gpio_chip.parent = &spi->dev; chip->gpio_chip.owner = THIS_MODULE; diff --git a/drivers/gpio/gpio-imx-rpmsg.c b/drivers/gpio/gpio-imx-rpmsg.c new file mode 100644 index 000000000000..f97ce459dc41 --- /dev/null +++ b/drivers/gpio/gpio-imx-rpmsg.c @@ -0,0 +1,535 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * 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/kernel.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/imx_rpmsg.h> +#include <linux/init.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_qos.h> +#include <linux/rpmsg.h> +#include <linux/virtio.h> +#include <linux/workqueue.h> + +#define IMX_RPMSG_GPIO_PER_PORT 32 +#define RPMSG_TIMEOUT 1000 +#define IMX_RPMSG_GPIO_PORT_PER_SOC_MAX 10 + +enum gpio_input_trigger_type { + GPIO_RPMSG_TRI_IGNORE, + GPIO_RPMSG_TRI_RISING, + GPIO_RPMSG_TRI_FALLING, + GPIO_RPMSG_TRI_BOTH_EDGE, + GPIO_RPMSG_TRI_LOW_LEVEL, + GPIO_RPMSG_TRI_HIGH_LEVEL, +}; + +enum gpio_rpmsg_header_type { + GPIO_RPMSG_SETUP, + GPIO_RPMSG_REPLY, + GPIO_RPMSG_NOTIFY, +}; + +enum gpio_rpmsg_header_cmd { + GPIO_RPMSG_INPUT_INIT, + GPIO_RPMSG_OUTPUT_INIT, + GPIO_RPMSG_INPUT_GET, +}; + +struct gpio_rpmsg_data { + struct imx_rpmsg_head header; + u8 pin_idx; + u8 port_idx; + union { + u8 event; + u8 retcode; + u8 value; + } out; + union { + u8 wakeup; + u8 value; + } in; +} __packed __aligned(8); + +struct imx_rpmsg_gpio_pin { + u32 irq_type; + struct gpio_rpmsg_data msg; +}; + +struct imx_rpmsg_gpio_port { + struct gpio_chip gc; + struct irq_chip chip; + struct irq_domain *domain; + struct imx_rpmsg_gpio_pin gpio_pins[IMX_RPMSG_GPIO_PER_PORT]; + int idx; +}; + +struct imx_gpio_rpmsg_info { + struct rpmsg_device *rpdev; + struct gpio_rpmsg_data *notify_msg; + struct gpio_rpmsg_data *reply_msg; + struct pm_qos_request pm_qos_req; + struct completion cmd_complete; + struct imx_rpmsg_gpio_port *port_store[IMX_RPMSG_GPIO_PORT_PER_SOC_MAX]; + struct mutex lock; +}; + +struct imx_rpmsg_gpio_work { + struct gpio_rpmsg_data *msg; + struct imx_rpmsg_gpio_port *port; + struct work_struct rpmsg_send_wq; +}; + +static struct imx_rpmsg_gpio_work imx_rpmsg_gpio_send_work; +static struct workqueue_struct *imx_rpmsg_gpio_workqueue; +static struct imx_gpio_rpmsg_info gpio_rpmsg; + +static int gpio_send_message(struct imx_rpmsg_gpio_port *port, + struct gpio_rpmsg_data *msg, + struct imx_gpio_rpmsg_info *info, + bool sync) +{ + int err; + + if (!info->rpdev) { + dev_dbg(&info->rpdev->dev, + "rpmsg channel not ready, m4 image ready?\n"); + return -EINVAL; + } + + mutex_lock(&info->lock); + cpu_latency_qos_add_request(&info->pm_qos_req, + 0); + + reinit_completion(&info->cmd_complete); + + err = rpmsg_send(info->rpdev->ept, (void *)msg, + sizeof(struct gpio_rpmsg_data)); + + if (err) { + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err); + goto err_out; + } + + if (sync) { + err = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(RPMSG_TIMEOUT)); + if (!err) { + dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n"); + err = -ETIMEDOUT; + goto err_out; + } + + if (info->reply_msg->out.retcode != 0) { + dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n", + info->reply_msg->out.retcode); + err = -EINVAL; + goto err_out; + } + + /* copy the reply message */ + memcpy(&port->gpio_pins[info->reply_msg->pin_idx].msg, + info->reply_msg, sizeof(*info->reply_msg)); + + err = 0; + } + +err_out: + cpu_latency_qos_remove_request(&info->pm_qos_req); + mutex_unlock(&info->lock); + + return err; +} + +static int gpio_rpmsg_cb(struct rpmsg_device *rpdev, + void *data, int len, void *priv, u32 src) +{ + struct gpio_rpmsg_data *msg = (struct gpio_rpmsg_data *)data; + unsigned long flags; + + if (msg->header.type == GPIO_RPMSG_REPLY) { + /* TBD: Add irq request_id check for A core msg */ + gpio_rpmsg.reply_msg = msg; + complete(&gpio_rpmsg.cmd_complete); + } else if (msg->header.type == GPIO_RPMSG_NOTIFY) { + gpio_rpmsg.notify_msg = msg; + local_irq_save(flags); + generic_handle_irq(irq_find_mapping(gpio_rpmsg.port_store[msg->port_idx]->domain, msg->pin_idx)); + local_irq_restore(flags); + } else + dev_err(&gpio_rpmsg.rpdev->dev, "wrong command type!\n"); + + return 0; +} + +static struct gpio_rpmsg_data *gpio_get_pin_msg(struct imx_rpmsg_gpio_port *port, unsigned int offset) +{ + struct gpio_rpmsg_data *msg = &port->gpio_pins[offset].msg; + + memset(msg, 0, sizeof(struct gpio_rpmsg_data)); + + return msg; +}; + +static int imx_rpmsg_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc); + struct gpio_rpmsg_data *msg = NULL; + int ret; + + msg = gpio_get_pin_msg(port, gpio); + msg->header.cate = IMX_RPMSG_GPIO; + msg->header.major = IMX_RMPSG_MAJOR; + msg->header.minor = IMX_RMPSG_MINOR; + msg->header.type = GPIO_RPMSG_SETUP; + msg->header.cmd = GPIO_RPMSG_INPUT_GET; + msg->pin_idx = gpio; + msg->port_idx = port->idx; + + ret = gpio_send_message(port, msg, &gpio_rpmsg, true); + if (!ret) + return !!port->gpio_pins[gpio].msg.in.value; + + return ret; +} + +static int imx_rpmsg_gpio_direction_input(struct gpio_chip *gc, + unsigned int gpio) +{ + struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc); + struct gpio_rpmsg_data *msg = NULL; + + msg = gpio_get_pin_msg(port, gpio); + msg->header.cate = IMX_RPMSG_GPIO; + msg->header.major = IMX_RMPSG_MAJOR; + msg->header.minor = IMX_RMPSG_MINOR; + msg->header.type = GPIO_RPMSG_SETUP; + msg->header.cmd = GPIO_RPMSG_INPUT_INIT; + msg->pin_idx = gpio; + msg->port_idx = port->idx; + + msg->out.event = GPIO_RPMSG_TRI_IGNORE; + msg->in.wakeup = 0; + + return gpio_send_message(port, msg, &gpio_rpmsg, true); +} + +static inline void imx_rpmsg_gpio_direction_output_init(struct gpio_chip *gc, + unsigned int gpio, int val, struct gpio_rpmsg_data *msg) +{ + struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc); + + msg->header.cate = IMX_RPMSG_GPIO; + msg->header.major = IMX_RMPSG_MAJOR; + msg->header.minor = IMX_RMPSG_MINOR; + msg->header.type = GPIO_RPMSG_SETUP; + msg->header.cmd = GPIO_RPMSG_OUTPUT_INIT; + msg->pin_idx = gpio; + msg->port_idx = port->idx; + msg->out.value = val; +} + +static void imx_rpmsg_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc); + struct gpio_rpmsg_data *msg = NULL; + + msg = gpio_get_pin_msg(port, gpio); + imx_rpmsg_gpio_direction_output_init(gc, gpio, val, msg); + gpio_send_message(port, msg, &gpio_rpmsg, true); +} + +static int imx_rpmsg_gpio_direction_output(struct gpio_chip *gc, + unsigned int gpio, int val) +{ + struct imx_rpmsg_gpio_port *port = gpiochip_get_data(gc); + struct gpio_rpmsg_data *msg = NULL; + + msg = gpio_get_pin_msg(port, gpio); + imx_rpmsg_gpio_direction_output_init(gc, gpio, val, msg); + return gpio_send_message(port, msg, &gpio_rpmsg, true); +} + +static int imx_rpmsg_irq_set_type(struct irq_data *d, u32 type) +{ + struct imx_rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d); + u32 gpio_idx = d->hwirq; + int edge = 0; + int ret = 0; + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + edge = GPIO_RPMSG_TRI_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + edge = GPIO_RPMSG_TRI_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + edge = GPIO_RPMSG_TRI_BOTH_EDGE; + break; + case IRQ_TYPE_LEVEL_LOW: + edge = GPIO_RPMSG_TRI_LOW_LEVEL; + break; + case IRQ_TYPE_LEVEL_HIGH: + edge = GPIO_RPMSG_TRI_HIGH_LEVEL; + break; + default: + ret = -EINVAL; + break; + } + + port->gpio_pins[gpio_idx].irq_type = edge; + return ret; +} + +static int imx_rpmsg_irq_set_wake(struct irq_data *d, u32 enable) +{ + struct imx_rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d); + struct gpio_rpmsg_data *msg = NULL; + u32 gpio_idx = d->hwirq; + + msg = gpio_get_pin_msg(port, gpio_idx); + msg->header.cate = IMX_RPMSG_GPIO; + msg->header.major = IMX_RMPSG_MAJOR; + msg->header.minor = IMX_RMPSG_MINOR; + msg->header.type = GPIO_RPMSG_SETUP; + msg->header.cmd = GPIO_RPMSG_INPUT_INIT; + msg->pin_idx = gpio_idx; + msg->port_idx = port->idx; + + /* set wakeup trigger source, + * if not set irq type, then use high level as trigger type + */ + msg->out.event = port->gpio_pins[gpio_idx].irq_type; + if (!msg->out.event) + msg->out.event = GPIO_RPMSG_TRI_LOW_LEVEL; + + msg->in.wakeup = enable; + + /* here should be atomic context */ + gpio_send_message(port, msg, &gpio_rpmsg, false); + + return 0; +} + +void imx_rpmsg_gpio_do_send(struct work_struct *w) +{ + struct imx_rpmsg_gpio_work *gpio_send_work = + container_of(w, struct imx_rpmsg_gpio_work, rpmsg_send_wq); + + gpio_send_message(gpio_send_work->port, + gpio_send_work->msg, &gpio_rpmsg, false); +} + +/* + * This function will be called at: + * - one interrupt setup. + * - the end of one interrupt happened + * The gpio over rpmsg driver will not write the real register, so save + * all infos before this function and then send all infos to M core in this + * step. + */ +static void imx_rpmsg_unmask_irq(struct irq_data *d) +{ + struct imx_rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d); + struct gpio_rpmsg_data *msg = NULL; + u32 gpio_idx = d->hwirq; + + msg = gpio_get_pin_msg(port, gpio_idx); + msg->header.cate = IMX_RPMSG_GPIO; + msg->header.major = IMX_RMPSG_MAJOR; + msg->header.minor = IMX_RMPSG_MINOR; + msg->header.type = GPIO_RPMSG_SETUP; + msg->header.cmd = GPIO_RPMSG_INPUT_INIT; + msg->pin_idx = gpio_idx; + msg->port_idx = port->idx; + + /* + * set wakeup trigger source, + * if not set irq type, then use high level as trigger type + */ + msg->out.event = port->gpio_pins[gpio_idx].irq_type; + if (!msg->out.event) + msg->out.event = GPIO_RPMSG_TRI_LOW_LEVEL; + + msg->in.wakeup = 0; + + imx_rpmsg_gpio_send_work.msg = msg; + imx_rpmsg_gpio_send_work.port = port; + + queue_work(imx_rpmsg_gpio_workqueue, &(imx_rpmsg_gpio_send_work.rpmsg_send_wq)); +} + +static void imx_rpmsg_mask_irq(struct irq_data *d) +{ + /* + * No need to implement the callback at A core side. + * M core will mask interrupt after a interrupt occurred, and then + * sends a notify to A core. + * After A core dealt with the notify, A core will send a rpmsg to + * M core to enable this interrupt again. + */ +} + +static void imx_rpmsg_irq_shutdown(struct irq_data *d) +{ + struct imx_rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d); + struct gpio_rpmsg_data *msg = NULL; + u32 gpio_idx = d->hwirq; + + msg = gpio_get_pin_msg(port, gpio_idx); + msg->header.cate = IMX_RPMSG_GPIO; + msg->header.major = IMX_RMPSG_MAJOR; + msg->header.minor = IMX_RMPSG_MINOR; + msg->header.type = GPIO_RPMSG_SETUP; + msg->header.cmd = GPIO_RPMSG_INPUT_INIT; + msg->pin_idx = gpio_idx; + msg->port_idx = port->idx; + + /* Disable interrupt here */ + msg->out.event = GPIO_RPMSG_TRI_IGNORE; + msg->in.wakeup = 0; + + imx_rpmsg_gpio_send_work.msg = msg; + imx_rpmsg_gpio_send_work.port = port; + + queue_work(imx_rpmsg_gpio_workqueue, &(imx_rpmsg_gpio_send_work.rpmsg_send_wq)); +} + +static struct irq_chip imx_rpmsg_irq_chip = { + .irq_mask = imx_rpmsg_mask_irq, + .irq_unmask = imx_rpmsg_unmask_irq, + .irq_set_wake = imx_rpmsg_irq_set_wake, + .irq_set_type = imx_rpmsg_irq_set_type, + .irq_shutdown = imx_rpmsg_irq_shutdown, + /* TBD: Add .irq_disable support */ +}; + +static int imx_rpmsg_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct imx_rpmsg_gpio_port *port; + struct gpio_chip *gc; + int i, irq_base; + int ret; + + port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + ret = of_property_read_u32(np, "port_idx", &port->idx); + if (ret) + return ret; + + gpio_rpmsg.port_store[port->idx] = port; + + gc = &port->gc; + gc->of_node = np; + gc->parent = dev; + gc->label = kasprintf(GFP_KERNEL, "imx-rpmsg-gpio-%d", port->idx); + gc->ngpio = IMX_RPMSG_GPIO_PER_PORT; + gc->base = of_alias_get_id(np, "gpio") * IMX_RPMSG_GPIO_PER_PORT; + + gc->direction_input = imx_rpmsg_gpio_direction_input; + gc->direction_output = imx_rpmsg_gpio_direction_output; + gc->get = imx_rpmsg_gpio_get; + gc->set = imx_rpmsg_gpio_set; + + platform_set_drvdata(pdev, port); + + ret = devm_gpiochip_add_data(dev, gc, port); + if (ret < 0) + return ret; + + /* generate one new irq domain */ + port->chip = imx_rpmsg_irq_chip; + port->chip.name = kasprintf(GFP_KERNEL, "rpmsg-irq-port-%d", port->idx); + port->chip.parent_device = NULL; + + irq_base = irq_alloc_descs(-1, 0, IMX_RPMSG_GPIO_PER_PORT, + numa_node_id()); + WARN_ON(irq_base < 0); + + port->domain = irq_domain_add_legacy(np, IMX_RPMSG_GPIO_PER_PORT, + irq_base, 0, + &irq_domain_simple_ops, port); + WARN_ON(!port->domain); + for (i = irq_base; i < irq_base + IMX_RPMSG_GPIO_PER_PORT; i++) { + irq_set_chip_and_handler(i, &port->chip, handle_level_irq); + irq_set_chip_data(i, port); + irq_clear_status_flags(i, IRQ_NOREQUEST); + irq_set_probe(i); + } + + imx_rpmsg_gpio_workqueue = create_workqueue("imx_rpmsg_gpio_workqueue"); + if (!imx_rpmsg_gpio_workqueue) + dev_err(&pdev->dev, "Failed to create imx_rpmsg_gpio_workqueue\n"); + INIT_WORK(&(imx_rpmsg_gpio_send_work.rpmsg_send_wq), imx_rpmsg_gpio_do_send); + + return 0; +} +static const struct of_device_id imx_rpmsg_gpio_dt_ids[] = { + { .compatible = "fsl,imx-rpmsg-gpio" }, + { /* sentinel */ } +}; + +static struct platform_driver imx_rpmsg_gpio_driver = { + .driver = { + .name = "gpio-imx-rpmsg", + .of_match_table = imx_rpmsg_gpio_dt_ids, + }, + .probe = imx_rpmsg_gpio_probe, +}; + +static int gpio_rpmsg_probe(struct rpmsg_device *rpdev) +{ + gpio_rpmsg.rpdev = rpdev; + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + init_completion(&gpio_rpmsg.cmd_complete); + mutex_init(&gpio_rpmsg.lock); + + return platform_driver_register(&imx_rpmsg_gpio_driver); +} + +static struct rpmsg_device_id gpio_rpmsg_id_table[] = { + { .name = "rpmsg-io-channel" }, + {}, +}; + +static struct rpmsg_driver gpio_rpmsg_driver = { + .drv.name = "gpio_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = gpio_rpmsg_id_table, + .probe = gpio_rpmsg_probe, + .callback = gpio_rpmsg_cb, +}; + + +static int __init gpio_imx_rpmsg_init(void) +{ + return register_rpmsg_driver(&gpio_rpmsg_driver); +} +device_initcall(gpio_imx_rpmsg_init); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("NXP i.MX7ULP rpmsg gpio driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-max732x.c b/drivers/gpio/gpio-max732x.c index 238cbe926b9f..4af7ea075fb5 100644 --- a/drivers/gpio/gpio-max732x.c +++ b/drivers/gpio/gpio-max732x.c @@ -19,6 +19,7 @@ #include <linux/i2c.h> #include <linux/platform_data/max732x.h> #include <linux/of.h> +#include <linux/reset.h> /* @@ -77,6 +78,12 @@ #define INT_CAPS(x) (((uint64_t)(x)) << 32) enum { + OUTPUT_MASK, + OUTPUT_VAL, + OUTPUT_NUM, +}; + +enum { MAX7319, MAX7320, MAX7321, @@ -622,6 +629,8 @@ static int max732x_probe(struct i2c_client *client, struct i2c_client *c; uint16_t addr_a, addr_b; int ret, nr_port; + u16 out_set[OUTPUT_NUM]; + unsigned long mask, val; pdata = dev_get_platdata(&client->dev); node = client->dev.of_node; @@ -639,6 +648,10 @@ static int max732x_probe(struct i2c_client *client, return -ENOMEM; chip->client = client; + ret = device_reset(&client->dev); + if (ret == -EPROBE_DEFER) + return ret; + nr_port = max732x_setup_gpio(chip, id, pdata->gpio_base); chip->gpio_chip.parent = &client->dev; @@ -711,6 +724,15 @@ static int max732x_probe(struct i2c_client *client, } i2c_set_clientdata(client, chip); + + /* set the output IO default voltage */ + if (!of_property_read_u16_array(node, "out-default", out_set, + ARRAY_SIZE(out_set))) { + mask = out_set[OUTPUT_MASK] & chip->dir_output; + val = out_set[OUTPUT_VAL]; + max732x_gpio_set_multiple(&chip->gpio_chip, &mask, &val); + } + return 0; } diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index 70d6ae20b1da..d8532c448976 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -432,6 +432,16 @@ static int mpc8xxx_remove(struct platform_device *pdev) return 0; } +static void mpc8xxx_shutdown(struct platform_device *pdev) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = platform_get_drvdata(pdev); + + if (mpc8xxx_gc->irq) { + irq_set_chained_handler_and_data(mpc8xxx_gc->irqn, NULL, NULL); + irq_domain_remove(mpc8xxx_gc->irq); + } +} + #ifdef CONFIG_ACPI static const struct acpi_device_id gpio_acpi_ids[] = { {"NXP0031",}, @@ -443,6 +453,7 @@ MODULE_DEVICE_TABLE(acpi, gpio_acpi_ids); static struct platform_driver mpc8xxx_plat_driver = { .probe = mpc8xxx_probe, .remove = mpc8xxx_remove, + .shutdown = mpc8xxx_shutdown, .driver = { .name = "gpio-mpc8xxx", .of_match_table = mpc8xxx_gpio_ids, diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c index c871602fc5ba..b0c0047c03c9 100644 --- a/drivers/gpio/gpio-mxc.c +++ b/drivers/gpio/gpio-mxc.c @@ -17,12 +17,47 @@ #include <linux/irqchip/chained_irq.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/syscore_ops.h> #include <linux/gpio/driver.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/bug.h> +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP +#include <linux/firmware/imx/sci.h> + +#define IMX_SC_PAD_FUNC_GET_WAKEUP 9 +#define IMX_SC_PAD_FUNC_SET_WAKEUP 4 +#define IMX_SC_PAD_WAKEUP_OFF 0 +#endif + +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP +struct mxc_gpio_pad_wakeup { + u32 pin_id; + u32 type; + u32 line; +}; + +struct imx_sc_msg_gpio_get_pad_wakeup { + struct imx_sc_rpc_msg hdr; + union { + struct req_pad { + u16 pad; + } __packed req; + struct resp_wakeup { + u8 wakeup; + } resp; + } data; +} __packed __aligned(4); + +struct imx_sc_msg_gpio_set_pad_wakeup { + struct imx_sc_rpc_msg hdr; + u16 pad; + u8 wakeup; +} __packed __aligned(4); + +#endif /* device type dependent stuff */ struct mxc_gpio_hwdata { @@ -62,8 +97,17 @@ struct mxc_gpio_port { struct mxc_gpio_reg_saved gpio_saved_reg; bool power_off; const struct mxc_gpio_hwdata *hwdata; + bool gpio_ranges; +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP + u32 pad_wakeup_num; + struct mxc_gpio_pad_wakeup pad_wakeup[32]; +#endif }; +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP +static struct imx_sc_ipc *gpio_ipc_handle; +#endif + static struct mxc_gpio_hwdata imx1_imx21_gpio_hwdata = { .dr_reg = 0x1c, .gdir_reg = 0x00, @@ -285,6 +329,85 @@ static void mx2_gpio_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP +static int mxc_gpio_get_pad_wakeup(struct mxc_gpio_port *port) +{ + struct imx_sc_msg_gpio_get_pad_wakeup msg; + struct imx_sc_rpc_msg *hdr = &msg.hdr; + u8 wakeup_type; + int ret; + int i; + + hdr->ver = IMX_SC_RPC_VERSION; + hdr->svc = IMX_SC_RPC_SVC_PAD; + hdr->func = IMX_SC_PAD_FUNC_GET_WAKEUP; + hdr->size = 2; + + for (i = 0; i < port->pad_wakeup_num; i++) { + /* get original pad type */ + wakeup_type = port->pad_wakeup[i].type; + msg.data.req.pad = port->pad_wakeup[i].pin_id; + ret = imx_scu_call_rpc(gpio_ipc_handle, &msg, true); + if (ret) { + dev_err(port->gc.parent, "get pad wakeup failed, ret %d\n", ret); + return ret; + } + wakeup_type = msg.data.resp.wakeup; + /* return wakeup gpio pin's line */ + if (wakeup_type != port->pad_wakeup[i].type) + return port->pad_wakeup[i].line; + } + + return -EINVAL; +} + +static void mxc_gpio_set_pad_wakeup(struct mxc_gpio_port *port, bool enable) +{ + struct imx_sc_msg_gpio_set_pad_wakeup msg; + struct imx_sc_rpc_msg *hdr = &msg.hdr; + int ret; + int i; + + hdr->ver = IMX_SC_RPC_VERSION; + hdr->svc = IMX_SC_RPC_SVC_PAD; + hdr->func = IMX_SC_PAD_FUNC_SET_WAKEUP; + hdr->size = 2; + + for (i = 0; i < port->pad_wakeup_num; i++) { + msg.pad = port->pad_wakeup[i].pin_id; + msg.wakeup = enable ? port->pad_wakeup[i].type : IMX_SC_PAD_WAKEUP_OFF; + ret = imx_scu_call_rpc(gpio_ipc_handle, &msg, true); + if (ret) { + dev_err(port->gc.parent, "set pad wakeup failed, ret %d\n", ret); + return; + } + } +} + +static void mxc_gpio_handle_pad_wakeup(struct mxc_gpio_port *port, int line) +{ + struct irq_desc *desc = irq_to_desc(port->irq); + struct irq_chip *chip = irq_desc_get_chip(desc); + u32 irq_stat; + + /* skip invalid line */ + if (line > 31) { + dev_err(port->gc.parent, "invalid wakeup line %d\n", line); + return; + } + + dev_info(port->gc.parent, "wakeup by pad, line %d\n", line); + + chained_irq_enter(chip, desc); + + irq_stat = (1 << line); + + mxc_gpio_irq_handler(port, irq_stat); + + chained_irq_exit(chip, desc); +} +#endif + /* * Set interrupt number "irq" in the GPIO as a wake-up source. * While system is running, all registered GPIO interrupts need to have @@ -316,7 +439,32 @@ static int gpio_set_wake_irq(struct irq_data *d, u32 enable) return ret; } -static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base) +static int mxc_gpio_irq_reqres(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mxc_gpio_port *port = gc->private; + + if (gpiochip_lock_as_irq(&port->gc, d->hwirq)) { + dev_err(port->gc.parent, + "unable to lock HW IRQ %lu for IRQ\n", + d->hwirq); + return -EINVAL; + } + + return irq_chip_pm_get(d); +} + +static void mxc_gpio_irq_relres(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mxc_gpio_port *port = gc->private; + + gpiochip_unlock_as_irq(&port->gc, d->hwirq); + irq_chip_pm_put(d); +} + +static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base, + struct device *dev) { struct irq_chip_generic *gc; struct irq_chip_type *ct; @@ -329,12 +477,15 @@ static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base) gc->private = port; ct = gc->chip_types; + ct->chip.parent_device = dev; ct->chip.irq_ack = irq_gc_ack_set_bit; ct->chip.irq_mask = irq_gc_mask_clr_bit; ct->chip.irq_unmask = irq_gc_mask_set_bit; ct->chip.irq_set_type = gpio_set_irq_type; ct->chip.irq_set_wake = gpio_set_wake_irq; ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND; + ct->chip.irq_request_resources = mxc_gpio_irq_reqres; + ct->chip.irq_release_resources = mxc_gpio_irq_relres, ct->regs.ack = GPIO_ISR; ct->regs.mask = GPIO_IMR; @@ -352,6 +503,30 @@ static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset) return irq_find_mapping(port->domain, offset); } +static int mxc_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct mxc_gpio_port *port = gpiochip_get_data(chip); + int ret; + + if (port->gpio_ranges) { + ret = gpiochip_generic_request(chip, offset); + if (ret) + return ret; + } + + ret = pm_runtime_get_sync(chip->parent); + return ret < 0 ? ret : 0; +} + +static void mxc_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct mxc_gpio_port *port = gpiochip_get_data(chip); + + if (port->gpio_ranges) + gpiochip_generic_free(chip, offset); + pm_runtime_put(chip->parent); +} + static int mxc_gpio_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -359,6 +534,9 @@ static int mxc_gpio_probe(struct platform_device *pdev) int irq_count; int irq_base; int err; +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP + int i; +#endif port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); if (!port) @@ -377,7 +555,7 @@ static int mxc_gpio_probe(struct platform_device *pdev) return irq_count; if (irq_count > 1) { - port->irq_high = platform_get_irq(pdev, 1); + port->irq_high = platform_get_irq_optional(pdev, 1); if (port->irq_high < 0) port->irq_high = 0; } @@ -397,9 +575,44 @@ static int mxc_gpio_probe(struct platform_device *pdev) return err; } +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP + /* + * parse pad wakeup info from dtb, each pad has to provide + * <pin_id, type, line>, these info should be put in each + * gpio node and with a "pad-wakeup-num" to indicate the + * total lines are with pad wakeup enabled. + */ + if (!of_property_read_u32(np, "pad-wakeup-num", &port->pad_wakeup_num)) { + if (port->pad_wakeup_num != 0) { + if (!gpio_ipc_handle) { + err = imx_scu_get_handle(&gpio_ipc_handle); + if (err) + return err; + } + for (i = 0; i < port->pad_wakeup_num; i++) { + of_property_read_u32_index(np, "pad-wakeup", + i * 3 + 0, &port->pad_wakeup[i].pin_id); + of_property_read_u32_index(np, "pad-wakeup", + i * 3 + 1, &port->pad_wakeup[i].type); + of_property_read_u32_index(np, "pad-wakeup", + i * 3 + 2, &port->pad_wakeup[i].line); + } + err = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_WAKE, IMX_SC_IRQ_PAD, true); + if (err) + dev_warn(&pdev->dev, "Enable irq failed, GPIO pad wakeup NOT supported\n"); + } + } +#endif + if (of_device_is_compatible(np, "fsl,imx7d-gpio")) port->power_off = true; + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + err = pm_runtime_get_sync(&pdev->dev); + if (err < 0) + goto out_pm_dis; + /* disable the interrupt and clear the status */ writel(0, port->base + GPIO_IMR); writel(~0, port->base + GPIO_ISR); @@ -430,8 +643,14 @@ static int mxc_gpio_probe(struct platform_device *pdev) if (err) goto out_bgio; - port->gc.request = gpiochip_generic_request; - port->gc.free = gpiochip_generic_free; + if (of_property_read_bool(np, "gpio_ranges")) + port->gpio_ranges = true; + else + port->gpio_ranges = false; + + port->gc.request = mxc_gpio_request; + port->gc.free = mxc_gpio_free; + port->gc.parent = &pdev->dev; port->gc.to_irq = mxc_gpio_to_irq; port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 : pdev->id * 32; @@ -454,16 +673,20 @@ static int mxc_gpio_probe(struct platform_device *pdev) } /* gpio-mxc can be a generic irq chip */ - err = mxc_gpio_init_gc(port, irq_base); + err = mxc_gpio_init_gc(port, irq_base, &pdev->dev); if (err < 0) goto out_irqdomain_remove; list_add_tail(&port->node, &mxc_gpio_ports); platform_set_drvdata(pdev, port); + pm_runtime_put(&pdev->dev); return 0; +out_pm_dis: + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(port->clk); out_irqdomain_remove: irq_domain_remove(port->domain); out_bgio: @@ -498,12 +721,73 @@ static void mxc_gpio_restore_regs(struct mxc_gpio_port *port) writel(port->gpio_saved_reg.dr, port->base + GPIO_DR); } +static int __maybe_unused mxc_gpio_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mxc_gpio_port *port = platform_get_drvdata(pdev); + + mxc_gpio_save_regs(port); + clk_disable_unprepare(port->clk); + + return 0; +} + +static int __maybe_unused mxc_gpio_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mxc_gpio_port *port = platform_get_drvdata(pdev); + int ret; + + ret = clk_prepare_enable(port->clk); + if (ret) + return ret; + + mxc_gpio_restore_regs(port); + + return 0; +} + +static int __maybe_unused mxc_gpio_noirq_suspend(struct device *dev) +{ +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP + struct platform_device *pdev = to_platform_device(dev); + struct mxc_gpio_port *port = platform_get_drvdata(pdev); + + mxc_gpio_set_pad_wakeup(port, true); +#endif + return 0; +} + +static int __maybe_unused mxc_gpio_noirq_resume(struct device *dev) +{ +#ifdef CONFIG_GPIO_MXC_PAD_WAKEUP + struct platform_device *pdev = to_platform_device(dev); + struct mxc_gpio_port *port = platform_get_drvdata(pdev); + int wakeup_line = mxc_gpio_get_pad_wakeup(port); + + mxc_gpio_set_pad_wakeup(port, false); + + if (wakeup_line >= 0) + mxc_gpio_handle_pad_wakeup(port, wakeup_line); +#endif + return 0; +} + +static const struct dev_pm_ops mxc_gpio_dev_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mxc_gpio_noirq_suspend, mxc_gpio_noirq_resume) + SET_RUNTIME_PM_OPS(mxc_gpio_runtime_suspend, mxc_gpio_runtime_resume, NULL) +}; + static int mxc_gpio_syscore_suspend(void) { struct mxc_gpio_port *port; + int ret; /* walk through all ports */ list_for_each_entry(port, &mxc_gpio_ports, node) { + ret = clk_prepare_enable(port->clk); + if (ret) + return ret; mxc_gpio_save_regs(port); clk_disable_unprepare(port->clk); } @@ -524,6 +808,7 @@ static void mxc_gpio_syscore_resume(void) return; } mxc_gpio_restore_regs(port); + clk_disable_unprepare(port->clk); } } @@ -537,6 +822,7 @@ static struct platform_driver mxc_gpio_driver = { .name = "gpio-mxc", .of_match_table = mxc_gpio_dt_ids, .suppress_bind_attrs = true, + .pm = &mxc_gpio_dev_pm_ops, }, .probe = mxc_gpio_probe, }; diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index d2fe76f3f34f..bf9288d307e5 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -20,6 +20,7 @@ #include <linux/platform_data/pca953x.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/reset.h> #include <linux/slab.h> #include <asm/unaligned.h> @@ -982,14 +983,19 @@ static int pca953x_probe(struct i2c_client *client, chip->client = client; - reg = devm_regulator_get(&client->dev, "vcc"); - if (IS_ERR(reg)) - return dev_err_probe(&client->dev, PTR_ERR(reg), "reg get err\n"); + reg = devm_regulator_get_optional(&client->dev, "vcc"); + if (IS_ERR(reg)) { + if (PTR_ERR(reg) != -ENODEV) + return dev_err_probe(&client->dev, PTR_ERR(reg), "reg get err\n"); + reg = NULL; + } - ret = regulator_enable(reg); - if (ret) { - dev_err(&client->dev, "reg en err: %d\n", ret); - return ret; + if (reg) { + ret = regulator_enable(reg); + if (ret) { + dev_err(&client->dev, "reg en err: %d\n", ret); + return ret; + } } chip->regulator = reg; @@ -1047,6 +1053,10 @@ static int pca953x_probe(struct i2c_client *client, lockdep_set_subclass(&chip->i2c_lock, i2c_adapter_depth(client->adapter)); + ret = device_reset(&client->dev); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* initialize cached registers from their original values. * we can't share this chip with another i2c master. */ @@ -1079,7 +1089,8 @@ static int pca953x_probe(struct i2c_client *client, return 0; err_exit: - regulator_disable(chip->regulator); + if (chip->regulator) + regulator_disable(chip->regulator); return ret; } @@ -1098,7 +1109,8 @@ static int pca953x_remove(struct i2c_client *client) ret = 0; } - regulator_disable(chip->regulator); + if (chip->regulator) + regulator_disable(chip->regulator); return ret; } @@ -1159,7 +1171,8 @@ static int pca953x_suspend(struct device *dev) if (atomic_read(&chip->wakeup_path)) device_set_wakeup_path(dev); else - regulator_disable(chip->regulator); + if (chip->regulator) + regulator_disable(chip->regulator); return 0; } @@ -1169,15 +1182,20 @@ static int pca953x_resume(struct device *dev) struct pca953x_chip *chip = dev_get_drvdata(dev); int ret; + regcache_cache_only(chip->regmap, false); + if (!atomic_read(&chip->wakeup_path)) { - ret = regulator_enable(chip->regulator); - if (ret) { - dev_err(dev, "Failed to enable regulator: %d\n", ret); + if (chip->regulator) { + ret = regulator_enable(chip->regulator); + if (ret) { + dev_err(dev, "Failed to enable regulator: %d\n", ret); + return 0; + } + } else { return 0; } } - regcache_cache_only(chip->regmap, false); regcache_mark_dirty(chip->regmap); ret = pca953x_regcache_sync(dev); if (ret) diff --git a/drivers/gpio/gpio-scu.c b/drivers/gpio/gpio-scu.c new file mode 100644 index 000000000000..5fb2d8b72653 --- /dev/null +++ b/drivers/gpio/gpio-scu.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2021 NXP + * + * The driver exports a standard gpiochip interface + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/gpio/driver.h> +#include <linux/platform_device.h> +#include <linux/firmware/imx/svc/rm.h> +#include <dt-bindings/firmware/imx/rsrc.h> + +#define PIN_NUMBER 8 + +struct imxscfw { + struct mutex lock; + struct imx_sc_ipc *handle; + struct gpio_chip chip; + struct device *dev; +}; + +static unsigned int sc_arr[] = { + IMX_SC_R_BOARD_R0, + IMX_SC_R_BOARD_R1, + IMX_SC_R_BOARD_R2, + IMX_SC_R_BOARD_R3, + IMX_SC_R_BOARD_R4, + IMX_SC_R_BOARD_R5, + IMX_SC_R_BOARD_R6, //R6 is MII select + IMX_SC_R_BOARD_R7, +}; + +static int imxscfw_get(struct gpio_chip *chip, unsigned int offset) +{ + struct imxscfw *scu = gpiochip_get_data(chip); + int err = -EINVAL, level = 0; + + if (offset >= sizeof(sc_arr)/sizeof(unsigned int)) + return err; + + mutex_lock(&scu->lock); + + /* to read PIN state via scu api */ + err = imx_sc_misc_get_control(scu->handle, sc_arr[offset], + 0, &level); + mutex_unlock(&scu->lock); + + if (err) { + pr_err("%s: failed %d\n", __func__, err); + return -EINVAL; + } + + return level; +} + +static void imxscfw_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct imxscfw *scu = gpiochip_get_data(chip); + int err; + + if (offset >= sizeof(sc_arr)/sizeof(unsigned int)) + return; + + mutex_lock(&scu->lock); + + /* to set PIN output level via scu api */ + err = imx_sc_misc_set_control(scu->handle, sc_arr[offset], 0, value); + + mutex_unlock(&scu->lock); + + if (err) + pr_err("%s: failed %d\n", __func__, err); + + +} + +static int imx_scu_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct imxscfw *port; + struct gpio_chip *gc; + int ret; + + port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + ret = imx_scu_get_handle(&port->handle); + if (ret) + return ret; + + mutex_init(&port->lock); + gc = &port->chip; + gc->of_node = np; + gc->parent = dev; + gc->label = "imx-scu-gpio"; + gc->ngpio = PIN_NUMBER; + gc->base = of_alias_get_id(np, "gpio") * 32; + + gc->get = imxscfw_get; + gc->set = imxscfw_set; + + platform_set_drvdata(pdev, port); + + ret = devm_gpiochip_add_data(dev, gc, port); + + return ret; +} + +static const struct of_device_id imx_scu_gpio_dt_ids[] = { + { .compatible = "fsl,imx-scu-gpio" }, + { /* sentinel */ } +}; + +static struct platform_driver imx_scu_gpio_driver = { + .driver = { + .name = "gpio-imx-scu", + .of_match_table = imx_scu_gpio_dt_ids, + }, + .probe = imx_scu_gpio_probe, +}; + +static int __init _imx_scu_gpio_init(void) +{ + return platform_driver_register(&imx_scu_gpio_driver); +} + +subsys_initcall_sync(_imx_scu_gpio_init); + +MODULE_AUTHOR("Shenwei Wang"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("NXP GPIO over SCU-MISC API, i.MX8"); diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index e0f2b67558e7..4c5ec52c723f 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -300,7 +300,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) gc = &port->gc; gc->of_node = np; gc->parent = dev; - gc->label = "vf610-gpio"; + gc->label = dev_name(dev); gc->ngpio = VF610_GPIO_PER_PORT; gc->base = of_alias_get_id(np, "gpio") * VF610_GPIO_PER_PORT; |