[PATCH v3 3/3] can: mcp251x: Add GPIO support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The mcp251x variants feature 3 general purpose digital inputs and 2
outputs. With this patch they are accessible through the gpio framework.

Signed-off-by: Timo Schlüßler <schluessler@xxxxxxxxx>
---
 drivers/net/can/spi/mcp251x.c | 232 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 232 insertions(+)

diff --git a/drivers/net/can/spi/mcp251x.c b/drivers/net/can/spi/mcp251x.c
index 8d65b73..fb50392 100644
--- a/drivers/net/can/spi/mcp251x.c
+++ b/drivers/net/can/spi/mcp251x.c
@@ -38,6 +38,8 @@
 #include <linux/spi/spi.h>
 #include <linux/uaccess.h>
 #include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
 
 /* SPI interface instruction set */
 #define INSTRUCTION_WRITE	0x02
@@ -52,6 +54,15 @@
 #define INSTRUCTION_RTS(n)	(0x80 | ((n) & 0x07))
 
 /* MPC251x registers */
+#define BFPCTRL			0x0c
+#  define BFPCTRL_B0BFE		0x04
+#  define BFPCTRL_B1BFE		0x08
+#  define BFPCTRL_B0BFS		0x10
+#  define BFPCTRL_B1BFS		0x20
+#define TXRTSCTRL		0x0d
+#  define TXRTSCTRL_B0RTS	0x08
+#  define TXRTSCTRL_B1RTS	0x10
+#  define TXRTSCTRL_B2RTS	0x20
 #define CANSTAT	      0x0e
 #define CANCTRL	      0x0f
 #  define CANCTRL_REQOP_MASK	    0xe0
@@ -225,6 +236,10 @@ struct mcp251x_priv {
 	struct regulator *power;
 	struct regulator *transceiver;
 	struct clk *clk;
+#ifdef CONFIG_GPIOLIB
+	struct gpio_chip gpio;
+	u8 reg_bfpctrl;
+#endif
 };
 
 #define MCP251X_IS(_model) \
@@ -236,6 +251,10 @@ static inline int mcp251x_is_##_model(struct spi_device *spi) \
 
 MCP251X_IS(2510);
 
