can: mcp251x: add GPIO support
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@krause.de> Tested-by: Timo Schlüßler <schluessler@krause.de> Link: https://lore.kernel.org/r/20200915223527.1417033-28-mkl@pengutronix.de Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
parent
cfc24a0aa7
commit
2d52dabbef
@ -19,6 +19,7 @@
|
||||
* Copyright 2007
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/can/core.h>
|
||||
#include <linux/can/dev.h>
|
||||
#include <linux/can/led.h>
|
||||
@ -27,6 +28,8 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -52,6 +55,30 @@
|
||||
#define INSTRUCTION_RTS(n) (0x80 | ((n) & 0x07))
|
||||
|
||||
/* MPC251x registers */
|
||||
#define BFPCTRL 0x0c
|
||||
# define BFPCTRL_B0BFM BIT(0)
|
||||
# define BFPCTRL_B1BFM BIT(1)
|
||||
# define BFPCTRL_BFM(n) (BFPCTRL_B0BFM << (n))
|
||||
# define BFPCTRL_BFM_MASK GENMASK(1, 0)
|
||||
# define BFPCTRL_B0BFE BIT(2)
|
||||
# define BFPCTRL_B1BFE BIT(3)
|
||||
# define BFPCTRL_BFE(n) (BFPCTRL_B0BFE << (n))
|
||||
# define BFPCTRL_BFE_MASK GENMASK(3, 2)
|
||||
# define BFPCTRL_B0BFS BIT(4)
|
||||
# define BFPCTRL_B1BFS BIT(5)
|
||||
# define BFPCTRL_BFS(n) (BFPCTRL_B0BFS << (n))
|
||||
# define BFPCTRL_BFS_MASK GENMASK(5, 4)
|
||||
#define TXRTSCTRL 0x0d
|
||||
# define TXRTSCTRL_B0RTSM BIT(0)
|
||||
# define TXRTSCTRL_B1RTSM BIT(1)
|
||||
# define TXRTSCTRL_B2RTSM BIT(2)
|
||||
# define TXRTSCTRL_RTSM(n) (TXRTSCTRL_B0RTSM << (n))
|
||||
# define TXRTSCTRL_RTSM_MASK GENMASK(2, 0)
|
||||
# define TXRTSCTRL_B0RTS BIT(3)
|
||||
# define TXRTSCTRL_B1RTS BIT(4)
|
||||
# define TXRTSCTRL_B2RTS BIT(5)
|
||||
# define TXRTSCTRL_RTS(n) (TXRTSCTRL_B0RTS << (n))
|
||||
# define TXRTSCTRL_RTS_MASK GENMASK(5, 3)
|
||||
#define CANSTAT 0x0e
|
||||
#define CANCTRL 0x0f
|
||||
# define CANCTRL_REQOP_MASK 0xe0
|
||||
@ -225,6 +252,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) \
|
||||
@ -345,6 +376,213 @@ static void mcp251x_write_bits(struct spi_device *spi, u8 reg,
|
||||
mcp251x_spi_trans(spi, 4);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GPIOLIB
|
||||
enum {
|
||||
MCP251X_GPIO_TX0RTS = 0, /* inputs */
|
||||
MCP251X_GPIO_TX1RTS,
|
||||
MCP251X_GPIO_TX2RTS,
|
||||
MCP251X_GPIO_RX0BF, /* outputs */
|
||||
MCP251X_GPIO_RX1BF,
|
||||
};
|
||||
|
||||
#define MCP251X_GPIO_INPUT_MASK \
|
||||
GENMASK(MCP251X_GPIO_TX2RTS, MCP251X_GPIO_TX0RTS)
|
||||
#define MCP251X_GPIO_OUTPUT_MASK \
|
||||
GENMASK(MCP251X_GPIO_RX1BF, MCP251X_GPIO_RX0BF)
|
||||
|
||||
static const char * const mcp251x_gpio_names[] = {
|
||||
[MCP251X_GPIO_TX0RTS] = "TX0RTS", /* inputs */
|
||||
[MCP251X_GPIO_TX1RTS] = "TX1RTS",
|
||||
[MCP251X_GPIO_TX2RTS] = "TX2RTS",
|
||||
[MCP251X_GPIO_RX0BF] = "RX0BF", /* outputs */
|
||||
[MCP251X_GPIO_RX1BF] = "RX1BF",
|
||||
};
|
||||
|
||||
static inline bool mcp251x_gpio_is_input(unsigned int offset)
|
||||
{
|
||||
return offset <= MCP251X_GPIO_TX2RTS;
|
||||
}
|
||||
|
||||
static int mcp251x_gpio_request(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
struct mcp251x_priv *priv = gpiochip_get_data(chip);
|
||||
u8 val;
|
||||
|
||||
/* nothing to be done for inputs */
|
||||
if (mcp251x_gpio_is_input(offset))
|
||||
return 0;
|
||||
|
||||
val = BFPCTRL_BFE(offset - MCP251X_GPIO_RX0BF);
|
||||
|
||||
mutex_lock(&priv->mcp_lock);
|
||||
mcp251x_write_bits(priv->spi, BFPCTRL, val, val);
|
||||
mutex_unlock(&priv->mcp_lock);
|
||||
|
||||
priv->reg_bfpctrl |= val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mcp251x_gpio_free(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
struct mcp251x_priv *priv = gpiochip_get_data(chip);
|
||||
u8 val;
|
||||
|
||||
/* nothing to be done for inputs */
|
||||
if (mcp251x_gpio_is_input(offset))
|
||||
return;
|
||||
|
||||
val = BFPCTRL_BFE(offset - MCP251X_GPIO_RX0BF);
|
||||
|
||||
mutex_lock(&priv->mcp_lock);
|
||||
mcp251x_write_bits(priv->spi, BFPCTRL, val, 0);
|
||||
mutex_unlock(&priv->mcp_lock);
|
||||
|
||||
priv->reg_bfpctrl &= ~val;
|
||||
}
|
||||
|
||||
static int mcp251x_gpio_get_direction(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
if (mcp251x_gpio_is_input(offset))
|
||||
return GPIOF_DIR_IN;
|
||||
|
||||
return GPIOF_DIR_OUT;
|
||||
}
|
||||
|
||||
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 (mcp251x_gpio_is_input(offset)) {
|
||||
reg = TXRTSCTRL;
|
||||
mask = TXRTSCTRL_RTS(offset);
|
||||
} else {
|
||||
reg = BFPCTRL;
|
||||
mask = BFPCTRL_BFS(offset - MCP251X_GPIO_RX0BF);
|
||||
}
|
||||
|
||||
mutex_lock(&priv->mcp_lock);
|
||||
val = mcp251x_read_reg(priv->spi, reg);
|
||||
mutex_unlock(&priv->mcp_lock);
|
||||
|
||||
return !!(val & mask);
|
||||
}
|
||||
|
||||
static int mcp251x_gpio_get_multiple(struct gpio_chip *chip,
|
||||
unsigned long *maskp, unsigned long *bitsp)
|
||||
{
|
||||
struct mcp251x_priv *priv = gpiochip_get_data(chip);
|
||||
unsigned long bits = 0;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&priv->mcp_lock);
|
||||
if (maskp[0] & MCP251X_GPIO_INPUT_MASK) {
|
||||
val = mcp251x_read_reg(priv->spi, TXRTSCTRL);
|
||||
val = FIELD_GET(TXRTSCTRL_RTS_MASK, val);
|
||||
bits |= FIELD_PREP(MCP251X_GPIO_INPUT_MASK, val);
|
||||
}
|
||||
if (maskp[0] & MCP251X_GPIO_OUTPUT_MASK) {
|
||||
val = mcp251x_read_reg(priv->spi, BFPCTRL);
|
||||
val = FIELD_GET(BFPCTRL_BFS_MASK, val);
|
||||
bits |= FIELD_PREP(MCP251X_GPIO_OUTPUT_MASK, val);
|
||||
}
|
||||
mutex_unlock(&priv->mcp_lock);
|
||||
|
||||
bitsp[0] = bits;
|
||||
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, val;
|
||||
|
||||
mask = BFPCTRL_BFS(offset - MCP251X_GPIO_RX0BF);
|
||||
val = value ? mask : 0;
|
||||
|
||||
mutex_lock(&priv->mcp_lock);
|
||||
mcp251x_write_bits(priv->spi, BFPCTRL, mask, val);
|
||||
mutex_unlock(&priv->mcp_lock);
|
||||
|
||||
priv->reg_bfpctrl &= ~mask;
|
||||
priv->reg_bfpctrl |= val;
|
||||
}
|
||||
|
||||
static void
|
||||
mcp251x_gpio_set_multiple(struct gpio_chip *chip,
|
||||
unsigned long *maskp, unsigned long *bitsp)
|
||||
{
|
||||
struct mcp251x_priv *priv = gpiochip_get_data(chip);
|
||||
u8 mask, val;
|
||||
|
||||
mask = FIELD_GET(MCP251X_GPIO_OUTPUT_MASK, maskp[0]);
|
||||
mask = FIELD_PREP(BFPCTRL_BFS_MASK, mask);
|
||||
|
||||
val = FIELD_GET(MCP251X_GPIO_OUTPUT_MASK, bitsp[0]);
|
||||
val = FIELD_PREP(BFPCTRL_BFS_MASK, val);
|
||||
|
||||
if (!mask)
|
||||
return;
|
||||
|
||||
mutex_lock(&priv->mcp_lock);
|
||||
mcp251x_write_bits(priv->spi, BFPCTRL, mask, val);
|
||||
mutex_unlock(&priv->mcp_lock);
|
||||
|
||||
priv->reg_bfpctrl &= ~mask;
|
||||
priv->reg_bfpctrl |= val;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!device_property_present(&priv->spi->dev, "gpio-controller"))
|
||||
return 0;
|
||||
|
||||
/* 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 = ARRAY_SIZE(mcp251x_gpio_names);
|
||||
gpio->names = mcp251x_gpio_names;
|
||||
gpio->can_sleep = true;
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
gpio->of_node = priv->spi->dev.of_node;
|
||||
#endif
|
||||
|
||||
return devm_gpiochip_add_data(&priv->spi->dev, gpio, priv);
|
||||
}
|
||||
#else
|
||||
static inline void mcp251x_gpio_restore(struct spi_device *spi)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int mcp251x_gpio_setup(struct mcp251x_priv *priv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mcp251x_hw_tx_frame(struct spi_device *spi, u8 *buf,
|
||||
int len, int tx_buf_idx)
|
||||
{
|
||||
@ -761,6 +999,7 @@ 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);
|
||||
mcp251x_gpio_restore(spi);
|
||||
} else {
|
||||
mcp251x_hw_wake(spi);
|
||||
}
|
||||
@ -1136,6 +1375,10 @@ static int mcp251x_can_probe(struct spi_device *spi)
|
||||
|
||||
devm_can_led_init(net);
|
||||
|
||||
ret = mcp251x_gpio_setup(priv);
|
||||
if (ret)
|
||||
goto error_probe;
|
||||
|
||||
netdev_info(net, "MCP%x successfully initialized.\n", priv->model);
|
||||
return 0;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user