[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20230920111329.GD13143@google.com>
Date: Wed, 20 Sep 2023 12:13:29 +0100
From: Lee Jones <lee@...nel.org>
To: advantech.susiteam@...il.com
Cc: wenkai.chung@...antech.com.tw, Susi.Driver@...antech.com,
linux-kernel@...r.kernel.org
Subject: Re: [PATCH 4/4] mfd: eiois200: Add EIO-IS200 Series EC Core Driver
On Thu, 07 Sep 2023, advantech.susiteam@...il.com wrote:
> From: Wenkai <advantech.susiteam@...il.com>
>
> This patch introduces the Advantech EIO-IS200 Series EC Core Driver.
> The EIO-IS200 combines hardware functionality with firmware
> capabilities to provide key features through a dedicated Power
> Management Channel (PMC).
>
> Key Features:
> - Implements the core EIO-IS200 IC driver that serves as the
> interface to the IC's firmware and hardware functions.
> - Contains low-level functions for accessing the IC's Power
> Management Channel (PMC).
> - Manages PMC command execution, PMC buffer handling, and
> communication.
> - Expose a regmap and a mutex for low-level access.
> - Activates support for GPIO, I2C, Hwmon, and Watchdog functionalities
> of the EIO-IS200 IC.
> - Provides various sysfs attributes to expose information about the
> EC chip, firmware, and motherboard.
> - Includes a timeout parameter to allow modification of the default
> PMC command timeout value, which is particularly useful when
> dealing with an extremely slow-responding device.
>
> Signed-off-by: Wenkai <advantech.susiteam@...il.com>
> ---
> drivers/mfd/eiois200_core.c | 590 ++++++++++++++++++++++++++++++++++++
Please organise patches by functionality, not file.
Each of the patches in this series are worthless without the others.
Have you run static analysis on this?
What about checkpatch.pl?
> 1 file changed, 590 insertions(+)
> create mode 100644 drivers/mfd/eiois200_core.c
>
> diff --git a/drivers/mfd/eiois200_core.c b/drivers/mfd/eiois200_core.c
> new file mode 100644
> index 000000000000..7459dee1ed53
> --- /dev/null
> +++ b/drivers/mfd/eiois200_core.c
> @@ -0,0 +1,590 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Advantech EIO-IS200 Series EC base Driver
> + *
> + * This driver provides an interface to access the EIO-IS200 Series EC
> + * firmware via its own Power Management Channel (PMC) for subdrivers:
> + *
> + * - Watchdog: drivers/watchdog/eiois200_wdt
> + * - GPIO: drivers/gpio/gpio_eiois200
> + * - Hwmon: drivers/hwmon/eiois200_hwmon
> + * - I2C: drivers/i2c/busses/i2c_eiois200
> + * - Thermal: drivers/thermal/eiois200_thermal
None of these exist. I would remove these completely. They will be
highly susceptible to bit-rot.
> + * A system may have one or two independent EIO-IS200s.
> + *
> + * Copyright (C) 2023 Advantech Co., Ltd.
> + * Author: wenkai.chung <wenkai.chung@...antech.com.tw>
Is the name on your government ID "wenkai.chung"?
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/isa.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mutex.h>
> +#include <linux/time.h>
> +#include <linux/uaccess.h>
> +
> +#include "eiois200.h"
> +
> +#define TIMEOUT_MAX (10 * 1000 * 1000)
There are helpers to convert between time bases. Please use them.
Where do these values come from?
> +#define TIMEOUT_MIN 200
> +
> +static uint timeout = 5000;
Why 5000?
> +module_param(timeout, uint, 0664);
> +MODULE_PARM_DESC(timeout,
> + "Default PMC command timeout in usec.\n");
How useful is it to override this on the commandline, really?
> +struct eiois200_dev_port {
> + u16 idx;
> + u16 data;
2 of the worst variable names of all time.
Please ensure variable names describe their purpose.
> +};
> +
> +struct eiois200_dev_port pnp_port[] = {
> + { .idx = EIOIS200_PNP_INDEX, .data = EIOIS200_PNP_DATA },
> + { .idx = EIOIS200_SUB_PNP_INDEX, .data = EIOIS200_SUB_PNP_DATA },
> +};
> +
> +static struct eiois200_dev *eiois200_dev;
> +static struct regmap *regmap_is200;
Avoid globals at all (most, many ...) costs.
> +static struct mfd_cell susi_mfd_devs[] = {
> + { .name = "eiois200_wdt" },
> + { .name = "gpio_eiois200" },
> + { .name = "eiois200_hwmon" },
> + { .name = "eiois200_i2c" },
> + { .name = "eiois200_thermal" },
> +};
> +
> +struct regmap_range is200_range[] = {
> + regmap_reg_range(EIOIS200_PNP_INDEX, EIOIS200_PNP_DATA),
> + regmap_reg_range(EIOIS200_SUB_PNP_INDEX, EIOIS200_SUB_PNP_DATA),
> + regmap_reg_range(EIOIS200_PMC_PORT, EIOIS200_PMC_PORT + 0x0F),
> + regmap_reg_range(EIOIS200_PMC_PORT_SUB, EIOIS200_PMC_PORT_SUB + 0x0F),
> +};
> +
> +static const struct regmap_access_table volatile_regs = {
> + .yes_ranges = is200_range,
> + .n_yes_ranges = ARRAY_SIZE(is200_range),
> +};
> +
> +static const struct regmap_config pnp_regmap_config = {
> + .name = "eiois200-gpio",
> + .reg_bits = 16,
> + .val_bits = 8,
> + .volatile_table = &volatile_regs,
> + .io_port = true,
> +};
> +
> +/* Following are EIO-IS200 pnp io port access functions */
"IO"
Probably "PNP" too.
> +static int is200_pnp_read(struct eiois200_dev_port *port, u8 idx)
> +{
> + int val;
> +
> + if (regmap_write(regmap_is200, port->idx, idx))
> + pr_err("Error port write 0x%X\n", port->idx);
Why pr_err()? You should have a 'dev' pointer. So use dev_err().
> + if (regmap_read(regmap_is200, port->data, &val))
> + pr_err("Error port read 0x%X\n", port->data);
> +
> + return val;
> +}
> +
> +static void is200_pnp_write(struct eiois200_dev_port *port, u8 idx, u8 data)
> +{
> + if (regmap_write(regmap_is200, port->idx, idx) ||
> + regmap_write(regmap_is200, port->data, data))
> + pr_err("Error port write 0x%X %X\n",
> + port->idx, port->data);
> +}
> +
> +static void is200_pnp_enter(struct eiois200_dev_port *port)
> +{
> + if (regmap_write(regmap_is200, port->idx, EIOIS200_EXT_MODE_ENTER) ||
> + regmap_write(regmap_is200, port->idx, EIOIS200_EXT_MODE_ENTER))
How are these 2 calls any different to each other?
> + pr_err("Error port write 0x%X\n", port->idx);
Return error?
> +}
> +
> +static void is200_pnp_leave(struct eiois200_dev_port *port)
> +{
> + if (regmap_write(regmap_is200, port->idx, EIOIS200_EXT_MODE_EXIT))
> + pr_err("Error port write 0x%X\n", port->idx);
Return error?
> +}
> +
> +/* Following are EIO-IS200 io port access functions for pmc command */
> +static int pmc_write_data(int id, u8 value, u16 timeout)
> +{
> + int ret;
> +
> + if (WAIT_IBF(id, timeout))
> + return -ETIME;
What's happening here?
> + ret = regmap_write(regmap_is200, eiois200_dev->pmc[id].data, value);
> + if (ret)
> + pr_err("Error pmc write %X:%X\n",
> + eiois200_dev->pmc[id].data, value);
> +
> + return ret;
> +}
> +
> +static int pmc_write_cmd(int id, u8 value, u16 timeout)
> +{
> + int ret;
> +
> + if (WAIT_IBF(id, timeout))
> + return -ETIME;
> +
> + ret = regmap_write(regmap_is200, eiois200_dev->pmc[id].cmd, value);
> + if (ret)
> + pr_err("Error pmc write %X:%X\n",
> + eiois200_dev->pmc[id].data, value);
> +
> + return ret;
> +}
> +
> +static int pmc_read_data(int id, u8 *value, u16 timeout)
> +{
> + int val, ret;
> +
> + if (WAIT_OBF(id, timeout))
> + return -ETIME;
> +
> + ret = regmap_read(regmap_is200, eiois200_dev->pmc[id].data, &val);
> + if (ret)
> + pr_err("Error pmc read %X\n", eiois200_dev->pmc[id].data);
> + else
> + *value = val & 0xFF;
> +
> + return ret;
> +}
> +
> +static int pmc_read_status(int id)
> +{
> + int val;
> +
> + if (regmap_read(regmap_is200, eiois200_dev->pmc[id].data, &val)) {
> + pr_err("Error pmc read %X\n", eiois200_dev->pmc[id].data);
> + return 0;
You've just filed and yet you are returning success?
> + }
> +
> + return val;
> +}
> +
> +static void pmc_clear(int id)
> +{
> + int val;
> +
> + /* Check if input buffer blocked */
> + if ((pmc_read_status(id) & EIOIS200_PMC_STATUS_IBF) == 0)
> + return;
> +
> + /* Read out previous garbage */
> + if (regmap_read(regmap_is200, eiois200_dev->pmc[id].data, &val))
> + pr_err("Error pmc clear\n");
> +
> + usleep_range(10, 100);
> +}
> +
> +/**
> + * eiois200_core_pmc_wait - Wait for input / output buffer to be ready.
> + * @id: 0 for main chip, 1 for sub chip.
> + * @wait: %PMC_WAIT_INPUT or %PMC_WAIT_OUTPUT.
> + * %PMC_WAIT_INPUT for waiting input buffer data ready.
> + * %PMC_WAIT_OUTPUT for waiting output buffer empty.
> + * max_duration: The timeout value in usec.
> + */
> +int eiois200_core_pmc_wait(int id,
> + enum eiois200_pmc_wait wait,
> + uint max_duration)
> +{
> + u32 cnt = 0;
> + uint val;
> + int ret;
> + int new_timeout = max_duration ? max_duration : timeout;
> + ktime_t time_end = ktime_add_us(ktime_get(), new_timeout);
Remove all of this type of lining up.
> +
> + if (new_timeout > TIMEOUT_MAX ||
> + new_timeout < TIMEOUT_MIN) {
Why the '\n' here?
> + pr_err("Error timeout value: %dus\nTimeout value should between %d and %d\n",
Avoid line breaks in error messages.
> + new_timeout, TIMEOUT_MIN, TIMEOUT_MAX);
> + return -ETIME;
> + }
> +
> + do {
> + ret = regmap_read(regmap_is200,
> + eiois200_dev->pmc[id].status,
> + &val);
> + if (ret)
> + return ret;
> +
> + if (wait == PMC_WAIT_INPUT) {
> + if ((val & EIOIS200_PMC_STATUS_IBF) == 0)
> + return 0;
> + } else {
> + if ((val & EIOIS200_PMC_STATUS_OBF) != 0)
> + return 0;
> + }
> +
> + /* Incremental delay */
> + fsleep(cnt++ * 10);
> +
> + } while (ktime_before(ktime_get(), time_end));
> +
> + return -ETIME;
> +}
> +EXPORT_SYMBOL_GPL(eiois200_core_pmc_wait);
> +
> +/**
> + * eiois200_core_pmc_operation - Execute a pmc command
"PMC"
> + * @op: Pointer to an pmc command.
> + */
> +int eiois200_core_pmc_operation(struct _pmc_op *op)
> +{
> + u8 i;
> + int ret;
> + bool read_cmd = op->cmd & 0x01;
> + ktime_t t = ktime_get();
> +
> + mutex_lock(&eiois200_dev->mutex);
> +
> + pmc_clear(op->chip);
> +
> + ret = pmc_write_cmd(op->chip, op->cmd, op->timeout);
> + if (ret)
> + goto err;
> +
> + ret = pmc_write_data(op->chip, op->control, op->timeout);
> + if (ret)
> + goto err;
> +
> + ret = pmc_write_data(op->chip, op->device_id, op->timeout);
> + if (ret)
> + goto err;
> +
> + ret = pmc_write_data(op->chip, op->size, op->timeout);
> + if (ret)
> + goto err;
> +
> + for (i = 0 ; i < op->size ; i++) {
Remove the spaces before the ';'s.
> + if (read_cmd)
> + ret = pmc_read_data(op->chip, &op->payload[i], op->timeout);
> + else
> + ret = pmc_write_data(op->chip, op->payload[i], op->timeout);
> +
> + if (ret)
> + goto err;
> + }
> +
> + mutex_unlock(&eiois200_dev->mutex);
> +
> + return 0;
> +
> +err:
> + mutex_unlock(&eiois200_dev->mutex);
> +
> + pr_err("PMC error duration:%lldus\n",
> + ktime_to_us(ktime_sub(ktime_get(), t)));
Why the line wrap.
> + pr_err(".cmd=0x%02X, .ctrl=0x%02X .id=0x%02X, .size=0x%02X .data=0x%02X%02X\n",
> + op->cmd, op->control, op->device_id,
> + op->size, op->payload[0], op->payload[1]);
Are these prints actually useful?
I'd suggest that they are debug prints at best.
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(eiois200_core_pmc_operation);
> +
> +static int get_pmc_port(struct device *dev, int id, struct eiois200_dev_port *port)
> +{
> + struct _pmc_port *pmc = &eiois200_dev->pmc[id];
What's with the '_'?
This usually means that it's internal and should not be used.
> + is200_pnp_enter(port);
> +
> + /* Switch to PMC device page */
> + is200_pnp_write(port, EIOIS200_LDN, EIOIS200_LDN_PMC1);
> +
> + /* Active this device */
> + is200_pnp_write(port, EIOIS200_LDAR, EIOIS200_LDAR_LDACT);
> +
> + /* Get PMC cmd and data port */
> + pmc->data = is200_pnp_read(port, EIOIS200_IOBA0H) << 8;
> + pmc->data |= is200_pnp_read(port, EIOIS200_IOBA0L);
> + pmc->cmd = is200_pnp_read(port, EIOIS200_IOBA1H) << 8;
> + pmc->cmd |= is200_pnp_read(port, EIOIS200_IOBA1L);
> +
> + /* Disable IRQ */
> + is200_pnp_write(port, EIOIS200_IRQCTRL, 0);
> +
> + is200_pnp_leave(port);
> +
> + /* Make sure IO ports are not occupied */
> + if (!devm_request_region(dev, pmc->data, 2, KBUILD_MODNAME)) {
> + dev_err(dev, "Request region %X error\n", pmc->data);
> + return -EBUSY;
> + }
> +
> + return 0;
> +}
> +
> +static int eiois200_exist(struct device *dev)
> +{
> + u16 chip_id = 0;
> + u8 tmp = 0;
> + int i = 0;
> + int ret = -ENOMEM;
> + char chip[][8] = { "First", "Second" };
> +
> + for (i = 0 ; i < ARRAY_SIZE(pnp_port) ; i++) {
Should 'i' be 'port' here?
> + struct eiois200_dev_port *port = pnp_port + i;
> +
> + if (!devm_request_region(dev,
> + pnp_port[i].idx,
> + 2,
What's 2? This should probably be defined.
> + KBUILD_MODNAME))
> + continue;
> +
> + is200_pnp_enter(port);
> +
> + chip_id = is200_pnp_read(port, EIOIS200_CHIPID1) << 8;
> + chip_id |= is200_pnp_read(port, EIOIS200_CHIPID2);
> +
> + if (chip_id != EIOIS200_CHIPID &&
> + chip_id != EIO201_211_CHIPID)
> + continue;
> +
> + /* Turn on the enable flag */
> + tmp = is200_pnp_read(port, EIOIS200_SIOCTRL);
> + tmp |= EIOIS200_SIOCTRL_SIOEN;
> + is200_pnp_write(port, EIOIS200_SIOCTRL, tmp);
> +
> + is200_pnp_leave(port);
> +
> + ret = get_pmc_port(dev, i, port);
> + if (ret)
> + return ret;
> +
> + eiois200_dev->flag |= i == 0 ? EIOIS200_F_CHIP_EXIST :
This logic either needs improving or commented on.
> + EIOIS200_F_SUB_CHIP_EXIST;
> +
> + pr_info("%s chip detected: %04X\n", chip[i], chip_id);
We can almost certainly do without these in production code.
> + }
> +
> + return ret;
> +}
> +
> +/* read information about acpi stored in EC */
Please use prober grammar.
"Read ACPI information stored in the EC"
> +static uint8_t acpiram_access(uint8_t offset)
> +{
> + u8 val;
> + int ret;
> +
> + mutex_lock(&eiois200_dev->mutex);
> +
> + pmc_clear(0);
> +
> + ret = pmc_write_cmd(0, EIOIS200_PMC_CMD_ACPIRAM_READ, 0);
All of the magic numbers in this function need to be defined.
> + if (ret)
> + goto err;
> +
> + ret = pmc_write_data(0, offset, 0);
> + if (ret)
> + goto err;
> +
> + ret = pmc_write_data(0, 1, 0);
> + if (ret)
> + goto err;
> +
> + ret = pmc_read_data(0, &val, 0);
> + if (ret)
> + goto err;
> +
> +err:
> + mutex_unlock(&eiois200_dev->mutex);
> + return ret ? 0 : val;
If it fails, you return success?
> +}
> +
> +static int firmware_code_base(struct device *dev)
> +{
> + u8 ic_vendor, ic_code, code_base;
> +
> + ic_vendor = acpiram_access(EIOIS200_ACPIRAM_ICVENDOR);
> + ic_code = acpiram_access(EIOIS200_ACPIRAM_ICCODE);
> + code_base = acpiram_access(EIOIS200_ACPIRAM_CODEBASE);
> +
> + if (ic_vendor != 'R')
> + return -ENODEV;
> +
> + if (ic_code != EIOIS200_ICCODE &&
> + ic_code != EIO201_ICCODE &&
> + ic_code != EIO211_ICCODE)
> + goto err;
> +
> + if (code_base == 0x80) {
No magic numbers please.
> + eiois200_dev->flag |= EIOIS200_F_NEW_CODE_BASE;
> + return 0;
> + }
> +
> + if (code_base == 0 && (ic_code != EIO201_ICCODE &&
> + ic_code != EIO211_ICCODE)) {
> + pr_info("Old code base not supported, yet.");
> + return -ENODEV;
> + }
> +
> + err:
> + dev_err(dev,
> + "Codebase check fail:\n"
> + "ic_vendor: 0x%X ,ic_code : 0x%X ,code_base : 0x%X\n",
> + ic_vendor, ic_code, code_base);
These are really ugly prints.
Also, they should not contain new lines.
> + return -ENODEV;
> +}
> +
> +int eiois200_probe(struct device *dev, unsigned int id)
.probe() usually goes near the bottom.
> +{
> + int ret = 0;
> + void __iomem *iomem;
Swap these over.
> + iomem = devm_ioport_map(dev, 0, EIOIS200_SUB_PNP_DATA + 1);
> + if (!iomem)
> + return -ENOMEM;
> +
> + regmap_is200 = devm_regmap_init_mmio(dev, iomem, &pnp_regmap_config);
> + if (!regmap_is200)
> + return -ENOMEM;
> +
> + eiois200_dev = devm_kzalloc(dev,
> + sizeof(struct eiois200_dev),
sizeof(*eiois200_dev)
> + GFP_KERNEL);
> + if (!eiois200_dev)
> + return -ENOMEM;
> +
> + mutex_init(&eiois200_dev->mutex);
> +
> + if (eiois200_exist(dev))
> + return -ENODEV;
> +
> + if (firmware_code_base(dev)) {
> + dev_err(dev, "Chip code base check fail\n");
> + return -EIO;
> + }
> +
> + dev_set_drvdata(dev, eiois200_dev);
What data have you stored in eiois200_dev?
> + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, susi_mfd_devs,
> + ARRAY_SIZE(susi_mfd_devs),
> + NULL, 0, NULL);
> + if (ret)
> + dev_err(dev, "Cannot register child devices (error = %d)\n", ret);
> +
> + pr_info("%s started", KBUILD_MODNAME);
These prints are not useful, please remove them.
> +
> + return 0;
> +}
> +
> +struct {
> + char name[32];
> + int cmd;
> + int ctrl;
> + int size;
> +} attrs[] = {
> + { "board_name", 0x53, 0x10, 16 },
> + { "board_serial", 0x53, 0x1F, 16 },
> + { "board_manufacturer", 0x53, 0x11, 16 },
> + { "board_id", 0x53, 0x1E, 4 },
> + { "firmware_version", 0x53, 0x22, 16 },
> + { "firmware_build", 0x53, 0x23, 26 },
> + { "firmware_date", 0x53, 0x24, 16 },
> + { "chip_id", 0x53, 0x12, 12 },
> + { "chip_detect", 0x53, 0x15, 12 },
> + { "platform_type", 0x53, 0x13, 16 },
> + { "platform_revision", 0x53, 0x14, 4 },
> + { "eapi_version", 0x53, 0x30, 4 },
> + { "eapi_id", 0x53, 0x31, 4 },
> + { "boot_count", 0x55, 0x10, 4 },
> + { "powerup_hour", 0x55, 0x11, 4 },
> +};
> +
> +static ssize_t info_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
SYSFS handling usually goes near the top.
> +{
> + uint i;
> +
> + for (i = 0 ; i < ARRAY_SIZE(attrs) ; i++)
> + if (!strcmp(attr->attr.name, attrs[i].name)) {
> + int ret;
> + char str[32] = "";
> + struct _pmc_op op = {
> + .cmd = attrs[i].cmd,
> + .control = attrs[i].ctrl,
> + .payload = (u8 *)str,
> + .size = attrs[i].size,
> + };
> +
> + ret = eiois200_core_pmc_operation(&op);
> + if (ret)
> + return ret;
> +
> + if (attrs[i].size == 4)
> + return sysfs_emit(buf, "%X\n", *(u32 *)str);
> + else
> + return sysfs_emit(buf, "%s\n", str);
> + }
> +
> + return -EINVAL;
> +}
> +
> +static DEVICE_ATTR(board_name, 0444, info_show, NULL);
> +static DEVICE_ATTR(board_serial, 0444, info_show, NULL);
> +static DEVICE_ATTR(board_manufacturer, 0444, info_show, NULL);
> +static DEVICE_ATTR(firmware_version, 0444, info_show, NULL);
> +static DEVICE_ATTR(firmware_build, 0444, info_show, NULL);
> +static DEVICE_ATTR(firmware_date, 0444, info_show, NULL);
> +static DEVICE_ATTR(chip_id, 0444, info_show, NULL);
> +static DEVICE_ATTR(chip_detect, 0444, info_show, NULL);
> +static DEVICE_ATTR(platform_type, 0444, info_show, NULL);
> +static DEVICE_ATTR(platform_revision, 0444, info_show, NULL);
> +static DEVICE_ATTR(board_id, 0444, info_show, NULL);
> +static DEVICE_ATTR(eapi_version, 0444, info_show, NULL);
> +static DEVICE_ATTR(eapi_id, 0444, info_show, NULL);
> +static DEVICE_ATTR(boot_count, 0444, info_show, NULL);
> +static DEVICE_ATTR(powerup_hour, 0444, info_show, NULL);
> +
> +static struct attribute *pmc_attrs[] = {
> + &dev_attr_board_name.attr,
> + &dev_attr_board_serial.attr,
> + &dev_attr_board_manufacturer.attr,
> + &dev_attr_firmware_version.attr,
> + &dev_attr_firmware_build.attr,
> + &dev_attr_firmware_date.attr,
> + &dev_attr_chip_id.attr,
> + &dev_attr_chip_detect.attr,
> + &dev_attr_platform_type.attr,
> + &dev_attr_platform_revision.attr,
> + &dev_attr_board_id.attr,
> + &dev_attr_eapi_version.attr,
> + &dev_attr_eapi_id.attr,
> + &dev_attr_boot_count.attr,
> + &dev_attr_powerup_hour.attr,
> + NULL
> +};
> +
> +static const struct attribute_group attr_group = {
> + .attrs = pmc_attrs,
> +};
> +
> +static const struct attribute_group *attr_groups[] = {
> + &attr_group,
> + NULL
> +};
Pretty sure there are helper MACROS for this.
> +static struct isa_driver eiois200_driver = {
> + .probe = eiois200_probe,
> +
> + .driver = {
> + .owner = THIS_MODULE,
Are you sure the class doesn't do this for you?
> + .name = KBUILD_MODNAME,
Use a proper string.
> + .dev_groups = attr_groups,
> + },
> +};
> +
> +module_isa_driver(eiois200_driver, 1);
> +
> +MODULE_AUTHOR("susi.driver <susi.driver@...antech.com.tw>");
This does not match the credentials above.
> +MODULE_DESCRIPTION("Advantech EIO-IS200 series EC core driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.34.1
>
--
Lee Jones [李琼斯]
Powered by blists - more mailing lists