From patchwork Tue Oct 1 11:53:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thomas Richard X-Patchwork-Id: 832075 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 50A311C1AB0; Tue, 1 Oct 2024 11:54:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.183.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727783650; cv=none; b=KdjlErSDMk5OmLlu86Mld/Qznhr75IjcUrK/zjje+SCLAccg+D+q+zAIEPmRsxulWCby4hpAFEbQ4RPszRd9V8/T/5EZ2xUqEnrMKFdNPMbOhy9vN8ayh5IV/dDpuJ3uw9d5XSPEaL1VGaukpcyyjbztPRZas7u6ZPIaUnD5pBE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727783650; c=relaxed/simple; bh=7dxY5gsLG+mtpmelht91UP7w8h0N0FQPJPsvcBpP8hc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=M3SOfpkkBHsoZWDwHSoZzl6sIqKQtVDsXTTCOkLCRAc6Cpj3517+3yfJZJyVBcnU0iwR//VzbacmO7YpU76fBCkyG8iM6X9gVNA3MH7Xb0GZOdcPT7vGdg0gHpu1eNzLetm+ygBHvmlk6jw5voeqJLAIpLCzE7ixfjjaTQ4d7hE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=VF6uph86; arc=none smtp.client-ip=217.70.183.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="VF6uph86" Received: by mail.gandi.net (Postfix) with ESMTPSA id 1D0F440002; Tue, 1 Oct 2024 11:54:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1727783645; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=0YFPwNqal6KGcUnKa18Tul/GswhEBHjbLZRfObJHAB4=; b=VF6uph86j4JxG4/jqamu1mYgh0bgSVEhZSXo1oQ51e38YDL0Z2aVdSZ6nlQCa6vOmT2xZk CrxiOzJZNVZBeagzuZs7kMHQEwhPAMN2guMTd1L3eiaL5vLxFhTgT/fyht4edxc1bK4CyA CKtjfRVGXvH6xjVhk06qYqLamxNEpNk5gLpwjpX5UHB8IVNw9kMYBeMtNGWo6hlWmZMJVz WYkZkSDEnVG0ii9/b0U/N5iMc+xb7RmBM1ncwFtvRaNAZCfzgflrMB5gqtbugcNFX3pPeG WenLS8dy4uikQbhBWzla24G3JifqAWDdg53B6i6lhOl5fJFsRZAM3u5l6fkfSA== From: Thomas Richard Date: Tue, 01 Oct 2024 13:53:27 +0200 Subject: [PATCH v3 1/5] mfd: add Congatec Board Controller mfd driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241001-congatec-board-controller-v3-1-39ceceed5c47@bootlin.com> References: <20241001-congatec-board-controller-v3-0-39ceceed5c47@bootlin.com> In-Reply-To: <20241001-congatec-board-controller-v3-0-39ceceed5c47@bootlin.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Andi Shyti , Wim Van Sebroeck , Guenter Roeck Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-watchdog@vger.kernel.org, thomas.petazzoni@bootlin.com, blake.vermeer@keysight.com, Thomas Richard X-Mailer: b4 0.14.1 X-GND-Sasl: thomas.richard@bootlin.com Add core MFD driver for the Board Controller found on some Congatec SMARC module. This Board Controller provides functions like watchdog, GPIO, and I2C busses. This commit adds support only for the conga-SA7 module. Signed-off-by: Thomas Richard --- drivers/mfd/Kconfig | 12 ++ drivers/mfd/Makefile | 1 + drivers/mfd/cgbc-core.c | 411 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/cgbc.h | 44 +++++ 4 files changed, 468 insertions(+) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index f9325bcce1b9..03c1e4e3eea4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -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 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 2a9f91e81af8..e057d6d6faef 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -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 diff --git a/drivers/mfd/cgbc-core.c b/drivers/mfd/cgbc-core.c new file mode 100644 index 000000000000..93004a6b29c1 --- /dev/null +++ b/drivers/mfd/cgbc-core.c @@ -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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cgbc-core"); diff --git a/include/linux/mfd/cgbc.h b/include/linux/mfd/cgbc.h new file mode 100644 index 000000000000..badbec4c7033 --- /dev/null +++ b/include/linux/mfd/cgbc.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Congatec Board Controller driver definitions + * + * Copyright (C) 2024 Bootlin + * Author: Thomas Richard + */ + +#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_*/ From patchwork Tue Oct 1 11:53:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thomas Richard X-Patchwork-Id: 832074 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49F301C1ABE; Tue, 1 Oct 2024 11:54:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.183.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727783651; cv=none; b=TSufoJgbublLBokFZXS1mUMmD3RqMV/9JuEHIq8S1zYzfryveJiIIxZZQ+WZKUd6pJmISdaCRJZZPVeOB5O/NcezSfLMOOWtpzHsHVS4RuGG8rwtmbA5hj9qPzZU9ocAAmibjrY8kV6OzgnXVtZdDyv/g4X73xMU+0IIF+AFNO0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727783651; c=relaxed/simple; bh=KDqmb3ktuc9m5IAQTHG4aDFNrvdGw74h2nSbelVa4A8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ldUEBvXft8ws/rJ7omva/74U5DYKz28ikMd3BmP+kRZO0m4rk7dxxmiTULsGZ3iwWJn4LFaLwTYwjd+dXA9pX9flnWrWpQYOaSUYDZA6ywCNQkp6hYyVa7Jjm/9fdmWTMdg+fc8sijbPmHOMvu44lRX5pH4oV85taHwpZkMhjDg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=LtQeucD+; arc=none smtp.client-ip=217.70.183.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="LtQeucD+" Received: by mail.gandi.net (Postfix) with ESMTPSA id 4FA7D40006; Tue, 1 Oct 2024 11:54:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1727783646; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=XCSDYGMQrc1XdlAUwurjNtgekrjkqYRs263mZ2xKJM0=; b=LtQeucD+Y8FdwLQi65BzzKt/Vmwb8yxGFEajTQYqOfxTAjG9FTJjfK2w/GCWYc/5gNR6e1 JvZA0EfCCj17cWSw8wHZkbQ+nH/T2nuoH+CfzBVFtIHCpz3j80qWSBPcMYgD6dne1UrtSs REEz4nMjpEYVhhXoH+98NSfDS9gBWmkx6PqoRsCpmuGZuLwrZFmU0xcDHZ2mwowhsi3vHA r3oKfMBcXxNEKrE3RaF1opE0m2NJvLv5scfvbLRTOu92V0Ntyyh5WQyuDMf/EayIq/TMOf odyL3jJhSo3O1SzaJI5TFzd5EOdkgQMvw0yWgNdnjWgtVuE1qwSK8Ba7MuVi1A== From: Thomas Richard Date: Tue, 01 Oct 2024 13:53:29 +0200 Subject: [PATCH v3 3/5] i2c: Congatec Board Controller i2c bus driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241001-congatec-board-controller-v3-3-39ceceed5c47@bootlin.com> References: <20241001-congatec-board-controller-v3-0-39ceceed5c47@bootlin.com> In-Reply-To: <20241001-congatec-board-controller-v3-0-39ceceed5c47@bootlin.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Andi Shyti , Wim Van Sebroeck , Guenter Roeck Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-watchdog@vger.kernel.org, thomas.petazzoni@bootlin.com, blake.vermeer@keysight.com, Thomas Richard X-Mailer: b4 0.14.1 X-GND-Sasl: thomas.richard@bootlin.com Add i2c support for the Congatec Board Controller. The Board Controller has two i2c busses, a General Purpose bus and a Power Management bus. Acked-by: Andi Shyti Signed-off-by: Thomas Richard --- drivers/i2c/busses/Kconfig | 10 ++ drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-cgbc.c | 406 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 6b3ba7e5723a..4977abcd7c46 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -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 diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index ecc07c50f2a0..a6bcbf2febcf 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -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 diff --git a/drivers/i2c/busses/i2c-cgbc.c b/drivers/i2c/busses/i2c-cgbc.c new file mode 100644 index 000000000000..eba0b205de11 --- /dev/null +++ b/drivers/i2c/busses/i2c-cgbc.c @@ -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 + */ + +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cgbc_i2c"); From patchwork Tue Oct 1 11:53:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thomas Richard X-Patchwork-Id: 832073 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C74AD1C1AC2; Tue, 1 Oct 2024 11:54:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.183.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727783652; cv=none; b=bbCJEwo4ibA8KKeHzCGRc2cVNZVpJUSaHn5EcJTqsHRKXKhRkgSuo8ixHT85Se0+cOWAFkVM5f3CzBrMFM+y1S0tbIlzXWYDEMy28ivm6A680DyvZdxufZf81eNkLRvkd1/plkcxRmGBFEQAWT5DFgQu03/DK+vcY2w8k+SH6S8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727783652; c=relaxed/simple; bh=l75P8wvWW76vAuHq66CKFYMo8Ry239dCB1UirylqHEY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Tnm787YDy6flkfa6777CiDz3/EwnLpaHeUVqdU85eYpxkMYgFxhwRzJInZ566k7FC49rHurj+di+8qcw0kd4JmsV+bG7fP1KTjhOZuqUqVbEmwVJS6mjpQWeT9MowVTaG1ga/G6MPHa/SPCkF/U+hFD2lQ/qHu78GNJ72Co/VL0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=SxR80kvB; arc=none smtp.client-ip=217.70.183.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="SxR80kvB" Received: by mail.gandi.net (Postfix) with ESMTPSA id D993540005; Tue, 1 Oct 2024 11:54:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1727783647; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8S/WWR1XIvLtlIPuH5AVajm0Rnr/KV/MFO37sHhN0cU=; b=SxR80kvByK9yqNfWdh30PYD/QBwt+gc4wN7zCVN2AJW5qM5b703r92VHEqTL/lcQEcONVC yAisssFjggU81m4W2Fie8zA8AJkgUE6VZBUcxLhD4WNkghWrhVqJaBTnKqmMjRU285nujf R4Un6HDbytzp6U9OmepWlh8Uy7BNMqWWqB5cudNWQSuf6dFtOgX0YXZ7pVtP+waVfj6cAU hNoj3gwhMCxEfkV2nlS9zGQRI3KAcTl8/atp8/rUJGI7VV+5lO8jkN9ceTBtu5sSNmw1yL lcoZB22TmGO3m7WziDufJ1xwYLaUmf774/ZHZtaA5DABjoqG5H57F1yXM9A0QA== From: Thomas Richard Date: Tue, 01 Oct 2024 13:53:30 +0200 Subject: [PATCH v3 4/5] watchdog: Congatec Board Controller watchdog timer driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241001-congatec-board-controller-v3-4-39ceceed5c47@bootlin.com> References: <20241001-congatec-board-controller-v3-0-39ceceed5c47@bootlin.com> In-Reply-To: <20241001-congatec-board-controller-v3-0-39ceceed5c47@bootlin.com> To: Lee Jones , Linus Walleij , Bartosz Golaszewski , Andi Shyti , Wim Van Sebroeck , Guenter Roeck Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-watchdog@vger.kernel.org, thomas.petazzoni@bootlin.com, blake.vermeer@keysight.com, Thomas Richard X-Mailer: b4 0.14.1 X-GND-Sasl: thomas.richard@bootlin.com Add watchdog timer support for the Congatec Board Controller. Acked-by: Guenter Roeck Signed-off-by: Thomas Richard --- drivers/watchdog/Kconfig | 10 +++ drivers/watchdog/Makefile | 1 + drivers/watchdog/cgbc_wdt.c | 211 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 684b9fe84fff..755cf3559540 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -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 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index ab6f2b41e38e..386d88d89fe5 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -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 diff --git a/drivers/watchdog/cgbc_wdt.c b/drivers/watchdog/cgbc_wdt.c new file mode 100644 index 000000000000..702b055ba6f4 --- /dev/null +++ b/drivers/watchdog/cgbc_wdt.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Congatec Board Controller watchdog driver + * + * Copyright (C) 2024 Bootlin + * Author: Thomas Richard + */ + +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +MODULE_LICENSE("GPL");