mirror of
https://github.com/torvalds/linux.git
synced 2026-05-31 18:43:33 +02:00
Immutable branch between MFD, GPIO, I2C and Watchdog due for the v6.13 merge window
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmcHkrwACgkQUa+KL4f8 d2EEeBAAoJio/mzf8zoc6dAygiT1xeWDIHQKIzKCT0RFzyfjTQnaajtNalYOS9Rx 16R3zVpElAwFSzafa/nARj//D68tdQOF47QFHE6necuhzY3HKHAFM6xxRmHt71e1 4SY6UU3Go741m/jZuXkicRAv52rOQteTy7XwSVREX3O1tld6ggwZYiiEdHHCQ4Tn caFQLchOJezpm4rfwcqMDhguODE+p1VVXybSrl/ellGt6G6ADi0E3gW8vaF7d6GO frNPNIRVFejOgAy0O6GT8T1m7qZnhACYRJSubKNMrB3UeKKLVL5YzWuPw0Mnl5GQ 64wK/qblFnHIhdifIJZYwn0xtoxjO7ooC5bK5aTJJhbg3e91ZuI2b3aqIQw/DWkk Fitl1XS2icRDsY4IkL1/MDD5HXQifFAEYLRWfn+rBSC3bILDck7tzSkWtUOrgtgJ z0wInRhfzkV4sFIHt0susqH+434CqPcjJ5jb+JpekFBUU5DTOO692x5sWBL7p4Qp qWm1fTfuX0kYPOHiChHw7NzSBrjdB/LOfNXNp7zZHpB0Vr3ds1XTHI0CBaxcGAYi msC/qEkZHyWCOiXy03gZEsloJGL7aS0jZ2g1fX6QxQV07bwiaTKj09wCxbckPkTb uCnFq2sZt4Bt5cYdaYopc4TF+bnS0mYUHgRm9CzVL/lZLN4F6Y0= =vhYn -----END PGP SIGNATURE----- Merge tag 'ib-mfd-gpio-i2c-watchdog-v6.13' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/lee/mfd into gpio/for-next Immutable branch between MFD, GPIO, I2C and Watchdog due for the v6.13 merge window
This commit is contained in:
commit
ea7f2dfd13
|
|
@ -5760,6 +5760,15 @@ F: fs/configfs/
|
||||||
F: include/linux/configfs.h
|
F: include/linux/configfs.h
|
||||||
F: samples/configfs/
|
F: samples/configfs/
|
||||||
|
|
||||||
|
CONGATEC BOARD CONTROLLER MFD DRIVER
|
||||||
|
M: Thomas Richard <thomas.richard@bootlin.com>
|
||||||
|
S: Maintained
|
||||||
|
F: drivers/gpio/gpio-cgbc.c
|
||||||
|
F: drivers/i2c/busses/i2c-cgbc.c
|
||||||
|
F: drivers/mfd/cgbc-core.c
|
||||||
|
F: drivers/watchdog/cgbc_wdt.c
|
||||||
|
F: include/linux/mfd/cgbc.h
|
||||||
|
|
||||||
CONSOLE SUBSYSTEM
|
CONSOLE SUBSYSTEM
|
||||||
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||||
S: Supported
|
S: Supported
|
||||||
|
|
|
||||||
|
|
@ -1286,6 +1286,16 @@ config GPIO_BD9571MWV
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called gpio-bd9571mwv.
|
will be called gpio-bd9571mwv.
|
||||||
|
|
||||||
|
config GPIO_CGBC
|
||||||
|
tristate "Congatec Board Controller GPIO support"
|
||||||
|
depends on MFD_CGBC
|
||||||
|
help
|
||||||
|
Select this option to enable GPIO support for the Congatec Board
|
||||||
|
Controller.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will be
|
||||||
|
called gpio-cgbc.
|
||||||
|
|
||||||
config GPIO_CROS_EC
|
config GPIO_CROS_EC
|
||||||
tristate "ChromeOS EC GPIO support"
|
tristate "ChromeOS EC GPIO support"
|
||||||
depends on CROS_EC
|
depends on CROS_EC
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o
|
||||||
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
|
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
|
||||||
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
|
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
|
||||||
obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o
|
obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o
|
||||||
|
obj-$(CONFIG_GPIO_CGBC) += gpio-cgbc.o
|
||||||
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
|
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
|
||||||
obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o
|
obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o
|
||||||
obj-$(CONFIG_GPIO_CROS_EC) += gpio-cros-ec.o
|
obj-$(CONFIG_GPIO_CROS_EC) += gpio-cros-ec.o
|
||||||
|
|
|
||||||
196
drivers/gpio/gpio-cgbc.c
Normal file
196
drivers/gpio/gpio-cgbc.c
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Congatec Board Controller GPIO driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bootlin
|
||||||
|
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/gpio/driver.h>
|
||||||
|
#include <linux/mfd/cgbc.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#define CGBC_GPIO_NGPIO 14
|
||||||
|
|
||||||
|
#define CGBC_GPIO_CMD_GET 0x64
|
||||||
|
#define CGBC_GPIO_CMD_SET 0x65
|
||||||
|
#define CGBC_GPIO_CMD_DIR_GET 0x66
|
||||||
|
#define CGBC_GPIO_CMD_DIR_SET 0x67
|
||||||
|
|
||||||
|
struct cgbc_gpio_data {
|
||||||
|
struct gpio_chip chip;
|
||||||
|
struct cgbc_device_data *cgbc;
|
||||||
|
struct mutex lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int cgbc_gpio_cmd(struct cgbc_device_data *cgbc,
|
||||||
|
u8 cmd0, u8 cmd1, u8 cmd2, u8 *value)
|
||||||
|
{
|
||||||
|
u8 cmd[3] = {cmd0, cmd1, cmd2};
|
||||||
|
|
||||||
|
return cgbc_command(cgbc, cmd, sizeof(cmd), value, 1, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||||
|
int ret;
|
||||||
|
u8 val;
|
||||||
|
|
||||||
|
scoped_guard(mutex, &gpio->lock)
|
||||||
|
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||||
|
|
||||||
|
offset %= 8;
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
else
|
||||||
|
return (int)(val & (u8)BIT(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __cgbc_gpio_set(struct gpio_chip *chip,
|
||||||
|
unsigned int offset, int value)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||||
|
u8 val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||||
|
if (ret)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
val |= BIT(offset % 8);
|
||||||
|
else
|
||||||
|
val &= ~(BIT(offset % 8));
|
||||||
|
|
||||||
|
cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_SET, (offset > 7) ? 1 : 0, val, &val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cgbc_gpio_set(struct gpio_chip *chip,
|
||||||
|
unsigned int offset, int value)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
|
||||||
|
scoped_guard(mutex, &gpio->lock)
|
||||||
|
__cgbc_gpio_set(chip, offset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_gpio_direction_set(struct gpio_chip *chip,
|
||||||
|
unsigned int offset, int direction)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||||
|
int ret;
|
||||||
|
u8 val;
|
||||||
|
|
||||||
|
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||||
|
if (ret)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
if (direction == GPIO_LINE_DIRECTION_IN)
|
||||||
|
val &= ~(BIT(offset % 8));
|
||||||
|
else
|
||||||
|
val |= BIT(offset % 8);
|
||||||
|
|
||||||
|
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_SET, (offset > 7) ? 1 : 0, val, &val);
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_gpio_direction_input(struct gpio_chip *chip,
|
||||||
|
unsigned int offset)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
|
||||||
|
guard(mutex)(&gpio->lock);
|
||||||
|
return cgbc_gpio_direction_set(chip, offset, GPIO_LINE_DIRECTION_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_gpio_direction_output(struct gpio_chip *chip,
|
||||||
|
unsigned int offset, int value)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
|
||||||
|
guard(mutex)(&gpio->lock);
|
||||||
|
|
||||||
|
__cgbc_gpio_set(chip, offset, value);
|
||||||
|
return cgbc_gpio_direction_set(chip, offset, GPIO_LINE_DIRECTION_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
|
||||||
|
{
|
||||||
|
struct cgbc_gpio_data *gpio = gpiochip_get_data(chip);
|
||||||
|
struct cgbc_device_data *cgbc = gpio->cgbc;
|
||||||
|
int ret;
|
||||||
|
u8 val;
|
||||||
|
|
||||||
|
scoped_guard(mutex, &gpio->lock)
|
||||||
|
ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_GET, (offset > 7) ? 1 : 0, 0, &val);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (val & BIT(offset % 8))
|
||||||
|
return GPIO_LINE_DIRECTION_OUT;
|
||||||
|
else
|
||||||
|
return GPIO_LINE_DIRECTION_IN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_gpio_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct cgbc_device_data *cgbc = dev_get_drvdata(dev->parent);
|
||||||
|
struct cgbc_gpio_data *gpio;
|
||||||
|
struct gpio_chip *chip;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
|
||||||
|
if (!gpio)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
gpio->cgbc = cgbc;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, gpio);
|
||||||
|
|
||||||
|
chip = &gpio->chip;
|
||||||
|
chip->label = dev_name(&pdev->dev);
|
||||||
|
chip->owner = THIS_MODULE;
|
||||||
|
chip->parent = dev;
|
||||||
|
chip->base = -1;
|
||||||
|
chip->direction_input = cgbc_gpio_direction_input;
|
||||||
|
chip->direction_output = cgbc_gpio_direction_output;
|
||||||
|
chip->get_direction = cgbc_gpio_get_direction;
|
||||||
|
chip->get = cgbc_gpio_get;
|
||||||
|
chip->set = cgbc_gpio_set;
|
||||||
|
chip->ngpio = CGBC_GPIO_NGPIO;
|
||||||
|
|
||||||
|
ret = devm_mutex_init(dev, &gpio->lock);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = devm_gpiochip_add_data(dev, chip, gpio);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Could not register GPIO chip\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver cgbc_gpio_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "cgbc-gpio",
|
||||||
|
},
|
||||||
|
.probe = cgbc_gpio_probe,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(cgbc_gpio_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Congatec Board Controller GPIO Driver");
|
||||||
|
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:cgbc-gpio");
|
||||||
|
|
@ -535,6 +535,16 @@ config I2C_CBUS_GPIO
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called i2c-cbus-gpio.
|
will be called i2c-cbus-gpio.
|
||||||
|
|
||||||
|
config I2C_CGBC
|
||||||
|
tristate "Congatec I2C Controller"
|
||||||
|
depends on MFD_CGBC
|
||||||
|
help
|
||||||
|
This driver supports the 2 I2C interfaces on the Congatec Board
|
||||||
|
Controller.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will
|
||||||
|
be called i2c-cgbc.ko.
|
||||||
|
|
||||||
config I2C_CPM
|
config I2C_CPM
|
||||||
tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
|
tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
|
||||||
depends on CPM1 || CPM2
|
depends on CPM1 || CPM2
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ obj-$(CONFIG_I2C_BCM2835) += i2c-bcm2835.o
|
||||||
obj-$(CONFIG_I2C_BCM_IPROC) += i2c-bcm-iproc.o
|
obj-$(CONFIG_I2C_BCM_IPROC) += i2c-bcm-iproc.o
|
||||||
obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
|
obj-$(CONFIG_I2C_CADENCE) += i2c-cadence.o
|
||||||
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
|
obj-$(CONFIG_I2C_CBUS_GPIO) += i2c-cbus-gpio.o
|
||||||
|
obj-$(CONFIG_I2C_CGBC) += i2c-cgbc.o
|
||||||
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
|
obj-$(CONFIG_I2C_CPM) += i2c-cpm.o
|
||||||
obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
|
obj-$(CONFIG_I2C_DAVINCI) += i2c-davinci.o
|
||||||
obj-$(CONFIG_I2C_DESIGNWARE_CORE) += i2c-designware-core.o
|
obj-$(CONFIG_I2C_DESIGNWARE_CORE) += i2c-designware-core.o
|
||||||
|
|
|
||||||
406
drivers/i2c/busses/i2c-cgbc.c
Normal file
406
drivers/i2c/busses/i2c-cgbc.c
Normal file
|
|
@ -0,0 +1,406 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Congatec Board Controller I2C busses driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bootlin
|
||||||
|
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/iopoll.h>
|
||||||
|
#include <linux/mfd/cgbc.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#define CGBC_I2C_PRIMARY_BUS_ID 0
|
||||||
|
#define CGBC_I2C_PM_BUS_ID 4
|
||||||
|
|
||||||
|
#define CGBC_I2C_CMD_START 0x40
|
||||||
|
#define CGBC_I2C_CMD_STAT 0x48
|
||||||
|
#define CGBC_I2C_CMD_DATA 0x50
|
||||||
|
#define CGBC_I2C_CMD_SPEED 0x58
|
||||||
|
|
||||||
|
#define CGBC_I2C_STAT_IDL 0x00
|
||||||
|
#define CGBC_I2C_STAT_DAT 0x01
|
||||||
|
#define CGBC_I2C_STAT_BUSY 0x02
|
||||||
|
|
||||||
|
#define CGBC_I2C_START 0x80
|
||||||
|
#define CGBC_I2C_STOP 0x40
|
||||||
|
|
||||||
|
#define CGBC_I2C_LAST_ACK 0x80 /* send ACK on last read byte */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reference code defines 1kHz as min freq and 6.1MHz as max freq.
|
||||||
|
* But in practice, the board controller limits the frequency to 1MHz, and the
|
||||||
|
* 1kHz is not functional (minimal working freq is 50kHz).
|
||||||
|
* So use these values as limits.
|
||||||
|
*/
|
||||||
|
#define CGBC_I2C_FREQ_MIN_HZ 50000 /* 50 kHz */
|
||||||
|
#define CGBC_I2C_FREQ_MAX_HZ 1000000 /* 1 MHz */
|
||||||
|
|
||||||
|
#define CGBC_I2C_FREQ_UNIT_1KHZ 0x40
|
||||||
|
#define CGBC_I2C_FREQ_UNIT_10KHZ 0x80
|
||||||
|
#define CGBC_I2C_FREQ_UNIT_100KHZ 0xC0
|
||||||
|
|
||||||
|
#define CGBC_I2C_FREQ_UNIT_MASK 0xC0
|
||||||
|
#define CGBC_I2C_FREQ_VALUE_MASK 0x3F
|
||||||
|
|
||||||
|
#define CGBC_I2C_READ_MAX_LEN 31
|
||||||
|
#define CGBC_I2C_WRITE_MAX_LEN 32
|
||||||
|
|
||||||
|
#define CGBC_I2C_CMD_HEADER_SIZE 4
|
||||||
|
#define CGBC_I2C_CMD_SIZE (CGBC_I2C_CMD_HEADER_SIZE + CGBC_I2C_WRITE_MAX_LEN)
|
||||||
|
|
||||||
|
enum cgbc_i2c_state {
|
||||||
|
CGBC_I2C_STATE_DONE = 0,
|
||||||
|
CGBC_I2C_STATE_INIT,
|
||||||
|
CGBC_I2C_STATE_START,
|
||||||
|
CGBC_I2C_STATE_READ,
|
||||||
|
CGBC_I2C_STATE_WRITE,
|
||||||
|
CGBC_I2C_STATE_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct i2c_algo_cgbc_data {
|
||||||
|
u8 bus_id;
|
||||||
|
unsigned long read_maxtime_us;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cgbc_i2c_data {
|
||||||
|
struct device *dev;
|
||||||
|
struct cgbc_device_data *cgbc;
|
||||||
|
struct i2c_adapter adap;
|
||||||
|
struct i2c_msg *msg;
|
||||||
|
int nmsgs;
|
||||||
|
int pos;
|
||||||
|
enum cgbc_i2c_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cgbc_i2c_transfer {
|
||||||
|
u8 bus_id;
|
||||||
|
bool start;
|
||||||
|
bool stop;
|
||||||
|
bool last_ack;
|
||||||
|
u8 read;
|
||||||
|
u8 write;
|
||||||
|
u8 addr;
|
||||||
|
u8 data[CGBC_I2C_WRITE_MAX_LEN];
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 cgbc_i2c_freq_to_reg(unsigned int bus_frequency)
|
||||||
|
{
|
||||||
|
u8 reg;
|
||||||
|
|
||||||
|
if (bus_frequency <= 10000)
|
||||||
|
reg = CGBC_I2C_FREQ_UNIT_1KHZ | (bus_frequency / 1000);
|
||||||
|
else if (bus_frequency <= 100000)
|
||||||
|
reg = CGBC_I2C_FREQ_UNIT_10KHZ | (bus_frequency / 10000);
|
||||||
|
else
|
||||||
|
reg = CGBC_I2C_FREQ_UNIT_100KHZ | (bus_frequency / 100000);
|
||||||
|
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int cgbc_i2c_reg_to_freq(u8 reg)
|
||||||
|
{
|
||||||
|
unsigned int freq = reg & CGBC_I2C_FREQ_VALUE_MASK;
|
||||||
|
u8 unit = reg & CGBC_I2C_FREQ_UNIT_MASK;
|
||||||
|
|
||||||
|
if (unit == CGBC_I2C_FREQ_UNIT_100KHZ)
|
||||||
|
return freq * 100000;
|
||||||
|
else if (unit == CGBC_I2C_FREQ_UNIT_10KHZ)
|
||||||
|
return freq * 10000;
|
||||||
|
else
|
||||||
|
return freq * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_i2c_get_status(struct i2c_adapter *adap)
|
||||||
|
{
|
||||||
|
struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
|
||||||
|
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||||
|
struct cgbc_device_data *cgbc = i2c->cgbc;
|
||||||
|
u8 cmd = CGBC_I2C_CMD_STAT | algo_data->bus_id;
|
||||||
|
u8 status;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = cgbc_command(cgbc, &cmd, sizeof(cmd), NULL, 0, &status);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_i2c_set_frequency(struct i2c_adapter *adap,
|
||||||
|
unsigned int bus_frequency)
|
||||||
|
{
|
||||||
|
struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
|
||||||
|
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||||
|
struct cgbc_device_data *cgbc = i2c->cgbc;
|
||||||
|
u8 cmd[2], data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (bus_frequency > CGBC_I2C_FREQ_MAX_HZ ||
|
||||||
|
bus_frequency < CGBC_I2C_FREQ_MIN_HZ) {
|
||||||
|
dev_info(i2c->dev, "invalid frequency %u, using default\n", bus_frequency);
|
||||||
|
bus_frequency = I2C_MAX_STANDARD_MODE_FREQ;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd[0] = CGBC_I2C_CMD_SPEED | algo_data->bus_id;
|
||||||
|
cmd[1] = cgbc_i2c_freq_to_reg(bus_frequency);
|
||||||
|
|
||||||
|
ret = cgbc_command(cgbc, &cmd, sizeof(cmd), &data, 1, NULL);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(i2c->dev, ret,
|
||||||
|
"Failed to initialize I2C bus %s",
|
||||||
|
adap->name);
|
||||||
|
|
||||||
|
cmd[1] = 0x00;
|
||||||
|
|
||||||
|
ret = cgbc_command(cgbc, &cmd, sizeof(cmd), &data, 1, NULL);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(i2c->dev, ret,
|
||||||
|
"Failed to get I2C bus frequency");
|
||||||
|
|
||||||
|
bus_frequency = cgbc_i2c_reg_to_freq(data);
|
||||||
|
|
||||||
|
dev_dbg(i2c->dev, "%s is running at %d Hz\n", adap->name, bus_frequency);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The read_maxtime_us variable represents the maximum time to wait
|
||||||
|
* for data during a read operation. The maximum amount of data that
|
||||||
|
* can be read by a command is CGBC_I2C_READ_MAX_LEN.
|
||||||
|
* Therefore, calculate the max time to properly size the timeout.
|
||||||
|
*/
|
||||||
|
algo_data->read_maxtime_us = (BITS_PER_BYTE + 1) * CGBC_I2C_READ_MAX_LEN
|
||||||
|
* USEC_PER_SEC / bus_frequency;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int cgbc_i2c_xfer_to_cmd(struct cgbc_i2c_transfer xfer, u8 *cmd)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
cmd[i++] = CGBC_I2C_CMD_START | xfer.bus_id;
|
||||||
|
|
||||||
|
cmd[i] = (xfer.start) ? CGBC_I2C_START : 0x00;
|
||||||
|
if (xfer.stop)
|
||||||
|
cmd[i] |= CGBC_I2C_STOP;
|
||||||
|
cmd[i++] |= (xfer.start) ? xfer.write + 1 : xfer.write;
|
||||||
|
|
||||||
|
cmd[i++] = (xfer.last_ack) ? (xfer.read | CGBC_I2C_LAST_ACK) : xfer.read;
|
||||||
|
|
||||||
|
if (xfer.start)
|
||||||
|
cmd[i++] = xfer.addr;
|
||||||
|
|
||||||
|
if (xfer.write > 0)
|
||||||
|
memcpy(&cmd[i], &xfer.data, xfer.write);
|
||||||
|
|
||||||
|
return i + xfer.write;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_i2c_xfer_msg(struct i2c_adapter *adap)
|
||||||
|
{
|
||||||
|
struct i2c_algo_cgbc_data *algo_data = adap->algo_data;
|
||||||
|
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||||
|
struct cgbc_device_data *cgbc = i2c->cgbc;
|
||||||
|
struct i2c_msg *msg = i2c->msg;
|
||||||
|
u8 cmd[CGBC_I2C_CMD_SIZE];
|
||||||
|
int ret, max_len, len, i;
|
||||||
|
unsigned int cmd_len;
|
||||||
|
u8 cmd_data;
|
||||||
|
|
||||||
|
struct cgbc_i2c_transfer xfer = {
|
||||||
|
.bus_id = algo_data->bus_id,
|
||||||
|
.addr = i2c_8bit_addr_from_msg(msg),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (i2c->state == CGBC_I2C_STATE_DONE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ret = cgbc_i2c_get_status(adap);
|
||||||
|
|
||||||
|
if (ret == CGBC_I2C_STAT_BUSY)
|
||||||
|
return -EBUSY;
|
||||||
|
else if (ret < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (i2c->state == CGBC_I2C_STATE_INIT ||
|
||||||
|
(i2c->state == CGBC_I2C_STATE_WRITE && msg->flags & I2C_M_RD))
|
||||||
|
xfer.start = true;
|
||||||
|
|
||||||
|
i2c->state = (msg->flags & I2C_M_RD) ? CGBC_I2C_STATE_READ : CGBC_I2C_STATE_WRITE;
|
||||||
|
|
||||||
|
max_len = (i2c->state == CGBC_I2C_STATE_READ) ?
|
||||||
|
CGBC_I2C_READ_MAX_LEN : CGBC_I2C_WRITE_MAX_LEN;
|
||||||
|
|
||||||
|
if (msg->len - i2c->pos > max_len) {
|
||||||
|
len = max_len;
|
||||||
|
} else {
|
||||||
|
len = msg->len - i2c->pos;
|
||||||
|
|
||||||
|
if (i2c->nmsgs == 1)
|
||||||
|
xfer.stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2c->state == CGBC_I2C_STATE_WRITE) {
|
||||||
|
xfer.write = len;
|
||||||
|
xfer.read = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++)
|
||||||
|
xfer.data[i] = msg->buf[i2c->pos + i];
|
||||||
|
|
||||||
|
cmd_len = cgbc_i2c_xfer_to_cmd(xfer, &cmd[0]);
|
||||||
|
|
||||||
|
ret = cgbc_command(cgbc, &cmd, cmd_len, NULL, 0, NULL);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
} else if (i2c->state == CGBC_I2C_STATE_READ) {
|
||||||
|
xfer.write = 0;
|
||||||
|
xfer.read = len;
|
||||||
|
|
||||||
|
if (i2c->nmsgs > 1 || msg->len - i2c->pos > max_len)
|
||||||
|
xfer.read |= CGBC_I2C_LAST_ACK;
|
||||||
|
|
||||||
|
cmd_len = cgbc_i2c_xfer_to_cmd(xfer, &cmd[0]);
|
||||||
|
ret = cgbc_command(cgbc, &cmd, cmd_len, NULL, 0, NULL);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ret = read_poll_timeout(cgbc_i2c_get_status, ret,
|
||||||
|
ret != CGBC_I2C_STAT_BUSY, 0,
|
||||||
|
2 * algo_data->read_maxtime_us, false, adap);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
cmd_data = CGBC_I2C_CMD_DATA | algo_data->bus_id;
|
||||||
|
ret = cgbc_command(cgbc, &cmd_data, sizeof(cmd_data),
|
||||||
|
msg->buf + i2c->pos, len, NULL);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == (msg->len - i2c->pos)) {
|
||||||
|
i2c->msg++;
|
||||||
|
i2c->nmsgs--;
|
||||||
|
i2c->pos = 0;
|
||||||
|
} else {
|
||||||
|
i2c->pos += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2c->nmsgs == 0)
|
||||||
|
i2c->state = CGBC_I2C_STATE_DONE;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
i2c->state = CGBC_I2C_STATE_ERROR;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
||||||
|
int num)
|
||||||
|
{
|
||||||
|
struct cgbc_i2c_data *i2c = i2c_get_adapdata(adap);
|
||||||
|
unsigned long timeout = jiffies + HZ;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
i2c->state = CGBC_I2C_STATE_INIT;
|
||||||
|
i2c->msg = msgs;
|
||||||
|
i2c->nmsgs = num;
|
||||||
|
i2c->pos = 0;
|
||||||
|
|
||||||
|
while (time_before(jiffies, timeout)) {
|
||||||
|
ret = cgbc_i2c_xfer_msg(adap);
|
||||||
|
if (i2c->state == CGBC_I2C_STATE_DONE)
|
||||||
|
return num;
|
||||||
|
|
||||||
|
if (i2c->state == CGBC_I2C_STATE_ERROR)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
|
timeout = jiffies + HZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->state = CGBC_I2C_STATE_ERROR;
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 cgbc_i2c_func(struct i2c_adapter *adap)
|
||||||
|
{
|
||||||
|
return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~(I2C_FUNC_SMBUS_QUICK));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct i2c_algorithm cgbc_i2c_algorithm = {
|
||||||
|
.master_xfer = cgbc_i2c_xfer,
|
||||||
|
.functionality = cgbc_i2c_func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct i2c_algo_cgbc_data cgbc_i2c_algo_data[] = {
|
||||||
|
{ .bus_id = CGBC_I2C_PRIMARY_BUS_ID },
|
||||||
|
{ .bus_id = CGBC_I2C_PM_BUS_ID },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct i2c_adapter cgbc_i2c_adapter[] = {
|
||||||
|
{
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "Congatec General Purpose I2C adapter",
|
||||||
|
.class = I2C_CLASS_DEPRECATED,
|
||||||
|
.algo = &cgbc_i2c_algorithm,
|
||||||
|
.algo_data = &cgbc_i2c_algo_data[0],
|
||||||
|
.nr = -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "Congatec Power Management I2C adapter",
|
||||||
|
.class = I2C_CLASS_DEPRECATED,
|
||||||
|
.algo = &cgbc_i2c_algorithm,
|
||||||
|
.algo_data = &cgbc_i2c_algo_data[1],
|
||||||
|
.nr = -1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int cgbc_i2c_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
struct cgbc_i2c_data *i2c;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
|
||||||
|
if (!i2c)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
i2c->cgbc = cgbc;
|
||||||
|
i2c->dev = &pdev->dev;
|
||||||
|
i2c->adap = cgbc_i2c_adapter[pdev->id];
|
||||||
|
i2c->adap.dev.parent = i2c->dev;
|
||||||
|
i2c_set_adapdata(&i2c->adap, i2c);
|
||||||
|
platform_set_drvdata(pdev, i2c);
|
||||||
|
|
||||||
|
ret = cgbc_i2c_set_frequency(&i2c->adap, I2C_MAX_STANDARD_MODE_FREQ);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return i2c_add_numbered_adapter(&i2c->adap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cgbc_i2c_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct cgbc_i2c_data *i2c = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
i2c_del_adapter(&i2c->adap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver cgbc_i2c_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "cgbc-i2c",
|
||||||
|
},
|
||||||
|
.probe = cgbc_i2c_probe,
|
||||||
|
.remove_new = cgbc_i2c_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(cgbc_i2c_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Congatec Board Controller I2C Driver");
|
||||||
|
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:cgbc_i2c");
|
||||||
|
|
@ -236,6 +236,18 @@ config MFD_AXP20X_RSB
|
||||||
components like regulators or the PEK (Power Enable Key) under the
|
components like regulators or the PEK (Power Enable Key) under the
|
||||||
corresponding menus.
|
corresponding menus.
|
||||||
|
|
||||||
|
config MFD_CGBC
|
||||||
|
tristate "Congatec Board Controller"
|
||||||
|
select MFD_CORE
|
||||||
|
depends on X86
|
||||||
|
help
|
||||||
|
This is the core driver of the Board Controller found on some Congatec
|
||||||
|
SMARC modules. The Board Controller provides functions like watchdog,
|
||||||
|
I2C busses, and GPIO controller.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here: the module will be
|
||||||
|
called cgbc-core.
|
||||||
|
|
||||||
config MFD_CROS_EC_DEV
|
config MFD_CROS_EC_DEV
|
||||||
tristate "ChromeOS Embedded Controller multifunction device"
|
tristate "ChromeOS Embedded Controller multifunction device"
|
||||||
select MFD_CORE
|
select MFD_CORE
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ obj-$(CONFIG_MFD_SM501) += sm501.o
|
||||||
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
|
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
|
||||||
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
|
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
|
||||||
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
|
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
|
||||||
|
obj-$(CONFIG_MFD_CGBC) += cgbc-core.o
|
||||||
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
|
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
|
||||||
obj-$(CONFIG_MFD_CS42L43) += cs42l43.o
|
obj-$(CONFIG_MFD_CS42L43) += cs42l43.o
|
||||||
obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o
|
obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o
|
||||||
|
|
|
||||||
411
drivers/mfd/cgbc-core.c
Normal file
411
drivers/mfd/cgbc-core.c
Normal file
|
|
@ -0,0 +1,411 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Congatec Board Controller core driver.
|
||||||
|
*
|
||||||
|
* The x86 Congatec modules have an embedded micro controller named Board
|
||||||
|
* Controller. This Board Controller has a Watchdog timer, some GPIOs, and two
|
||||||
|
* I2C busses.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bootlin
|
||||||
|
*
|
||||||
|
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/dmi.h>
|
||||||
|
#include <linux/iopoll.h>
|
||||||
|
#include <linux/mfd/cgbc.h>
|
||||||
|
#include <linux/mfd/core.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/sysfs.h>
|
||||||
|
|
||||||
|
#define CGBC_IO_SESSION_BASE 0x0E20
|
||||||
|
#define CGBC_IO_SESSION_END 0x0E30
|
||||||
|
#define CGBC_IO_CMD_BASE 0x0E00
|
||||||
|
#define CGBC_IO_CMD_END 0x0E10
|
||||||
|
|
||||||
|
#define CGBC_MASK_STATUS (BIT(6) | BIT(7))
|
||||||
|
#define CGBC_MASK_DATA_COUNT 0x1F
|
||||||
|
#define CGBC_MASK_ERROR_CODE 0x1F
|
||||||
|
|
||||||
|
#define CGBC_STATUS_DATA_READY 0x00
|
||||||
|
#define CGBC_STATUS_CMD_READY BIT(6)
|
||||||
|
#define CGBC_STATUS_ERROR (BIT(6) | BIT(7))
|
||||||
|
|
||||||
|
#define CGBC_SESSION_CMD 0x00
|
||||||
|
#define CGBC_SESSION_CMD_IDLE 0x00
|
||||||
|
#define CGBC_SESSION_CMD_REQUEST 0x01
|
||||||
|
#define CGBC_SESSION_DATA 0x01
|
||||||
|
#define CGBC_SESSION_STATUS 0x02
|
||||||
|
#define CGBC_SESSION_STATUS_FREE 0x03
|
||||||
|
#define CGBC_SESSION_ACCESS 0x04
|
||||||
|
#define CGBC_SESSION_ACCESS_GAINED 0x00
|
||||||
|
|
||||||
|
#define CGBC_SESSION_VALID_MIN 0x02
|
||||||
|
#define CGBC_SESSION_VALID_MAX 0xFE
|
||||||
|
|
||||||
|
#define CGBC_CMD_STROBE 0x00
|
||||||
|
#define CGBC_CMD_INDEX 0x02
|
||||||
|
#define CGBC_CMD_INDEX_CBM_MAN8 0x00
|
||||||
|
#define CGBC_CMD_INDEX_CBM_AUTO32 0x03
|
||||||
|
#define CGBC_CMD_DATA 0x04
|
||||||
|
#define CGBC_CMD_ACCESS 0x0C
|
||||||
|
|
||||||
|
#define CGBC_CMD_GET_FW_REV 0x21
|
||||||
|
|
||||||
|
static struct platform_device *cgbc_pdev;
|
||||||
|
|
||||||
|
/* Wait the Board Controller is ready to receive some session commands */
|
||||||
|
static int cgbc_wait_device(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
u16 status;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = readx_poll_timeout(ioread16, cgbc->io_session + CGBC_SESSION_STATUS, status,
|
||||||
|
status == CGBC_SESSION_STATUS_FREE, 0, 500000);
|
||||||
|
|
||||||
|
if (ret || ioread32(cgbc->io_session + CGBC_SESSION_ACCESS))
|
||||||
|
ret = -ENODEV;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_session_command(struct cgbc_device_data *cgbc, u8 cmd)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u8 val;
|
||||||
|
|
||||||
|
ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
|
||||||
|
val == CGBC_SESSION_CMD_IDLE, 0, 100000);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
iowrite8(cmd, cgbc->io_session + CGBC_SESSION_CMD);
|
||||||
|
|
||||||
|
ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
|
||||||
|
val == CGBC_SESSION_CMD_IDLE, 0, 100000);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = (int)ioread8(cgbc->io_session + CGBC_SESSION_DATA);
|
||||||
|
|
||||||
|
iowrite8(CGBC_SESSION_STATUS_FREE, cgbc->io_session + CGBC_SESSION_STATUS);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_session_request(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
unsigned int ret;
|
||||||
|
|
||||||
|
ret = cgbc_wait_device(cgbc);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(cgbc->dev, ret, "device not found or not ready\n");
|
||||||
|
|
||||||
|
cgbc->session = cgbc_session_command(cgbc, CGBC_SESSION_CMD_REQUEST);
|
||||||
|
|
||||||
|
/* The Board Controller sent us a wrong session handle, we cannot communicate with it */
|
||||||
|
if (cgbc->session < CGBC_SESSION_VALID_MIN || cgbc->session > CGBC_SESSION_VALID_MAX)
|
||||||
|
return dev_err_probe(cgbc->dev, -ECONNREFUSED,
|
||||||
|
"failed to get a valid session handle\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cgbc_session_release(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
if (cgbc_session_command(cgbc, cgbc->session) != cgbc->session)
|
||||||
|
dev_warn(cgbc->dev, "failed to release session\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cgbc_command_lock(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
|
||||||
|
|
||||||
|
return ioread8(cgbc->io_cmd + CGBC_CMD_ACCESS) == cgbc->session;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cgbc_command_unlock(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size, void *data,
|
||||||
|
unsigned int data_size, u8 *status)
|
||||||
|
{
|
||||||
|
u8 checksum = 0, data_checksum = 0, istatus = 0, val;
|
||||||
|
u8 *_data = (u8 *)data;
|
||||||
|
u8 *_cmd = (u8 *)cmd;
|
||||||
|
int mode_change = -1;
|
||||||
|
bool lock;
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
|
mutex_lock(&cgbc->lock);
|
||||||
|
|
||||||
|
/* Request access */
|
||||||
|
ret = readx_poll_timeout(cgbc_command_lock, cgbc, lock, lock, 0, 100000);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* Wait board controller is ready */
|
||||||
|
ret = readx_poll_timeout(ioread8, cgbc->io_cmd + CGBC_CMD_STROBE, val,
|
||||||
|
val == CGBC_CMD_STROBE, 0, 100000);
|
||||||
|
if (ret)
|
||||||
|
goto release;
|
||||||
|
|
||||||
|
/* Write command packet */
|
||||||
|
if (cmd_size <= 2) {
|
||||||
|
iowrite8(CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||||
|
} else {
|
||||||
|
iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||||
|
if ((cmd_size % 4) != 0x03)
|
||||||
|
mode_change = (cmd_size & 0xFFFC) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < cmd_size; i++) {
|
||||||
|
iowrite8(_cmd[i], cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
|
||||||
|
checksum ^= _cmd[i];
|
||||||
|
if (mode_change == i)
|
||||||
|
iowrite8((i + 1) | CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append checksum byte */
|
||||||
|
iowrite8(checksum, cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
|
||||||
|
|
||||||
|
/* Perform command strobe */
|
||||||
|
iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_STROBE);
|
||||||
|
|
||||||
|
/* Rewind cmd buffer index */
|
||||||
|
iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
|
||||||
|
|
||||||
|
/* Wait command completion */
|
||||||
|
ret = read_poll_timeout(ioread8, val, val == CGBC_CMD_STROBE, 0, 100000, false,
|
||||||
|
cgbc->io_cmd + CGBC_CMD_STROBE);
|
||||||
|
if (ret)
|
||||||
|
goto release;
|
||||||
|
|
||||||
|
istatus = ioread8(cgbc->io_cmd + CGBC_CMD_DATA);
|
||||||
|
checksum = istatus;
|
||||||
|
|
||||||
|
/* Check command status */
|
||||||
|
switch (istatus & CGBC_MASK_STATUS) {
|
||||||
|
case CGBC_STATUS_DATA_READY:
|
||||||
|
if (istatus > data_size)
|
||||||
|
istatus = data_size;
|
||||||
|
for (i = 0; i < istatus; i++) {
|
||||||
|
_data[i] = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
|
||||||
|
checksum ^= _data[i];
|
||||||
|
}
|
||||||
|
data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
|
||||||
|
istatus &= CGBC_MASK_DATA_COUNT;
|
||||||
|
break;
|
||||||
|
case CGBC_STATUS_ERROR:
|
||||||
|
case CGBC_STATUS_CMD_READY:
|
||||||
|
data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
|
||||||
|
if ((istatus & CGBC_MASK_STATUS) == CGBC_STATUS_ERROR)
|
||||||
|
ret = -EIO;
|
||||||
|
istatus = istatus & CGBC_MASK_ERROR_CODE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
|
||||||
|
istatus &= CGBC_MASK_ERROR_CODE;
|
||||||
|
ret = -EIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checksum verification */
|
||||||
|
if (ret == 0 && data_checksum != checksum)
|
||||||
|
ret = -EIO;
|
||||||
|
|
||||||
|
release:
|
||||||
|
cgbc_command_unlock(cgbc);
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&cgbc->lock);
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
*status = istatus;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(cgbc_command);
|
||||||
|
|
||||||
|
static struct mfd_cell cgbc_devs[] = {
|
||||||
|
{ .name = "cgbc-wdt" },
|
||||||
|
{ .name = "cgbc-gpio" },
|
||||||
|
{ .name = "cgbc-i2c", .id = 1 },
|
||||||
|
{ .name = "cgbc-i2c", .id = 2 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int cgbc_map(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
struct device *dev = cgbc->dev;
|
||||||
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
struct resource *ioport;
|
||||||
|
|
||||||
|
ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||||
|
if (!ioport)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
cgbc->io_session = devm_ioport_map(dev, ioport->start, resource_size(ioport));
|
||||||
|
if (!cgbc->io_session)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ioport = platform_get_resource(pdev, IORESOURCE_IO, 1);
|
||||||
|
if (!ioport)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
cgbc->io_cmd = devm_ioport_map(dev, ioport->start, resource_size(ioport));
|
||||||
|
if (!cgbc->io_cmd)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct resource cgbc_resources[] = {
|
||||||
|
{
|
||||||
|
.start = CGBC_IO_SESSION_BASE,
|
||||||
|
.end = CGBC_IO_SESSION_END,
|
||||||
|
.flags = IORESOURCE_IO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.start = CGBC_IO_CMD_BASE,
|
||||||
|
.end = CGBC_IO_CMD_END,
|
||||||
|
.flags = IORESOURCE_IO,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t cgbc_version_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct cgbc_device_data *cgbc = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "CGBCP%c%c%c\n", cgbc->version.feature, cgbc->version.major,
|
||||||
|
cgbc->version.minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR_RO(cgbc_version);
|
||||||
|
|
||||||
|
static struct attribute *cgbc_attrs[] = {
|
||||||
|
&dev_attr_cgbc_version.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
ATTRIBUTE_GROUPS(cgbc);
|
||||||
|
|
||||||
|
static int cgbc_get_version(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
u8 cmd = CGBC_CMD_GET_FW_REV;
|
||||||
|
u8 data[4];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = cgbc_command(cgbc, &cmd, 1, &data, sizeof(data), NULL);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
cgbc->version.feature = data[0];
|
||||||
|
cgbc->version.major = data[1];
|
||||||
|
cgbc->version.minor = data[2];
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_init_device(struct cgbc_device_data *cgbc)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = cgbc_session_request(cgbc);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = cgbc_get_version(cgbc);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return mfd_add_devices(cgbc->dev, -1, cgbc_devs, ARRAY_SIZE(cgbc_devs), NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct cgbc_device_data *cgbc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
cgbc = devm_kzalloc(dev, sizeof(*cgbc), GFP_KERNEL);
|
||||||
|
if (!cgbc)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
cgbc->dev = dev;
|
||||||
|
|
||||||
|
ret = cgbc_map(cgbc);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
mutex_init(&cgbc->lock);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, cgbc);
|
||||||
|
|
||||||
|
return cgbc_init_device(cgbc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cgbc_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct cgbc_device_data *cgbc = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
cgbc_session_release(cgbc);
|
||||||
|
|
||||||
|
mfd_remove_devices(&pdev->dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver cgbc_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "cgbc",
|
||||||
|
.dev_groups = cgbc_groups,
|
||||||
|
},
|
||||||
|
.probe = cgbc_probe,
|
||||||
|
.remove_new = cgbc_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct dmi_system_id cgbc_dmi_table[] __initconst = {
|
||||||
|
{
|
||||||
|
.ident = "SA7",
|
||||||
|
.matches = {
|
||||||
|
DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
|
||||||
|
DMI_MATCH(DMI_BOARD_NAME, "conga-SA7"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table);
|
||||||
|
|
||||||
|
static int __init cgbc_init(void)
|
||||||
|
{
|
||||||
|
const struct dmi_system_id *id;
|
||||||
|
int ret = -ENODEV;
|
||||||
|
|
||||||
|
id = dmi_first_match(cgbc_dmi_table);
|
||||||
|
if (IS_ERR_OR_NULL(id))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
cgbc_pdev = platform_device_register_simple("cgbc", PLATFORM_DEVID_NONE, cgbc_resources,
|
||||||
|
ARRAY_SIZE(cgbc_resources));
|
||||||
|
if (IS_ERR(cgbc_pdev))
|
||||||
|
return PTR_ERR(cgbc_pdev);
|
||||||
|
|
||||||
|
return platform_driver_register(&cgbc_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit cgbc_exit(void)
|
||||||
|
{
|
||||||
|
platform_device_unregister(cgbc_pdev);
|
||||||
|
platform_driver_unregister(&cgbc_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(cgbc_init);
|
||||||
|
module_exit(cgbc_exit);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
|
||||||
|
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:cgbc-core");
|
||||||
|
|
@ -1151,6 +1151,16 @@ config ALIM7101_WDT
|
||||||
|
|
||||||
Most people will say N.
|
Most people will say N.
|
||||||
|
|
||||||
|
config CGBC_WDT
|
||||||
|
tristate "Congatec Board Controller Watchdog Timer"
|
||||||
|
depends on MFD_CGBC
|
||||||
|
select WATCHDOG_CORE
|
||||||
|
help
|
||||||
|
Enables watchdog timer support for the Congatec Board Controller.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will be
|
||||||
|
called cgbc_wdt.
|
||||||
|
|
||||||
config EBC_C384_WDT
|
config EBC_C384_WDT
|
||||||
tristate "WinSystems EBC-C384 Watchdog Timer"
|
tristate "WinSystems EBC-C384 Watchdog Timer"
|
||||||
depends on (X86 || COMPILE_TEST) && HAS_IOPORT
|
depends on (X86 || COMPILE_TEST) && HAS_IOPORT
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
|
||||||
obj-$(CONFIG_ADVANTECH_EC_WDT) += advantech_ec_wdt.o
|
obj-$(CONFIG_ADVANTECH_EC_WDT) += advantech_ec_wdt.o
|
||||||
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
|
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
|
||||||
obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
|
obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
|
||||||
|
obj-$(CONFIG_CGBC_WDT) += cgbc_wdt.o
|
||||||
obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
|
obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
|
||||||
obj-$(CONFIG_EXAR_WDT) += exar_wdt.o
|
obj-$(CONFIG_EXAR_WDT) += exar_wdt.o
|
||||||
obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
|
obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
|
||||||
|
|
|
||||||
211
drivers/watchdog/cgbc_wdt.c
Normal file
211
drivers/watchdog/cgbc_wdt.c
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Congatec Board Controller watchdog driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bootlin
|
||||||
|
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/build_bug.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/watchdog.h>
|
||||||
|
|
||||||
|
#include <linux/mfd/cgbc.h>
|
||||||
|
|
||||||
|
#define CGBC_WDT_CMD_TRIGGER 0x27
|
||||||
|
#define CGBC_WDT_CMD_INIT 0x28
|
||||||
|
#define CGBC_WDT_DISABLE 0x00
|
||||||
|
|
||||||
|
#define CGBC_WDT_MODE_SINGLE_EVENT 0x02
|
||||||
|
|
||||||
|
#define CGBC_WDT_MIN_TIMEOUT 1
|
||||||
|
#define CGBC_WDT_MAX_TIMEOUT ((U32_MAX >> 8) / 1000)
|
||||||
|
|
||||||
|
#define CGBC_WDT_DEFAULT_TIMEOUT 30
|
||||||
|
#define CGBC_WDT_DEFAULT_PRETIMEOUT 0
|
||||||
|
|
||||||
|
enum action {
|
||||||
|
ACTION_INT = 0,
|
||||||
|
ACTION_SMI,
|
||||||
|
ACTION_RESET,
|
||||||
|
ACTION_BUTTON,
|
||||||
|
};
|
||||||
|
|
||||||
|
static unsigned int timeout;
|
||||||
|
module_param(timeout, uint, 0);
|
||||||
|
MODULE_PARM_DESC(timeout,
|
||||||
|
"Watchdog timeout in seconds. (>=0, default="
|
||||||
|
__MODULE_STRING(CGBC_WDT_DEFAULT_TIMEOUT) ")");
|
||||||
|
|
||||||
|
static unsigned int pretimeout = CGBC_WDT_DEFAULT_PRETIMEOUT;
|
||||||
|
module_param(pretimeout, uint, 0);
|
||||||
|
MODULE_PARM_DESC(pretimeout,
|
||||||
|
"Watchdog pretimeout in seconds. (>=0, default="
|
||||||
|
__MODULE_STRING(CGBC_WDT_DEFAULT_PRETIMEOUT) ")");
|
||||||
|
|
||||||
|
static bool nowayout = WATCHDOG_NOWAYOUT;
|
||||||
|
module_param(nowayout, bool, 0);
|
||||||
|
MODULE_PARM_DESC(nowayout,
|
||||||
|
"Watchdog cannot be stopped once started (default="
|
||||||
|
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||||
|
|
||||||
|
struct cgbc_wdt_data {
|
||||||
|
struct cgbc_device_data *cgbc;
|
||||||
|
struct watchdog_device wdd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cgbc_wdt_cmd_cfg {
|
||||||
|
u8 cmd;
|
||||||
|
u8 mode;
|
||||||
|
u8 action;
|
||||||
|
u8 timeout1[3];
|
||||||
|
u8 timeout2[3];
|
||||||
|
u8 reserved[3];
|
||||||
|
u8 delay[3];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
static_assert(sizeof(struct cgbc_wdt_cmd_cfg) == 15);
|
||||||
|
|
||||||
|
static int cgbc_wdt_start(struct watchdog_device *wdd)
|
||||||
|
{
|
||||||
|
struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
||||||
|
struct cgbc_device_data *cgbc = wdt_data->cgbc;
|
||||||
|
unsigned int timeout1 = (wdd->timeout - wdd->pretimeout) * 1000;
|
||||||
|
unsigned int timeout2 = wdd->pretimeout * 1000;
|
||||||
|
u8 action;
|
||||||
|
|
||||||
|
struct cgbc_wdt_cmd_cfg cmd_start = {
|
||||||
|
.cmd = CGBC_WDT_CMD_INIT,
|
||||||
|
.mode = CGBC_WDT_MODE_SINGLE_EVENT,
|
||||||
|
.timeout1[0] = (u8)timeout1,
|
||||||
|
.timeout1[1] = (u8)(timeout1 >> 8),
|
||||||
|
.timeout1[2] = (u8)(timeout1 >> 16),
|
||||||
|
.timeout2[0] = (u8)timeout2,
|
||||||
|
.timeout2[1] = (u8)(timeout2 >> 8),
|
||||||
|
.timeout2[2] = (u8)(timeout2 >> 16),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (wdd->pretimeout) {
|
||||||
|
action = 2;
|
||||||
|
action |= ACTION_SMI << 2;
|
||||||
|
action |= ACTION_RESET << 4;
|
||||||
|
} else {
|
||||||
|
action = 1;
|
||||||
|
action |= ACTION_RESET << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_start.action = action;
|
||||||
|
|
||||||
|
return cgbc_command(cgbc, &cmd_start, sizeof(cmd_start), NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_wdt_stop(struct watchdog_device *wdd)
|
||||||
|
{
|
||||||
|
struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
||||||
|
struct cgbc_device_data *cgbc = wdt_data->cgbc;
|
||||||
|
struct cgbc_wdt_cmd_cfg cmd_stop = {
|
||||||
|
.cmd = CGBC_WDT_CMD_INIT,
|
||||||
|
.mode = CGBC_WDT_DISABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
return cgbc_command(cgbc, &cmd_stop, sizeof(cmd_stop), NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_wdt_keepalive(struct watchdog_device *wdd)
|
||||||
|
{
|
||||||
|
struct cgbc_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
||||||
|
struct cgbc_device_data *cgbc = wdt_data->cgbc;
|
||||||
|
u8 cmd_ping = CGBC_WDT_CMD_TRIGGER;
|
||||||
|
|
||||||
|
return cgbc_command(cgbc, &cmd_ping, sizeof(cmd_ping), NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_wdt_set_pretimeout(struct watchdog_device *wdd,
|
||||||
|
unsigned int pretimeout)
|
||||||
|
{
|
||||||
|
wdd->pretimeout = pretimeout;
|
||||||
|
|
||||||
|
if (watchdog_active(wdd))
|
||||||
|
return cgbc_wdt_start(wdd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cgbc_wdt_set_timeout(struct watchdog_device *wdd,
|
||||||
|
unsigned int timeout)
|
||||||
|
{
|
||||||
|
if (timeout < wdd->pretimeout)
|
||||||
|
wdd->pretimeout = 0;
|
||||||
|
|
||||||
|
wdd->timeout = timeout;
|
||||||
|
|
||||||
|
if (watchdog_active(wdd))
|
||||||
|
return cgbc_wdt_start(wdd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct watchdog_info cgbc_wdt_info = {
|
||||||
|
.identity = "CGBC Watchdog",
|
||||||
|
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
|
||||||
|
WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct watchdog_ops cgbc_wdt_ops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.start = cgbc_wdt_start,
|
||||||
|
.stop = cgbc_wdt_stop,
|
||||||
|
.ping = cgbc_wdt_keepalive,
|
||||||
|
.set_timeout = cgbc_wdt_set_timeout,
|
||||||
|
.set_pretimeout = cgbc_wdt_set_pretimeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int cgbc_wdt_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct cgbc_wdt_data *wdt_data;
|
||||||
|
struct watchdog_device *wdd;
|
||||||
|
|
||||||
|
wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
|
||||||
|
if (!wdt_data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
wdt_data->cgbc = cgbc;
|
||||||
|
wdd = &wdt_data->wdd;
|
||||||
|
wdd->parent = dev;
|
||||||
|
|
||||||
|
wdd->info = &cgbc_wdt_info;
|
||||||
|
wdd->ops = &cgbc_wdt_ops;
|
||||||
|
wdd->max_timeout = CGBC_WDT_MAX_TIMEOUT;
|
||||||
|
wdd->min_timeout = CGBC_WDT_MIN_TIMEOUT;
|
||||||
|
|
||||||
|
watchdog_set_drvdata(wdd, wdt_data);
|
||||||
|
watchdog_set_nowayout(wdd, nowayout);
|
||||||
|
|
||||||
|
wdd->timeout = CGBC_WDT_DEFAULT_TIMEOUT;
|
||||||
|
watchdog_init_timeout(wdd, timeout, dev);
|
||||||
|
cgbc_wdt_set_pretimeout(wdd, pretimeout);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, wdt_data);
|
||||||
|
watchdog_stop_on_reboot(wdd);
|
||||||
|
watchdog_stop_on_unregister(wdd);
|
||||||
|
|
||||||
|
return devm_watchdog_register_device(dev, wdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver cgbc_wdt_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "cgbc-wdt",
|
||||||
|
},
|
||||||
|
.probe = cgbc_wdt_probe,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(cgbc_wdt_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Congatec Board Controller Watchdog Driver");
|
||||||
|
MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
44
include/linux/mfd/cgbc.h
Normal file
44
include/linux/mfd/cgbc.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Congatec Board Controller driver definitions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bootlin
|
||||||
|
* Author: Thomas Richard <thomas.richard@bootlin.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_MFD_CGBC_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct cgbc_version - Board Controller device version structure
|
||||||
|
* @feature: Board Controller feature number
|
||||||
|
* @major: Board Controller major revision
|
||||||
|
* @minor: Board Controller minor revision
|
||||||
|
*/
|
||||||
|
struct cgbc_version {
|
||||||
|
unsigned char feature;
|
||||||
|
unsigned char major;
|
||||||
|
unsigned char minor;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct cgbc_device_data - Internal representation of the Board Controller device
|
||||||
|
* @io_session: Pointer to the session IO memory
|
||||||
|
* @io_cmd: Pointer to the command IO memory
|
||||||
|
* @session: Session id returned by the Board Controller
|
||||||
|
* @dev: Pointer to kernel device structure
|
||||||
|
* @cgbc_version: Board Controller version structure
|
||||||
|
* @mutex: Board Controller mutex
|
||||||
|
*/
|
||||||
|
struct cgbc_device_data {
|
||||||
|
void __iomem *io_session;
|
||||||
|
void __iomem *io_cmd;
|
||||||
|
u8 session;
|
||||||
|
struct device *dev;
|
||||||
|
struct cgbc_version version;
|
||||||
|
struct mutex lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size,
|
||||||
|
void *data, unsigned int data_size, u8 *status);
|
||||||
|
|
||||||
|
#endif /*_LINUX_MFD_CGBC_H_*/
|
||||||
Loading…
Reference in New Issue
Block a user