AHC1EC0 is the embedded controller for Advantech industrial products. This provides sub-devices such as HWMON and Watchdog, and also exposes functions for sub-devices to read/write the value to embedded controller. Changed in V7: Fix the patch according to reviewer's comment: - donot need to do memory clear since devm_kzalloc() do it internal - rename ADVEC_SUBDEV_{DEVICE} to ADVEC_ACPI_ID_{DEVICE} - move some common functions to drivers/platform/x86/ahc1ec0-core.c - change the data content of ACPI table, use a clear sub-devices name and properties instead of index to start sub-devices - correction of words Changed in V6: - Kconfig: add "AHC1EC0" string to clearly define the EC name - fix the code according to reviewer's suggestion - remove unnecessary header files - change the structure name to lower case, align with others naming Signed-off-by: Campion Kang <campion.kang@xxxxxxxxxxxxxxxx> --- drivers/mfd/Kconfig | 11 +++ drivers/mfd/Makefile | 1 + drivers/mfd/ahc1ec0.c | 172 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 drivers/mfd/ahc1ec0.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 5c7f2b100191..b61b11506103 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2174,5 +2174,16 @@ config MFD_INTEL_M10_BMC additional drivers must be enabled in order to use the functionality of the device. +config MFD_AHC1EC0 + tristate "Advantech AHC1EC0 Embedded Controller" + depends on X86 + depends on AHC1EC0_CORE + select MFD_CORE + help + Select this to get support for Advantech Embedded Controller sub-devices. + This driver will instantiate additional drivers such as HWMON, Watchdog, + etc., you also have to select the individual drivers. + The controller is running firmware customized for Advantech hardware. + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4f6d2b8a5f76..270db521e1d8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -271,3 +271,4 @@ obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o +obj-$(CONFIG_MFD_AHC1EC0) += ahc1ec0.o diff --git a/drivers/mfd/ahc1ec0.c b/drivers/mfd/ahc1ec0.c new file mode 100644 index 000000000000..575b654ade5f --- /dev/null +++ b/drivers/mfd/ahc1ec0.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Advantech AHC1EC0 Embedded Controller + * + * Copyright 2021 Advantech IIoT Group + */ + +#include <linux/acpi.h> +#include <linux/errno.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/platform_data/ahc1ec0.h> + +/* This order cannot be changed, it is use in enum and may in BIOS ACPI table. */ +enum { + ADVEC_ACPI_ID_BRIGHTNESS = 0, + ADVEC_ACPI_ID_EEPROM, + ADVEC_ACPI_ID_GPIO, + ADVEC_ACPI_ID_HWMON, + ADVEC_ACPI_ID_LED, + ADVEC_ACPI_ID_WDT, + ADVEC_ACPI_ID_MAX, +}; + +static const struct mfd_cell adv_ec_sub_cells[] = { + { .name = "adv-ec-brightness", }, + { .name = "adv-ec-eeprom", }, + { .name = "adv-ec-gpio", }, + { .name = "ahc1ec0-hwmon", }, + { .name = "adv-ec-led", }, + { .name = "ahc1ec0-wdt", }, +}; + +static int adv_ec_init_ec_data(struct adv_ec_ddata *ddata) +{ + int ret; + + mutex_init(&ddata->lock); + + /* Get product name */ + ddata->bios_product_name = + devm_kzalloc(ddata->dev, AMI_ADVANTECH_BOARD_ID_LENGTH, GFP_KERNEL); + if (!ddata->bios_product_name) + return -ENOMEM; + + ret = adv_ec_get_productname(ddata, ddata->bios_product_name); + if (ret) + return ret; + + /* Get pin table */ + ddata->dym_tbl = devm_kzalloc(ddata->dev, + EC_MAX_TBL_NUM * sizeof(struct ec_dynamic_table), + GFP_KERNEL); + if (!ddata->dym_tbl) + return -ENOMEM; + + return adv_get_dynamic_tab(ddata); +} + +static int adv_ec_parse_prop(struct adv_ec_ddata *ddata) +{ + int ret; + u32 value; + bool has_watchdog = true; + + /* check whether this EC has the following subdevices, hwmon and watchdog. */ + if (device_property_read_u32(ddata->dev, "advantech,hwmon-profile", &value) >= 0) { + ret = mfd_add_hotplug_devices(ddata->dev, + &adv_ec_sub_cells[ADVEC_ACPI_ID_HWMON], 1); + if (ret) { + dev_err(ddata->dev, "Failed to add %s subdevice: %d\n", + adv_ec_sub_cells[ADVEC_ACPI_ID_HWMON].name, ret); + } else { + dev_info(ddata->dev, "Success to add %s subdevice\n", + adv_ec_sub_cells[ADVEC_ACPI_ID_HWMON].name); + } + } else { + dev_err(ddata->dev, "No 'advantech,hwmon-profile' property: %d\n", + ret); + } + + /* default is true for watchdog even if it is not existed */ + if (device_property_present(ddata->dev, "advantech,has-watchdog")) + has_watchdog = device_property_read_bool(ddata->dev, "advantech,has-watchdog"); + if (has_watchdog) { + ret = mfd_add_hotplug_devices(ddata->dev, &adv_ec_sub_cells[ADVEC_ACPI_ID_WDT], 1); + if (ret) { + dev_err(ddata->dev, "Failed to add %s subdevice: %d\n", + adv_ec_sub_cells[ADVEC_ACPI_ID_WDT].name, ret); + } else { + dev_info(ddata->dev, "Success to add %s subdevice\n", + adv_ec_sub_cells[ADVEC_ACPI_ID_WDT].name); + } + } + + return 0; +} + +static int adv_ec_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct adv_ec_ddata *ddata; + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + dev_set_drvdata(dev, ddata); + ddata->dev = dev; + + ret = adv_ec_init_ec_data(ddata); + if (ret) + goto err_prop; + + ret = adv_ec_parse_prop(ddata); + if (ret) + goto err_prop; + + dev_info(ddata->dev, "Advantech AHC1EC0 probe done"); + + return 0; + +err_prop: + mutex_destroy(&ddata->lock); + + dev_dbg(dev, "Failed to init data and probe\n"); + return ret; +} + +static int adv_ec_remove(struct platform_device *pdev) +{ + struct adv_ec_ddata *ddata; + + ddata = dev_get_drvdata(&pdev->dev); + + mutex_destroy(&ddata->lock); + return 0; +} + +static const struct of_device_id adv_ec_of_match[] __maybe_unused = { + { .compatible = "advantech,ahc1ec0" }, + { }, +}; +MODULE_DEVICE_TABLE(of, adv_ec_of_match); + +static const struct acpi_device_id adv_ec_acpi_match[] __maybe_unused = { + { "AHC1EC0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, adv_ec_acpi_match); + +static struct platform_driver adv_ec_driver = { + .driver = { + .name = "ahc1ec0", + .of_match_table = of_match_ptr(adv_ec_of_match), + .acpi_match_table = ACPI_PTR(adv_ec_acpi_match), + }, + .probe = adv_ec_probe, + .remove = adv_ec_remove, +}; +module_platform_driver(adv_ec_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ahc1ec0"); +MODULE_DESCRIPTION("Advantech AHC1EC0 Embedded Controller"); +MODULE_AUTHOR("Campion Kang <campion.kang@xxxxxxxxxxxxxxxx>"); +MODULE_AUTHOR("Jianfeng Dai <jianfeng.dai@xxxxxxxxxxxxxxxx>"); +MODULE_VERSION("1.0"); -- 2.17.1