+#ifdef CONFIG_GPIOLIB
+static void mcp251x_gpio_restore(struct spi_device *spi);
+#endif
+
 static void mcp251x_clean(struct net_device *net)
 {
 	struct mcp251x_priv *priv = netdev_priv(net);
@@ -761,6 +780,9 @@ static void mcp251x_restart_work_handler(struct work_struct *ws)
 		if (priv->after_suspend & AFTER_SUSPEND_POWER) {
 			mcp251x_hw_reset(spi);
 			mcp251x_setup(net, spi);
+#ifdef CONFIG_GPIOLIB
+			mcp251x_gpio_restore(spi);
+#endif
 		} else {
 			mcp251x_hw_wake(spi);
 		}
@@ -918,6 +940,201 @@ static irqreturn_t mcp251x_can_ist(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+#ifdef CONFIG_GPIOLIB
+
+#define TX0RTS 0
+#define TX1RTS 1
+#define TX2RTS 2
+#define RX0BF 3
+#define RX1BF 4
+#define NGPIO 5
+
+static const char * const mcp251x_gpio_names[] = {
+	"TX0RTS",
+	"TX1RTS",
+	"TX2RTS",
+	"RX0BF",
+	"RX1BF"
+};
+
+static int mcp251x_gpio_request(struct gpio_chip *chip,
+				unsigned int offset)
+{
+	struct mcp251x_priv *priv = gpiochip_get_data(chip);
+	u8 bit = BFPCTRL_B0BFE << (offset - RX0BF);
+
+	if (offset >= NGPIO)
+		return -EINVAL;
+	if (offset <= TX2RTS) // nothing to be done for inputs
+		return 0;
+
+	mutex_lock(&priv->mcp_lock);
+	mcp251x_write_bits(priv->spi, BFPCTRL, bit, bit);
+	mutex_unlock(&priv->mcp_lock);
+
+	priv->reg_bfpctrl = priv->reg_bfpctrl | bit;
+
+	return 0;
+}
+
+static void mcp251x_gpio_free(struct gpio_chip *chip,
+			      unsigned int offset)
+{
+	struct mcp251x_priv *priv = gpiochip_get_data(chip);
+	u8 bit = BFPCTRL_B0BFE << (offset - RX0BF);
+
+	if (offset >= NGPIO)
+		return;
+	if (offset <= TX2RTS) // nothing to be done for inputs
+		return;
+
+	mutex_lock(&priv->mcp_lock);
+	mcp251x_write_bits(priv->spi, BFPCTRL, bit, 0);
+	mutex_unlock(&priv->mcp_lock);
+
+	priv->reg_bfpctrl = priv->reg_bfpctrl & ~bit;
+}
+
+static int mcp251x_gpio_get_direction(struct gpio_chip *chip,
+				      unsigned int offset)
+{
+	if (offset <= TX2RTS)
+		return GPIOF_DIR_IN;
+	else if (offset <= RX1BF)
+		return GPIOF_DIR_OUT;
+
+	return -EINVAL;
+}
+
+static int mcp251x_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct mcp251x_priv *priv = gpiochip_get_data(chip);
+	u8 reg, mask, val;
+
+	if (offset <= TX2RTS) {
+		reg = TXRTSCTRL;
+		mask = TXRTSCTRL_B0RTS << offset;
+	} else if (offset <= RX1BF) {
+		reg = BFPCTRL;
+		mask = BFPCTRL_B0BFS << (offset - RX0BF);
+	} else {
+		return -EINVAL;
+	}
+
+	mutex_lock(&priv->mcp_lock);
+	val = mcp251x_read_reg(priv->spi, reg);
+	mutex_unlock(&priv->mcp_lock);
+
+	if (val & mask)
+		return 1;
+	return 0;
+}
+
+static int mcp251x_gpio_get_multiple(struct gpio_chip *chip,
+				     unsigned long *mask, unsigned long *bits)
+{
+	struct mcp251x_priv *priv = gpiochip_get_data(chip);
+	unsigned long retbits = 0;
+
+	mutex_lock(&priv->mcp_lock);
+
+	if (mask[0] & GENMASK(TX2RTS, TX0RTS))
+		retbits |= mcp251x_read_reg(priv->spi, TXRTSCTRL)
+					>> __ffs(TXRTSCTRL_B0RTS);
+	if (mask[0] & GENMASK(RX1BF, RX0BF))
+		retbits |= (mcp251x_read_reg(priv->spi, BFPCTRL)
+					>> (__ffs(BFPCTRL_B0BFS) - RX0BF))
+					& GENMASK(RX1BF, RX0BF);
+
+	mutex_unlock(&priv->mcp_lock);
+
+	bits[0] = retbits;
+	return 0;
+}
+
+static void mcp251x_gpio_set(struct gpio_chip *chip, unsigned int offset,
+			     int value)
+{
+	struct mcp251x_priv *priv = gpiochip_get_data(chip);
+	u8 mask, content;
+
+	if (offset < RX0BF)
+		return;
+
+	mask = BFPCTRL_B0BFS << (offset - RX0BF);
+	content = value ? mask : 0;
+
+	mutex_lock(&priv->mcp_lock);
+	mcp251x_write_bits(priv->spi, BFPCTRL, mask, content);
+	mutex_unlock(&priv->mcp_lock);
+
+	priv->reg_bfpctrl = (priv->reg_bfpctrl & ~mask) | content;
+}
+
+static void mcp251x_gpio_set_multiple(struct gpio_chip *chip,
+				      unsigned long *mask, unsigned long *bits)
+{
+	struct mcp251x_priv *priv = gpiochip_get_data(chip);
+	u8 reg_mask = 0, value = 0;
+	int pin;
+
+	for (pin = RX0BF; pin <= RX1BF; pin++) {
+		if (test_bit(pin, mask)) {
+			u8 mask_val = BFPCTRL_B0BFS << (pin - RX0BF);
+
+			reg_mask |= mask_val;
+			if (test_bit(pin, bits))
+				value |= mask_val;
+		}
+	}
+
+	if (!reg_mask)
+		return;
+
+	mutex_lock(&priv->mcp_lock);
+	mcp251x_write_bits(priv->spi, BFPCTRL, reg_mask, value);
+	mutex_unlock(&priv->mcp_lock);
+
+	priv->reg_bfpctrl = (priv->reg_bfpctrl & ~reg_mask) | value;
+}
+
+static void mcp251x_gpio_restore(struct spi_device *spi)
+{
+	struct mcp251x_priv *priv = spi_get_drvdata(spi);
+
+	mcp251x_write_reg(spi, BFPCTRL, priv->reg_bfpctrl);
+}
+
+static int mcp251x_gpio_setup(struct mcp251x_priv *priv)
+{
+	struct gpio_chip *gpio = &priv->gpio;
+
+	/* gpiochip handles TX[0..2]RTS and RX[0..1]BF */
+	gpio->label                = priv->spi->modalias;
+	gpio->parent               = &priv->spi->dev;
+	gpio->owner                = THIS_MODULE;
+	gpio->request              = mcp251x_gpio_request;
+	gpio->free                 = mcp251x_gpio_free;
+	gpio->get_direction        = mcp251x_gpio_get_direction;
+	gpio->get                  = mcp251x_gpio_get;
+	gpio->get_multiple         = mcp251x_gpio_get_multiple;
+	gpio->set                  = mcp251x_gpio_set;
+	gpio->set_multiple         = mcp251x_gpio_set_multiple;
+	gpio->base                 = -1;
+	gpio->ngpio                = NGPIO;
+	gpio->names                = mcp251x_gpio_names;
+	gpio->can_sleep            = 1;
+
+	return gpiochip_add_data(gpio, priv);
+}
+
+static void mcp251x_gpio_remove(struct mcp251x_priv *priv)
+{
+	gpiochip_remove(&priv->gpio);
+}
+
+#endif // CONFIG_GPIOLIB
+
 static int mcp251x_open(struct net_device *net)
 {
 	struct mcp251x_priv *priv = netdev_priv(net);
@@ -1134,9 +1351,20 @@ static int mcp251x_can_probe(struct spi_device *spi)
 
 	devm_can_led_init(net);
 
+#ifdef CONFIG_GPIOLIB
+	ret = mcp251x_gpio_setup(priv);
+	if (ret)
+		goto error_gpio;
+#endif
+
 	netdev_info(net, "MCP%x successfully initialized.\n", priv->model);
 	return 0;
 
+#ifdef CONFIG_GPIOLIB
+error_gpio:
+	mcp251x_gpio_remove(priv);
+#endif
+
 error_probe:
 	mcp251x_power_enable(priv->power, 0);
 
@@ -1157,6 +1385,10 @@ static int mcp251x_can_remove(struct spi_device *spi)
 
 	unregister_candev(net);
 
+#ifdef CONFIG_GPIOLIB
+	mcp251x_gpio_remove(priv);
+#endif
+
 	mcp251x_power_enable(priv->power, 0);
 
 	clk_disable_unprepare(priv->clk);
-- 
2.7.2




[Index of Archives]     [Automotive Discussions]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [CAN Bus]

  Powered by Linux