summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Sperl <kernel@martin.sperl.org>2018-08-26 20:50:34 +0000
committerMarcel Ziswiler <marcel.ziswiler@toradex.com>2020-02-09 22:38:55 +0100
commitcf3b06aa05645f1e83548c3bfeeba5b0f525e11e (patch)
tree4987e0a83804821261ac13b7dc57d32392e5db60
parent02ed03ad5dcd2e2a3b663741dae87e273a57be75 (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/Makefile1
-rw-r--r--drivers/net/can/spi/mcp25xxfd/mcp25xxfd_base.c13
-rw-r--r--drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.c255
-rw-r--r--drivers/net/can/spi/mcp25xxfd/mcp25xxfd_gpio.h16
-rw-r--r--drivers/net/can/spi/mcp25xxfd/mcp25xxfd_priv.h5
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 */