diff mbox series

[v7,5/7] mfd: ahc1ec0: Add support for Advantech embedded controller

Message ID 20210506081619.2443-5-campion.kang@advantech.com.tw
State New
Headers show
Series [v7,1/7] MAINTAINERS: Add Advantech AHC1EC0 embedded controller entry | expand

Commit Message

Campion Kang May 6, 2021, 8:16 a.m. UTC
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@advantech.com.tw>
---
 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 mbox series

Patch

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@advantech.com.tw>");
+MODULE_AUTHOR("Jianfeng Dai <jianfeng.dai@advantech.com.cn>");
+MODULE_VERSION("1.0");