Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1513736 > unrolled thread
| Started by | Richard Vidal-Dorsch <richard.dorsch@gmail.com> |
|---|---|
| First post | 2016-11-02 09:40 +0100 |
| Last post | 2016-11-03 09:40 +0100 |
| Articles | 7 — 3 participants |
Back to article view | Back to linux.kernel
[PATCH v4 0/6] Advantech iManager EC driver set Richard Vidal-Dorsch <richard.dorsch@gmail.com> - 2016-11-02 09:40 +0100
[PATCH v4 6/6] Add Advantech iManager Watchdog driver Richard Vidal-Dorsch <richard.dorsch@gmail.com> - 2016-11-02 09:40 +0100
[PATCH v4 5/6] Add Advantech iManager Backlight driver Richard Vidal-Dorsch <richard.dorsch@gmail.com> - 2016-11-02 09:50 +0100
[PATCH v4 2/6] Add Advantech iManager GPIO driver Richard Vidal-Dorsch <richard.dorsch@gmail.com> - 2016-11-02 09:50 +0100
Re: [PATCH v4 2/6] Add Advantech iManager GPIO driver Linus Walleij <linus.walleij@linaro.org> - 2016-11-05 09:40 +0100
[PATCH v4 4/6] Add Advantech iManager I2C driver Richard Vidal-Dorsch <richard.dorsch@gmail.com> - 2016-11-02 09:50 +0100
Re: [PATCH v4 1/6] Add Advantech iManager MFD core driver Lee Jones <lee.jones@linaro.org> - 2016-11-03 09:40 +0100
| From | Richard Vidal-Dorsch <richard.dorsch@gmail.com> |
|---|---|
| Date | 2016-11-02 09:40 +0100 |
| Subject | [PATCH v4 0/6] Advantech iManager EC driver set |
| Message-ID | <syZfr-3TD-3@gated-at.bofh.it> |
The Advantech iManager is a custom embedded controller based on ITE IT8518 or IT8528 EC (depending on PCB). It runs a custom firmware that provides access to features such as GPIO, I2C/SMbus, hwmon, watchdog, and backlight/brightness control. All drivers are being managed by the iManager (mfd) core driver. It acts as a 'gateway' and handles communications between EC and sub-drivers. The imanager-core follows a similar concept as Kontron's kempld-core driver. During core init, the device id list is retrieved from the firmware and an internal device list is being created. This list is then passed down to managed drivers. The out-of-tree iManager driver set is maintained at https://www.github.com/rvido/iManager Notebook manufactures such as Dell (XPS series) or Razerzone (Razer Blade) are using similar ITE ECs or even the same ones to implement their own embedded functions. Due to the level of firmware customization which those ITE ECs provide, the iManager EC driver set is incompatible with those vendors solutions (and probably others too). Note that v1..v3 were submitted in Jan. 2016. It took some time to apply suggested changes. However, I kept the original versioning to avoid conflict with previous patch submissions. Thanks to Krzysztof Kozłowski, Guenter Roeck, and Lee Jones for their invaluable feedback. Those drivers clearly needed to be further improved and cleaned. Changes from v3: - Merge imanager-ec-* with corresponding drivers This eliminates static init vars and reduces code size - Remove Documentation/devicetree/bindings/mfd/imanager.txt No support for DT - Add API comments - List include files in alphabetic order - Run patches through ./scripts/checkpatch.pl --strict - Remove headers except imanger.h/imanager-ec.h Store them in include/linux/mfd/ imanager-core: - Reduce amount of exported functions in core - Use usleep_range() instead of udelay() - Use dev_info() or dev_warn() where necessary - Fix register/probing and __init/__exit - Optimize imanager_read_device_config() - Shrink down EC device table to known and supported devices - Define flags with BIT() - Use new imanager device struct i2c-imanager: - Add support for multiple I2C/SMbus adapters - Use new imanager device struct imanager-hwmon-* gpio-imanager: imanager_bl: imanager_wdt: - Define flags with BIT() - Use new imanager device struct Changes from v2: - Remove .owner from platform_driver in: drivers/gpio/imanager-bl drivers/gpio/imanager-core.c drivers/gpio/imanager-i2c.c drivers/gpio/imanager-hwmon.c Changes from v1: - Remove .owner from platform_driver in drivers/gpio/imanager-gpio.c - Remove .owner from platform_driver in drivers/gpio/imanager-wdt.c - Replace 2015 by 2016 in all files Richard Vidal-Dorsch (6): Add Advantech iManager MFD core driver Add Advantech iManager GPIO driver Add Advantech iManager HWmon driver Add Advantech iManager I2C driver Add Advantech iManager Backlight driver Add Advantech iManager Watchdog driver drivers/gpio/Kconfig | 10 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-imanager.c | 155 +++++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/imanager-hwmon.c | 1226 +++++++++++++++++++++++++++++++++ drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-imanager.c | 461 +++++++++++++ drivers/mfd/Kconfig | 18 + drivers/mfd/Makefile | 1 + drivers/mfd/imanager-core.c | 941 +++++++++++++++++++++++++ drivers/video/backlight/Kconfig | 8 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/imanager_bl.c | 210 ++++++ drivers/watchdog/Kconfig | 11 + drivers/watchdog/Makefile | 1 + drivers/watchdog/imanager_wdt.c | 303 ++++++++ include/linux/mfd/imanager-ec.h | 228 ++++++ include/linux/mfd/imanager.h | 221 ++++++ 20 files changed, 3819 insertions(+) create mode 100644 drivers/gpio/gpio-imanager.c create mode 100644 drivers/hwmon/imanager-hwmon.c create mode 100644 drivers/i2c/busses/i2c-imanager.c create mode 100644 drivers/mfd/imanager-core.c create mode 100644 drivers/video/backlight/imanager_bl.c create mode 100644 drivers/watchdog/imanager_wdt.c create mode 100644 include/linux/mfd/imanager-ec.h create mode 100644 include/linux/mfd/imanager.h -- 2.10.1
[toc] | [next] | [standalone]
| From | Richard Vidal-Dorsch <richard.dorsch@gmail.com> |
|---|---|
| Date | 2016-11-02 09:40 +0100 |
| Subject | [PATCH v4 6/6] Add Advantech iManager Watchdog driver |
| Message-ID | <syZfr-3TD-9@gated-at.bofh.it> |
| In reply to | #1513736 |
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/watchdog/Kconfig | 11 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/imanager_wdt.c | 303 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 315 insertions(+)
create mode 100644 drivers/watchdog/imanager_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index fdd3228..d6859da 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -912,6 +912,17 @@ config WAFER_WDT
To compile this driver as a module, choose M here: the
module will be called wafer5823wdt.
+config IMANAGER_WDT
+ tristate "Advantech iManager Watchdog"
+ depends on MFD_IMANAGER
+ select WATCHDOG_CORE
+ help
+ Support for Advantech iManager watchdog on some Advantech
+ SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager_wdt.
+
config I6300ESB_WDT
tristate "Intel 6300ESB Timer/Watchdog"
depends on PCI
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index caa9f4a..eb7fccf 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -117,6 +117,7 @@ endif
obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
obj-$(CONFIG_IT87_WDT) += it87_wdt.o
obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
diff --git a/drivers/watchdog/imanager_wdt.c b/drivers/watchdog/imanager_wdt.c
new file mode 100644
index 0000000..53b409e
--- /dev/null
+++ b/drivers/watchdog/imanager_wdt.c
@@ -0,0 +1,303 @@
+/*
+ * Advantech iManager Watchdog driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define WDT_DEFAULT_TIMEOUT 30 /* seconds */
+#define WDT_FREQ 10 /* Hz */
+
+struct imanager_wdt_data {
+ struct imanager_device_data *imgr;
+ struct watchdog_device wdt;
+ ulong last_updated;
+ uint timeout;
+};
+
+static uint timeout = WDT_DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1 <= timeout <= 65534, default="
+ __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ".");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum wdt_ctrl {
+ START = 1, STOP, RESET, GET_TIMEOUT, SET_TIMEOUT, STOPBOOT = 8
+};
+
+enum imanager_wdt_event {
+ WDT_EVT_NONE,
+ WDT_EVT_DELAY,
+ WDT_EVT_PWRBTN,
+ WDT_EVT_NMI,
+ WDT_EVT_RESET,
+ WDT_EVT_WDPIN,
+ WDT_EVT_SCI,
+};
+
+struct event_delay {
+ u16 delay,
+ pwrbtn,
+ nmi,
+ reset,
+ wdpin,
+ sci,
+ dummy;
+} __attribute__((__packed__));
+
+static int imanager_wdt_ctrl(struct imanager_ec_data *ec, int ctrl,
+ int event_type, uint timeout)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(0, 0, ctrl, NULL)
+ };
+ u8 *fevent = &msg.u.data[0];
+ struct event_delay *event = (struct event_delay *)&msg.u.data[1];
+ int val;
+
+ if (ctrl == SET_TIMEOUT) {
+ memset(event, 0xff, sizeof(*event));
+ msg.wlen = sizeof(*event);
+ *fevent = 0;
+ val = (!timeout) ? 0xffff : cpu_to_be16(timeout * WDT_FREQ);
+
+ switch (event_type) {
+ case WDT_EVT_DELAY:
+ event->delay = val;
+ break;
+ case WDT_EVT_PWRBTN:
+ event->pwrbtn = val;
+ break;
+ case WDT_EVT_NMI:
+ event->nmi = val;
+ break;
+ case WDT_EVT_RESET:
+ event->reset = val;
+ break;
+ case WDT_EVT_WDPIN:
+ event->wdpin = val;
+ break;
+ case WDT_EVT_SCI:
+ event->sci = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return imanager_write(ec, EC_CMD_WDT_CTRL, &msg);
+}
+
+static inline int imanager_wdt_disable_all(struct imanager_wdt_data *data)
+{
+ struct imanager_ec_data *ec = &data->imgr->ec;
+
+ return (imanager_wdt_ctrl(ec, STOP, WDT_EVT_NONE, 0) ||
+ imanager_wdt_ctrl(ec, STOPBOOT, WDT_EVT_NONE, 0));
+}
+
+static int imanager_wdt_set(struct imanager_wdt_data *data, uint timeout)
+{
+ struct imanager_ec_data *ec = &data->imgr->ec;
+ int ret;
+
+ if (time_before(jiffies, data->last_updated + HZ + HZ / 2))
+ return 0;
+
+ if (data->timeout == timeout)
+ return 0;
+
+ ret = imanager_wdt_ctrl(ec, SET_TIMEOUT, WDT_EVT_PWRBTN, timeout);
+ if (ret < 0)
+ return ret;
+
+ data->timeout = timeout;
+ data->last_updated = jiffies;
+
+ return 0;
+}
+
+static int imanager_wdt_set_timeout(struct watchdog_device *wdt, uint timeout)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_set(data, timeout);
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static uint imanager_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ uint timeleft = 0;
+ ulong time_diff = ((jiffies - data->last_updated) / HZ);
+
+ if (data->last_updated && (data->timeout > time_diff))
+ timeleft = data->timeout - time_diff;
+
+ return timeleft;
+}
+
+static int imanager_wdt_start(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_ctrl(&imgr->ec, START, WDT_EVT_NONE, 0);
+ data->last_updated = jiffies;
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static int imanager_wdt_stop(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+ data->last_updated = 0;
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static int imanager_wdt_ping(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_ctrl(&imgr->ec, RESET, WDT_EVT_NONE, 0);
+ data->last_updated = jiffies;
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static const struct watchdog_info imanager_wdt_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "imanager-wdt",
+};
+
+static const struct watchdog_ops imanager_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = imanager_wdt_start,
+ .stop = imanager_wdt_stop,
+ .ping = imanager_wdt_ping,
+ .set_timeout = imanager_wdt_set_timeout,
+ .get_timeleft = imanager_wdt_get_timeleft,
+};
+
+static int imanager_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_wdt_data *data;
+ struct watchdog_device *wdt_dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+
+ wdt_dev = &data->wdt;
+ wdt_dev->info = &imanager_wdt_info;
+ wdt_dev->ops = &imanager_wdt_ops;
+ wdt_dev->timeout = WDT_DEFAULT_TIMEOUT;
+ wdt_dev->min_timeout = 1;
+ wdt_dev->max_timeout = 0xfffe;
+
+ watchdog_set_nowayout(wdt_dev, nowayout);
+ watchdog_set_drvdata(wdt_dev, data);
+
+ ret = watchdog_register_device(wdt_dev);
+ if (ret) {
+ dev_err(dev, "Could not register watchdog device\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, data);
+
+ imanager_wdt_disable_all(data);
+ imanager_wdt_set_timeout(wdt_dev, timeout);
+
+ dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
+
+ return 0;
+}
+
+static int imanager_wdt_remove(struct platform_device *pdev)
+{
+ struct imanager_wdt_data *data = platform_get_drvdata(pdev);
+
+ if (!nowayout)
+ imanager_wdt_disable_all(data);
+
+ watchdog_unregister_device(&data->wdt);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static void imanager_wdt_shutdown(struct platform_device *pdev)
+{
+ struct imanager_device_data *imgr = dev_get_drvdata(pdev->dev.parent);
+
+ mutex_lock(&imgr->lock);
+ imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+ mutex_unlock(&imgr->lock);
+}
+
+static struct platform_driver imanager_wdt_driver = {
+ .driver = {
+ .name = "imanager-wdt",
+ },
+ .probe = imanager_wdt_probe,
+ .remove = imanager_wdt_remove,
+ .shutdown = imanager_wdt_shutdown,
+};
+
+module_platform_driver(imanager_wdt_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Watchdog Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-wdt");
--
2.10.1
[toc] | [prev] | [next] | [standalone]
| From | Richard Vidal-Dorsch <richard.dorsch@gmail.com> |
|---|---|
| Date | 2016-11-02 09:50 +0100 |
| Subject | [PATCH v4 5/6] Add Advantech iManager Backlight driver |
| Message-ID | <syZp7-3X5-5@gated-at.bofh.it> |
| In reply to | #1513736 |
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/video/backlight/Kconfig | 8 ++
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/imanager_bl.c | 210 ++++++++++++++++++++++++++++++++++
3 files changed, 219 insertions(+)
create mode 100644 drivers/video/backlight/imanager_bl.c
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5ffa4b4..2dac696 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -251,6 +251,14 @@ config BACKLIGHT_HP700
If you have an HP Jornada 700 series,
say Y to include backlight control driver.
+config BACKLIGHT_IMANAGER
+ tristate "Advantech iManager Backlight/Brightness"
+ depends on MFD_IMANAGER
+ help
+ This enables support for Advantech iManager Backlight and
+ Brightness control of some Advantech SOM, MIO, AIMB, and
+ PCM modules/boards.
+
config BACKLIGHT_CARILLO_RANCH
tristate "Intel Carillo Ranch Backlight Driver"
depends on LCD_CLASS_DEVICE && PCI && X86 && FB_LE80578
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 16ec534..713b406 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_BACKLIGHT_GENERIC) += generic_bl.o
obj-$(CONFIG_BACKLIGHT_GPIO) += gpio_backlight.o
obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o
obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o
+obj-$(CONFIG_BACKLIGHT_IMANAGER) += imanager_bl.o
obj-$(CONFIG_BACKLIGHT_IPAQ_MICRO) += ipaq_micro_bl.o
obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o
obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o
diff --git a/drivers/video/backlight/imanager_bl.c b/drivers/video/backlight/imanager_bl.c
new file mode 100644
index 0000000..d8b4e3d
--- /dev/null
+++ b/drivers/video/backlight/imanager_bl.c
@@ -0,0 +1,210 @@
+/*
+ * Advantech iManager Backlight driver
+ * Partially derived from wm831x_bl
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/backlight.h>
+#include <linux/device.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mfd/imanager.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define BL_MAX_PWM 100
+
+enum backlight_units { BL_UNIT_1 = 0, BL_UNIT_2 };
+
+static bool polarity = PWM_POLARITY_NORMAL;
+module_param(polarity, bool, 0444);
+MODULE_PARM_DESC(polarity, "Select backlight polarity (inverted := 1)");
+
+static ushort unit = BL_UNIT_1;
+module_param(unit, ushort, 0444);
+MODULE_PARM_DESC(unit, "Select backlight control unit [0, 1] (defaults to 0)");
+
+struct imanager_backlight_data {
+ struct imanager_device_data *imgr;
+};
+
+struct brightness_level {
+ uint value : 7, /* Brightness Value - LSB [6..0] */
+ enable : 1; /* Brightness Enable - MSB [7] */
+};
+
+struct backlight_ctrl {
+ uint enable : 1, /* Backlight Control Enable - LSB [0] */
+ pwmpol : 1, /* PWM Polarity - bit [1] */
+ blpol : 1, /* Backlight Polarity - bit [2] */
+ dnc : 5; /* Don't care - bit [7..3] */
+};
+
+static int imanager_bl_enable(struct imanager_ec_data *ec, int unit)
+{
+ u8 val8;
+ struct brightness_level *ctrl = (struct brightness_level *)&val8;
+ u8 devid = ec->bl.attr[unit]->did;
+ u8 bl_unit = ec->bl.brightness[unit];
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, bl_unit, &val8, sizeof(val8));
+ if (ret < 0)
+ return ret;
+
+ ctrl->enable = 1;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, devid, &val8, sizeof(val8));
+}
+
+static int imanager_bl_set_polarity(struct imanager_ec_data *ec, uint polarity)
+{
+ u8 val8;
+ struct backlight_ctrl *ctrl = (struct backlight_ctrl *)&val8;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_BACKLIGHT_CTRL,
+ &val8, sizeof(val8));
+ if (ret < 0)
+ return ret;
+
+ ctrl->blpol = polarity ? 1 : 0;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_BACKLIGHT_CTRL,
+ &val8, sizeof(val8));
+}
+
+static int imanager_bl_get_brightness(struct backlight_device *bd)
+{
+ struct imanager_backlight_data *data = bl_get_data(bd);
+ struct imanager_device_data *imgr = data->imgr;
+ u8 devid = imgr->ec.bl.attr[unit]->did;
+ int pwm;
+
+ mutex_lock(&imgr->lock);
+
+ pwm = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, devid);
+ if (pwm < 0) {
+ dev_warn(&bd->dev, "Failed while reading PWM\n");
+ pwm = 0;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return polarity ? BL_MAX_PWM - pwm : pwm;
+}
+
+static int imanager_bl_set_brightness(struct backlight_device *bd)
+{
+ struct imanager_backlight_data *data = bl_get_data(bd);
+ struct imanager_device_data *imgr = data->imgr;
+ u8 devid = imgr->ec.bl.attr[unit]->did;
+ u8 brightness = bd->props.brightness;
+ int ret;
+
+ if (bd->props.power != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ if (bd->props.state & BL_CORE_SUSPENDED)
+ brightness = 0;
+
+ /* invert brightness if polarity is set */
+ brightness = polarity ? BL_MAX_PWM - brightness : brightness;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_write8(&imgr->ec, EC_CMD_HWP_WR, devid, brightness);
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static const struct backlight_ops imanager_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .get_brightness = imanager_bl_get_brightness,
+ .update_status = imanager_bl_set_brightness,
+};
+
+static int imanager_bl_init(struct device *dev,
+ struct imanager_backlight_data *data)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+ int ret;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = BL_MAX_PWM;
+ bd = devm_backlight_device_register(dev, "imanager-backlight", dev,
+ data, &imanager_bl_ops, &props);
+
+ if (IS_ERR(bd)) {
+ dev_err(dev, "Unable to register backlight device\n");
+ return PTR_ERR(bd);
+ }
+
+ bd->props.brightness = imanager_bl_get_brightness(bd);
+ bd->props.max_brightness = BL_MAX_PWM;
+ bd->props.power = FB_BLANK_UNBLANK;
+
+ backlight_update_status(bd);
+
+ ret = imanager_bl_enable(&data->imgr->ec, unit);
+ if (ret < 0)
+ dev_warn(dev, "Could not enable backlight control\n");
+
+ ret = imanager_bl_set_polarity(&data->imgr->ec, polarity);
+ if (ret < 0)
+ dev_warn(dev, "Could not set backlight polarity\n");
+
+ return 0;
+}
+
+static int imanager_bl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_backlight_data *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+
+ ret = imanager_bl_init(dev, data);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static struct platform_driver imanager_backlight_driver = {
+ .driver = {
+ .name = "imanager-backlight",
+ },
+ .probe = imanager_bl_probe,
+};
+
+module_platform_driver(imanager_backlight_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Backlight driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-backlight");
--
2.10.1
[toc] | [prev] | [next] | [standalone]
| From | Richard Vidal-Dorsch <richard.dorsch@gmail.com> |
|---|---|
| Date | 2016-11-02 09:50 +0100 |
| Subject | [PATCH v4 2/6] Add Advantech iManager GPIO driver |
| Message-ID | <syZp7-3X5-15@gated-at.bofh.it> |
| In reply to | #1513736 |
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/gpio/Kconfig | 10 +++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-imanager.c | 155 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 166 insertions(+)
create mode 100644 drivers/gpio/gpio-imanager.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d011cb8..52e371f 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -884,6 +884,16 @@ config HTC_EGPIO
several HTC phones. It provides basic support for input
pins, output pins, and irqs.
+config GPIO_IMANAGER
+ tristate "Advantech iManager GPIO"
+ depends on MFD_IMANAGER
+ help
+ This enables support for the iManager GPIO interface on some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-imanager.
+
config GPIO_JANZ_TTL
tristate "Janz VMOD-TTL Digital IO Module"
depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index ab28a2d..e1250eb 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o
obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
+obj-$(CONFIG_GPIO_IMANAGER) += gpio-imanager.o
obj-$(CONFIG_GPIO_IOP) += gpio-iop.o
obj-$(CONFIG_GPIO_IT87) += gpio-it87.o
obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o
diff --git a/drivers/gpio/gpio-imanager.c b/drivers/gpio/gpio-imanager.c
new file mode 100644
index 0000000..f60592b
--- /dev/null
+++ b/drivers/gpio/gpio-imanager.c
@@ -0,0 +1,155 @@
+/*
+ * Advantech iManager GPIO driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define EC_GPIOF_DIR_OUT BIT(6)
+#define EC_GPIOF_DIR_IN BIT(7)
+
+struct imanager_gpio_data {
+ struct imanager_device_data *imgr;
+ struct gpio_chip chip;
+};
+
+static int imanager_gpio_direction_in(struct gpio_chip *chip, uint offset)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+ mutex_lock(&imgr->lock);
+ imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
+ EC_GPIOF_DIR_IN);
+ mutex_unlock(&imgr->lock);
+
+ return 0;
+}
+
+static int
+imanager_gpio_direction_out(struct gpio_chip *chip, uint offset, int val)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+ mutex_lock(&imgr->lock);
+ imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
+ EC_GPIOF_DIR_OUT);
+ mutex_unlock(&imgr->lock);
+
+ return 0;
+}
+
+static int imanager_gpio_get_direction(struct gpio_chip *chip, uint offset)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_read8(&imgr->ec, EC_CMD_GPIO_DIR_RD, attr->did);
+ mutex_unlock(&imgr->lock);
+
+ return ret & EC_GPIOF_DIR_IN ? GPIOF_DIR_IN : GPIOF_DIR_OUT;
+}
+
+static int imanager_gpio_get(struct gpio_chip *chip, uint offset)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, attr->did);
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static void imanager_gpio_set(struct gpio_chip *chip, uint offset, int val)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+ mutex_lock(&imgr->lock);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, attr->did, val);
+ mutex_unlock(&imgr->lock);
+}
+
+static int imanager_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_gpio_data *gpio;
+ struct gpio_chip *chip;
+ int ret;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->imgr = imgr;
+
+ platform_set_drvdata(pdev, gpio);
+
+ chip = &gpio->chip;
+
+ chip->owner = THIS_MODULE;
+ chip->parent = dev;
+ chip->label = "gpio-imanager";
+ chip->base = -1;
+ chip->ngpio = imgr->ec.gpio.num;
+ chip->get = imanager_gpio_get;
+ chip->set = imanager_gpio_set;
+ chip->direction_input = imanager_gpio_direction_in;
+ chip->direction_output = imanager_gpio_direction_out;
+ chip->get_direction = imanager_gpio_get_direction;
+ if (!chip->ngpio) {
+ dev_err(dev, "No GPIO pins detected\n");
+ return -ENODEV;
+ }
+
+ ret = devm_gpiochip_add_data(dev, chip, gpio);
+ if (ret < 0) {
+ dev_err(dev, "Could not register GPIO chip\n");
+ return ret;
+ }
+
+ dev_info(dev, "GPIO initialized with %d pins\n", chip->ngpio);
+
+ return 0;
+}
+
+static struct platform_driver imanager_gpio_driver = {
+ .driver = {
+ .name = "imanager-gpio",
+ },
+ .probe = imanager_gpio_probe,
+};
+
+module_platform_driver(imanager_gpio_driver);
+
+MODULE_DESCRIPTION("Advantech iManager GPIO Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-gpio");
--
2.10.1
[toc] | [prev] | [next] | [standalone]
| From | Linus Walleij <linus.walleij@linaro.org> |
|---|---|
| Date | 2016-11-05 09:40 +0100 |
| Subject | Re: [PATCH v4 2/6] Add Advantech iManager GPIO driver |
| Message-ID | <sA4G5-5zz-3@gated-at.bofh.it> |
| In reply to | #1513745 |
On Wed, Nov 2, 2016 at 9:37 AM, Richard Vidal-Dorsch
<richard.dorsch@gmail.com> wrote:
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> +#include <linux/device.h>
> +#include <linux/gpio.h>
#include <linux/gpio/driver.h>
should be enough.
> +#include <linux/init.h>
> +#include <linux/mfd/imanager.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +
> +#define EC_GPIOF_DIR_OUT BIT(6)
> +#define EC_GPIOF_DIR_IN BIT(7)
> +
> +struct imanager_gpio_data {
> + struct imanager_device_data *imgr;
> + struct gpio_chip chip;
> +};
Maybe some kerneldoc for this. Not necessary though since its sort of
self-explanatory.
> +static int imanager_gpio_direction_in(struct gpio_chip *chip, uint offset)
> +{
> + struct imanager_gpio_data *data = gpiochip_get_data(chip);
> + struct imanager_device_data *imgr = data->imgr;
> + struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
> +
> + mutex_lock(&imgr->lock);
> + imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
> + EC_GPIOF_DIR_IN);
> + mutex_unlock(&imgr->lock);
It kind of looks like it would be smarter if the imanager_write8() was
taking and releasing the lock so you don't have to do it everywhere.
> +static int imanager_gpio_get_direction(struct gpio_chip *chip, uint offset)
> +{
> + struct imanager_gpio_data *data = gpiochip_get_data(chip);
> + struct imanager_device_data *imgr = data->imgr;
> + struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
> + int ret;
> +
> + mutex_lock(&imgr->lock);
> + ret = imanager_read8(&imgr->ec, EC_CMD_GPIO_DIR_RD, attr->did);
> + mutex_unlock(&imgr->lock);
> +
> + return ret & EC_GPIOF_DIR_IN ? GPIOF_DIR_IN : GPIOF_DIR_OUT;
Don't use GPIOF* flags, those are for consumers. Just return 0/1.
> +static int imanager_gpio_get(struct gpio_chip *chip, uint offset)
> +{
> + struct imanager_gpio_data *data = gpiochip_get_data(chip);
> + struct imanager_device_data *imgr = data->imgr;
> + struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
> + int ret;
> +
> + mutex_lock(&imgr->lock);
> + ret = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, attr->did);
> + mutex_unlock(&imgr->lock);
> +
> + return ret;
> +}
Can the read function return an error code? In that case it should be checked
everywhere.
Also be sure to clamp ret like this:
return !!ret;
Apart from this it looks good.
Yours,
Linus Walleij
[toc] | [prev] | [next] | [standalone]
| From | Richard Vidal-Dorsch <richard.dorsch@gmail.com> |
|---|---|
| Date | 2016-11-02 09:50 +0100 |
| Subject | [PATCH v4 4/6] Add Advantech iManager I2C driver |
| Message-ID | <syZp7-3X5-13@gated-at.bofh.it> |
| In reply to | #1513736 |
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-imanager.c | 461 ++++++++++++++++++++++++++++++++++++++
3 files changed, 472 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-imanager.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d252276..7d1ecb4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -42,6 +42,16 @@ config I2C_ALI15X3
This driver can also be built as a module. If so, the module
will be called i2c-ali15x3.
+config I2C_IMANAGER
+ tristate "Advantech iManager I2C Interface"
+ depends on MFD_IMANAGER
+ help
+ This enables support for Advantech iManager I2C of some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-imanager.
+
config I2C_AMD756
tristate "AMD 756/766/768/8111 and nVidia nForce"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 29764cc..d9ff210 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_HIX5HD2) += i2c-hix5hd2.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
+obj-$(CONFIG_I2C_IMANAGER) += i2c-imanager.o
obj-$(CONFIG_I2C_IMG) += i2c-img-scb.o
obj-$(CONFIG_I2C_IMX) += i2c-imx.o
obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
diff --git a/drivers/i2c/busses/i2c-imanager.c b/drivers/i2c/busses/i2c-imanager.c
new file mode 100644
index 0000000..15c496d
--- /dev/null
+++ b/drivers/i2c/busses/i2c-imanager.c
@@ -0,0 +1,461 @@
+/*
+ * Advantech iManager SMBus bus driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define I2C_SMBUS_BLOCK_SIZE 32UL
+#define I2C_MAX_READ_SIZE I2C_SMBUS_BLOCK_SIZE
+#define I2C_MAX_WRITE_SIZE (I2C_SMBUS_BLOCK_SIZE - 1)
+
+#define EC_HWRAM_OFFSET_STATUS 0UL
+
+#define I2C_ERR_PROTO 0x19UL
+#define I2C_ERR_TIMEOUT 0x18UL
+#define I2C_ERR_ACCESS 0x17UL
+#define I2C_ERR_UNKNOWN 0x13UL
+#define I2C_ERR_ADDR_NACK 0x10UL
+
+#define SMBUS_FREQ_50KHZ 0x0100
+#define SMBUS_FREQ_100KHZ 0x0200
+#define SMBUS_FREQ_400KHZ 0x0300
+
+#define imanager_i2c_wr_combined(ec, message) \
+ imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_WR)
+
+#define imanager_i2c_rw_combined(ec, message) \
+ imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_RW)
+
+struct ec_i2c_status {
+ u32 error : 7;
+ u32 complete : 1;
+};
+
+struct adapter_info {
+ struct i2c_adapter adapter;
+ int smb_devid;
+};
+
+struct imanager_i2c_data {
+ struct device *dev;
+ struct imanager_device_data *imgr;
+ struct adapter_info adap_info[EC_MAX_SMB_NUM];
+ int nadap;
+};
+
+static int imanager_i2c_eval_status(u8 status)
+{
+ struct ec_i2c_status *_status = (struct ec_i2c_status *)&status;
+
+ switch (_status->error) {
+ case 0:
+ return 0;
+ case I2C_ERR_ADDR_NACK:
+ return -ENXIO;
+ case I2C_ERR_ACCESS:
+ return -EACCES;
+ case I2C_ERR_UNKNOWN:
+ return -EAGAIN;
+ case I2C_ERR_TIMEOUT:
+ return -ETIME;
+ case I2C_ERR_PROTO:
+ return -EPROTO;
+ }
+
+ return -EIO;
+}
+
+static int imanager_i2c_wait_proc_complete(struct imanager_ec_data *ec)
+{
+ int ret, i;
+ u8 val;
+
+ for (i = 0; i < EC_MAX_RETRY; i++) {
+ ret = imanager_read_ram(ec, EC_RAM_HW, EC_HWRAM_OFFSET_STATUS,
+ &val, sizeof(val));
+ if (ret < 0)
+ return ret;
+
+ if (!val)
+ return 0;
+
+ usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+ }
+
+ return -ETIME;
+}
+
+static int imanager_i2c_block_wr_rw_combined(struct imanager_ec_data *ec,
+ struct imanager_ec_message *msg,
+ unsigned int protocol)
+{
+ int ret;
+
+ ret = imanager_i2c_wait_proc_complete(ec);
+ if (ret)
+ return ret;
+
+ ret = imanager_write(ec, protocol, msg);
+ if (ret)
+ return imanager_i2c_eval_status(ret);
+
+ if (msg->rlen) {
+ if (msg->rlen == 1)
+ return msg->u.data[0];
+ else if (msg->rlen == 2)
+ return (msg->u.data[1] << 8) | msg->u.data[0];
+ else
+ return msg->rlen;
+ }
+
+ return 0;
+}
+
+static inline int
+imanager_i2c_write_freq(struct imanager_ec_data *ec, int did, int freq)
+{
+ return imanager_write16(ec, EC_CMD_SMB_FREQ_WR, did, freq);
+}
+
+static inline int eval_read_len(int len)
+{
+ return len && len <= I2C_MAX_READ_SIZE ? len : I2C_MAX_READ_SIZE;
+}
+
+static inline int
+imanager_i2c_read_block(struct imanager_ec_data *ec,
+ struct imanager_ec_message *msg, u8 *buf)
+{
+ int ret;
+
+ ret = imanager_i2c_wr_combined(ec, msg);
+ if (ret < 0)
+ return ret;
+
+ buf[0] = ret;
+ memcpy(&buf[1], msg->u.data, ret);
+
+ return 0;
+}
+
+static inline int
+imanager_i2c_write_block(struct imanager_ec_data *ec,
+ struct imanager_ec_message *msg, u8 *buf)
+{
+ if (!buf[0] || (buf[0] > I2C_MAX_WRITE_SIZE))
+ return -EINVAL;
+
+ memcpy(&msg->u.data[EC_MSG_HDR_SIZE], &buf[1], buf[0]);
+
+ return imanager_i2c_wr_combined(ec, msg);
+}
+
+static s32 imanager_i2c_xfer(struct i2c_adapter *adap, u16 addr, ushort flags,
+ char read_write, u8 command, int size,
+ union i2c_smbus_data *smb_data)
+{
+ struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct device *dev = data->dev;
+ int smb_devid = *(int *)adap->algo_data;
+ int val, ret = 0;
+ u16 addr16 = addr << 1; /* convert to 8-bit i2c slave address */
+ u8 *buf = smb_data->block;
+ struct imanager_ec_message msg = {
+ .rlen = 0,
+ .wlen = EC_MSG_HDR_SIZE,
+ .param = smb_devid,
+ .u = {
+ .smb.hdr = {
+ .addr_low = addr16 & 0x00ff,
+ .addr_high = addr16 >> 8,
+ .rlen = 0,
+ .wlen = 0,
+ },
+ },
+ };
+ struct imanager_ec_smb_message *smb = &msg.u.smb;
+
+ mutex_lock(&imgr->lock);
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ msg.rlen = 0;
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 1;
+ ret = imanager_i2c_wr_combined(ec, &msg);
+ break;
+ case I2C_SMBUS_BYTE:
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ smb->hdr.rlen = 1;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ if (val < 0)
+ ret = val;
+ } else {
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ msg.rlen = 1;
+ smb->hdr.rlen = 1;
+ smb->hdr.wlen = 0;
+ val = imanager_i2c_rw_combined(ec, &msg);
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->byte = val;
+ break;
+ }
+ case I2C_SMBUS_BYTE_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += 1;
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 2;
+ smb->hdr.cmd = command;
+ smb->data[0] = smb_data->byte;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ } else {
+ msg.rlen = 1;
+ smb->hdr.rlen = 1;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ }
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->byte = val;
+ break;
+ case I2C_SMBUS_WORD_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += 2;
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 3;
+ smb->hdr.cmd = command;
+ smb->data[0] = smb_data->word & 0x00ff;
+ smb->data[1] = smb_data->word >> 8;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ } else {
+ msg.rlen = 2;
+ smb->hdr.rlen = 2;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ }
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->word = val;
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += buf[0];
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 1 + buf[0];
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_write_block(ec, &msg, buf);
+ } else {
+ msg.rlen = eval_read_len(buf[0]);
+ smb->hdr.rlen = msg.rlen;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_read_block(ec, &msg, buf);
+ }
+ break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += buf[0];
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 1 + buf[0];
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_write_block(ec, &msg, buf);
+ } else {
+ msg.rlen = eval_read_len(buf[0]);
+ smb->hdr.rlen = msg.rlen;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_read_block(ec, &msg, buf);
+ }
+ break;
+ default:
+ dev_err(dev, "Unsupported transaction %d\n", size);
+ ret = -EOPNOTSUPP;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static u32 imanager_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
+}
+
+static const struct i2c_algorithm imanager_i2c_algorithm = {
+ .smbus_xfer = imanager_i2c_xfer,
+ .functionality = imanager_i2c_func,
+};
+
+static const struct i2c_adapter imanager_i2c_adapters[] = {
+ [SMB_EEP] = {
+ .owner = THIS_MODULE,
+ .name = "iManager SMB EEP adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+ [I2C_OEM] = {
+ .owner = THIS_MODULE,
+ .name = "iManager I2C OEM adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+ [SMB_1] = {
+ .owner = THIS_MODULE,
+ .name = "iManager SMB 1 adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+ [SMB_PECI] = {
+ .owner = THIS_MODULE,
+ .name = "iManager SMB PECI adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+};
+
+static int
+imanager_i2c_add_bus(struct imanager_i2c_data *i2c, struct adapter_info *info,
+ const struct i2c_adapter *adap, int did, int freq)
+{
+ int ret;
+
+ info->adapter = *adap;
+ info->adapter.dev.parent = i2c->dev;
+ info->smb_devid = did;
+ info->adapter.algo_data = &info->smb_devid;
+ i2c_set_adapdata(&info->adapter, i2c);
+
+ ret = i2c_add_adapter(&info->adapter);
+ if (ret) {
+ dev_warn(i2c->dev, "Failed to add %s\n", info->adapter.name);
+ return ret;
+ }
+
+ ret = imanager_i2c_write_freq(&i2c->imgr->ec, did, freq);
+ if (ret < 0)
+ dev_warn(i2c->dev, "Failed to set bus frequency of %s\n",
+ info->adapter.name);
+
+ return 0;
+}
+
+static int imanager_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_device_attribute **attr = imgr->ec.i2c.attr;
+ struct imanager_i2c_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+ data->dev = dev;
+
+ if (attr[SMB_EEP])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[SMB_EEP],
+ attr[SMB_EEP]->did, SMBUS_FREQ_100KHZ);
+
+ if (attr[I2C_OEM])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[I2C_OEM],
+ attr[I2C_OEM]->did, SMBUS_FREQ_400KHZ);
+
+ if (attr[SMB_1])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[SMB_1],
+ attr[SMB_1]->did, SMBUS_FREQ_100KHZ);
+
+ if (attr[SMB_PECI])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[SMB_PECI],
+ attr[SMB_PECI]->did, SMBUS_FREQ_100KHZ);
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int imanager_i2c_remove(struct platform_device *pdev)
+{
+ struct imanager_i2c_data *i2c = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ for (i = 0; i < i2c->nadap; i++) {
+ i2c_del_adapter(&i2c->adap_info[i].adapter);
+ i2c_set_adapdata(&i2c->adap_info[i].adapter, NULL);
+ }
+
+ return 0;
+}
+
+static struct platform_driver imanager_i2c_driver = {
+ .driver = {
+ .name = "imanager-smbus",
+ },
+ .probe = imanager_i2c_probe,
+ .remove = imanager_i2c_remove,
+};
+
+module_platform_driver(imanager_i2c_driver);
+
+MODULE_DESCRIPTION("Advantech iManager SMBus Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-smbus");
--
2.10.1
[toc] | [prev] | [next] | [standalone]
| From | Lee Jones <lee.jones@linaro.org> |
|---|---|
| Date | 2016-11-03 09:40 +0100 |
| Subject | Re: [PATCH v4 1/6] Add Advantech iManager MFD core driver |
| Message-ID | <szlJ0-1tA-17@gated-at.bofh.it> |
| In reply to | #1513736 |
On Wed, 02 Nov 2016, Richard Vidal-Dorsch wrote: > This patch adds Advantech iManager Embedded Controller MFD core driver. > This mfd core dirver provides an interface for GPIO, I2C, HWmon, > Watchdog, and Backlight/Brightness control. > > Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com> > --- > drivers/mfd/Kconfig | 18 + > drivers/mfd/Makefile | 1 + > drivers/mfd/imanager-core.c | 941 ++++++++++++++++++++++++++++++++++++++++ > include/linux/mfd/imanager-ec.h | 228 ++++++++++ > include/linux/mfd/imanager.h | 221 ++++++++++ > 5 files changed, 1409 insertions(+) > create mode 100644 drivers/mfd/imanager-core.c > create mode 100644 include/linux/mfd/imanager-ec.h > create mode 100644 include/linux/mfd/imanager.h After a brief review, it is my belief that this looks like a platform driver, which should live in drivers/platform/. -- Lee Jones Linaro STMicroelectronics Landing Team Lead Linaro.org │ Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web