mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-04 04:04:19 +00:00
Merge branches 'ib-dt-mfd-input-leds-power-rtc-6.13' and 'ib-mfd-gpio-i2c-watchdog-6.13' into ibs-for-mfd-merged
This commit is contained in:
commit
98b5f7706f
@ -5760,6 +5760,15 @@ F: fs/configfs/
|
||||
F: include/linux/configfs.h
|
||||
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
|
||||
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
S: Supported
|
||||
|
@ -1287,6 +1287,16 @@ config GPIO_BD9571MWV
|
||||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "ChromeOS EC GPIO support"
|
||||
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_BT8XX) += gpio-bt8xx.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_SNPS_CREG) += gpio-creg-snps.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
|
||||
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
|
||||
tristate "Freescale CPM1 or CPM2 (MPC8xx/826x)"
|
||||
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_CADENCE) += i2c-cadence.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_DAVINCI) += i2c-davinci.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
|
||||
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
|
||||
tristate "ChromeOS Embedded Controller multifunction device"
|
||||
select MFD_CORE
|
||||
|
@ -13,6 +13,7 @@ obj-$(CONFIG_MFD_SM501) += sm501.o
|
||||
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
|
||||
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.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_CS42L43) += cs42l43.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.
|
||||
|
||||
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
|
||||
tristate "WinSystems EBC-C384 Watchdog Timer"
|
||||
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_ALIM1535_WDT) += alim1535_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_EXAR_WDT) += exar_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