diff options
author | Martin Sperl <kernel@martin.sperl.org> | 2018-08-26 20:50:34 +0000 |
---|---|---|
committer | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2020-02-09 22:38:55 +0100 |
commit | cf3b06aa05645f1e83548c3bfeeba5b0f525e11e (patch) | |
tree | 4987e0a83804821261ac13b7dc57d32392e5db60 | |
parent | 02ed03ad5dcd2e2a3b663741dae87e273a57be75 (diff) |
can: mcp25xxfd: add gpiolib support for GPIO0/1 (aka. INT0/INT1)
Add GPIOLIB support to mcp25xxfd.
--
Changelog:
V4 -> V5: reorganisation of the patchset into smaller patches
V7 -> V8: include mcp25xxfd_gpio.h to fix sparse checks
as per feedback from Marc Kleine-Budde
added xstandby and open drain support
Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
(cherry picked from commit a350a0e406c4c3d988d6486ab893a4755b441d7d)
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c | 13 | ||||
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.c | 255 | ||||
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.h | 16 | ||||
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h | 5 |
5 files changed, 289 insertions, 1 deletions
diff --git a/drivers/net/can/spi/mcp25xxfd/Makefile b/drivers/net/can/spi/mcp25xxfd/Makefile index 2e2f05790d5d..4eb61e6808d0 100644 --- a/drivers/net/can/spi/mcp25xxfd/Makefile +++ b/drivers/net/can/spi/mcp25xxfd/Makefile @@ -9,4 +9,5 @@ mcp25xxfd-objs += mcp25xxfd_cmd.o mcp25xxfd-objs += mcp25xxfd_crc.o mcp25xxfd-objs += mcp25xxfd_debugfs.o mcp25xxfd-objs += mcp25xxfd_ecc.o +mcp25xxfd-objs += mcp25xxfd_gpio.o mcp25xxfd-objs += mcp25xxfd_int.o diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c index e9303bf4e2cc..52a66a560276 100644 --- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c +++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c @@ -18,6 +18,7 @@ #include "mcp25xxfd_cmd.h" #include "mcp25xxfd_debugfs.h" #include "mcp25xxfd_ecc.h" +#include "mcp25xxfd_gpio.h" #include "mcp25xxfd_int.h" #include "mcp25xxfd_priv.h" @@ -148,15 +149,22 @@ static int mcp25xxfd_base_probe(struct spi_device *spi) if (ret) goto out_debugfs; + /* setting up GPIO */ + ret = mcp25xxfd_gpio_setup(priv); + if (ret) + goto out_debugfs; + /* and put controller to sleep by stopping the can clock */ ret = mcp25xxfd_clock_stop(priv, MCP25XXFD_CLK_USER_CAN); if (ret) - goto out_debugfs; + goto out_gpio; dev_info(&spi->dev, "MCP%x successfully initialized.\n", priv->model); return 0; +out_gpio: + mcp25xxfd_gpio_remove(priv); out_debugfs: mcp25xxfd_debugfs_remove(priv); out_ctlclk: @@ -174,6 +182,9 @@ static int mcp25xxfd_base_remove(struct spi_device *spi) { struct mcp25xxfd_priv *priv = spi_get_drvdata(spi); + /* remove gpio */ + mcp25xxfd_gpio_remove(priv); + /* clear all running clocks */ mcp25xxfd_clock_stop(priv, priv->clk_user_mask); diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.c new file mode 100644 index 000000000000..60dc9f98d892 --- /dev/null +++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface + * + * Copyright 2019 Martin Sperl <kernel@martin.sperl.org> + * + * Based on Microchip MCP251x CAN controller driver written by + * David Vrabel, Copyright 2006 Arcom Control Systems Ltd. + */ + +#include <linux/gpio/driver.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "mcp25xxfd_clock.h" +#include "mcp25xxfd_cmd.h" +#include "mcp25xxfd_gpio.h" +#include "mcp25xxfd_priv.h" + +/* GPIO component */ +#ifdef CONFIG_GPIOLIB + +enum mcp25xxfd_gpio_pins { + MCP25XXFD_GPIO_GPIO0 = 0, + MCP25XXFD_GPIO_GPIO1 = 1, +}; + +static int mcp25xxfd_gpio_request(struct gpio_chip *chip, + unsigned int offset) +{ + struct mcp25xxfd_priv *priv = gpiochip_get_data(chip); + int clock_requestor = offset ? + MCP25XXFD_CLK_USER_GPIO1 : MCP25XXFD_CLK_USER_GPIO0; + + /* only handle gpio 0/1 */ + if (offset > 1) + return -EINVAL; + + /* if we have XSTANDBY enabled then gpio0 is not available either */ + if (priv->config.gpio0_xstandby && offset == 0) + return -EINVAL; + + mcp25xxfd_clock_start(priv, clock_requestor); + + return 0; +} + +static void mcp25xxfd_gpio_free(struct gpio_chip *chip, + unsigned int offset) +{ + struct mcp25xxfd_priv *priv = gpiochip_get_data(chip); + int clock_requestor = offset ? + MCP25XXFD_CLK_USER_GPIO1 : MCP25XXFD_CLK_USER_GPIO0; + + /* only handle gpio 0/1 */ + if (offset > 1) + return; + + mcp25xxfd_clock_stop(priv, clock_requestor); +} + +static int mcp25xxfd_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct mcp25xxfd_priv *priv = gpiochip_get_data(chip); + u32 mask = (offset) ? MCP25XXFD_IOCON_GPIO1 : MCP25XXFD_IOCON_GPIO0; + int ret; + + /* only handle gpio 0/1 */ + if (offset > 1) + return -EINVAL; + + /* read the relevant gpio Latch */ + ret = mcp25xxfd_cmd_read_mask(priv->spi, MCP25XXFD_IOCON, + &priv->regs.iocon, mask); + if (ret) + return ret; + + /* return the match */ + return priv->regs.iocon & mask; +} + +static void mcp25xxfd_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct mcp25xxfd_priv *priv = gpiochip_get_data(chip); + u32 mask = (offset) ? MCP25XXFD_IOCON_LAT1 : MCP25XXFD_IOCON_LAT0; + + /* only handle gpio 0/1 */ + if (offset > 1) + return; + + /* update in memory representation with the corresponding value */ + if (value) + priv->regs.iocon |= mask; + else + priv->regs.iocon &= ~mask; + + mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_IOCON, + priv->regs.iocon, mask); +} + +static int mcp25xxfd_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct mcp25xxfd_priv *priv = gpiochip_get_data(chip); + u32 mask_tri = (offset) ? + MCP25XXFD_IOCON_TRIS1 : MCP25XXFD_IOCON_TRIS0; + u32 mask_stby = (offset) ? + 0 : MCP25XXFD_IOCON_XSTBYEN; + u32 mask_pm = (offset) ? + MCP25XXFD_IOCON_PM1 : MCP25XXFD_IOCON_PM0; + + /* only handle gpio 0/1 */ + if (offset > 1) + return -EINVAL; + + /* set the mask */ + priv->regs.iocon |= mask_tri | mask_pm; + + /* clear stby */ + priv->regs.iocon &= ~mask_stby; + + return mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_IOCON, + priv->regs.iocon, + mask_tri | mask_stby | mask_pm); +} + +static int mcp25xxfd_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct mcp25xxfd_priv *priv = gpiochip_get_data(chip); + u32 mask_tri = (offset) ? + MCP25XXFD_IOCON_TRIS1 : MCP25XXFD_IOCON_TRIS0; + u32 mask_lat = (offset) ? + MCP25XXFD_IOCON_LAT1 : MCP25XXFD_IOCON_LAT0; + u32 mask_pm = (offset) ? + MCP25XXFD_IOCON_PM1 : MCP25XXFD_IOCON_PM0; + u32 mask_stby = (offset) ? + 0 : MCP25XXFD_IOCON_XSTBYEN; + + /* only handle gpio 0/1 */ + if (offset > 1) + return -EINVAL; + + /* clear the tristate bit and also clear stby */ + priv->regs.iocon &= ~(mask_tri | mask_stby); + + /* set GPIO mode */ + priv->regs.iocon |= mask_pm; + + /* set the value */ + if (value) + priv->regs.iocon |= mask_lat; + else + priv->regs.iocon &= ~mask_lat; + + return mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_IOCON, + priv->regs.iocon, + mask_tri | mask_lat | + mask_pm | mask_stby); +} + +#ifdef CONFIG_OF_DYNAMIC +static void mcp25xxfd_gpio_read_of(struct mcp25xxfd_priv *priv) +{ + const struct device_node *np = priv->spi->dev.of_node; + + priv->config.gpio_open_drain = + of_property_read_bool(np, "microchip,gpio-open-drain"); + priv->config.gpio0_xstandby = + of_property_read_bool(np, "microchip,gpio0-xstandby"); +} +#else +static void mcp25xxfd_gpio_read_of(struct mcp25xxfd_priv *priv) +{ + priv->config.gpio_open_drain = false; + priv->config.gpio0_xstandby = false; +} +#endif + +static int mcp25xxfd_gpio_setup_regs(struct mcp25xxfd_priv *priv) +{ + /* handle open-drain */ + if (priv->config.gpio_open_drain) { + priv->regs.iocon |= MCP25XXFD_IOCON_INTOD; + } else { + priv->regs.iocon &= ~MCP25XXFD_IOCON_INTOD; + } + + /* handle xstandby */ + if (priv->config.gpio0_xstandby) { + priv->regs.iocon &= ~(MCP25XXFD_IOCON_TRIS0 | + MCP25XXFD_IOCON_GPIO0); + priv->regs.iocon |= MCP25XXFD_IOCON_XSTBYEN; + } else { + priv->regs.iocon &= ~(MCP25XXFD_IOCON_XSTBYEN); + } + + /* update the iocon register */ + return mcp25xxfd_cmd_write_regs(priv->spi, MCP25XXFD_IOCON, + &priv->regs.iocon, sizeof(u32)); +} + +static int mcp25xxfd_gpio_setup_gpiochip(struct mcp25xxfd_priv *priv) +{ + struct gpio_chip *gpio = &priv->gpio; + + /* gpiochip only handles GPIO0 and GPIO1 */ + gpio->owner = THIS_MODULE; + gpio->parent = &priv->spi->dev; + gpio->label = dev_name(&priv->spi->dev); + gpio->direction_input = mcp25xxfd_gpio_direction_input; + gpio->get = mcp25xxfd_gpio_get; + gpio->direction_output = mcp25xxfd_gpio_direction_output; + gpio->set = mcp25xxfd_gpio_set; + gpio->request = mcp25xxfd_gpio_request; + gpio->free = mcp25xxfd_gpio_free; + gpio->base = -1; + gpio->ngpio = 2; + gpio->can_sleep = 1; + + return gpiochip_add_data(gpio, priv); +} + +int mcp25xxfd_gpio_setup(struct mcp25xxfd_priv *priv) +{ + int ret; + + /* setting up defaults */ + priv->config.gpio0_xstandby = false; + + mcp25xxfd_gpio_read_of(priv); + ret = mcp25xxfd_gpio_setup_regs(priv); + if (ret) + return ret; + + return mcp25xxfd_gpio_setup_gpiochip(priv); +} + +void mcp25xxfd_gpio_remove(struct mcp25xxfd_priv *priv) +{ + gpiochip_remove(&priv->gpio); +} + +#else +int mcp25xxfd_gpio_setup(struct mcp25xxfd_priv *priv) +{ + return 0; +} + +void mcp25xxfd_gpio_remove(struct mcp25xxfd_priv *priv) +{ +} +#endif diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.h new file mode 100644 index 000000000000..46740e8abc45 --- /dev/null +++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface + * + * Copyright 2019 Martin Sperl <kernel@martin.sperl.org> + */ +#ifndef __MCP25XXFD_GPIO_H +#define __MCP25XXFD_GPIO_H + +#include "mcp25xxfd_priv.h" + +/* gpiolib support */ +int mcp25xxfd_gpio_setup(struct mcp25xxfd_priv *priv); +void mcp25xxfd_gpio_remove(struct mcp25xxfd_priv *priv); + +#endif /* __MCP25XXFD_GPIO_H */ diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h index b4b574613306..e13ed7b992e5 100644 --- a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h +++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h @@ -9,6 +9,7 @@ #include <linux/clk.h> #include <linux/debugfs.h> +#include <linux/gpio/driver.h> #include <linux/mutex.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> @@ -26,6 +27,7 @@ enum mcp25xxfd_model { struct mcp25xxfd_priv { struct spi_device *spi; struct clk *clk; + struct gpio_chip gpio; /* the actual model of the mcp25xxfd */ enum mcp25xxfd_model model; @@ -40,6 +42,9 @@ struct mcp25xxfd_priv { int clock_pll; int clock_div2; int clock_odiv; + /* gpio related */ + bool gpio_open_drain; + bool gpio0_xstandby; } config; /* lock for enabling/disabling the clock */ |