From 0b30d25d08ab2033fd6e7fced7fa0d77f9eaa7de Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 9 Apr 2015 15:47:26 +0100 Subject: [PATCH 01/59] mfd: dt-bindings: Provide human readable defines for LPC mode choosing ST's Low Power Controller can currently operate in two supported modes; Watchdog and Real Time Clock. These defines will aid engineers to easily identify the selected mode. Reviewed-by: Guenter Roeck Acked-by: Maxime Coquelin Signed-off-by: Lee Jones --- include/dt-bindings/mfd/st-lpc.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 include/dt-bindings/mfd/st-lpc.h diff --git a/include/dt-bindings/mfd/st-lpc.h b/include/dt-bindings/mfd/st-lpc.h new file mode 100644 index 000000000000..e3e6c75d8822 --- /dev/null +++ b/include/dt-bindings/mfd/st-lpc.h @@ -0,0 +1,15 @@ +/* + * This header provides shared DT/Driver defines for ST's LPC device + * + * Copyright (C) 2014 STMicroelectronics -- All Rights Reserved + * + * Author: Lee Jones for STMicroelectronics + */ + +#ifndef __DT_BINDINGS_ST_LPC_H__ +#define __DT_BINDINGS_ST_LPC_H__ + +#define ST_LPC_MODE_RTC 0 +#define ST_LPC_MODE_WDT 1 + +#endif /* __DT_BINDINGS_ST_LPC_H__ */ From b0e6867411ea656124390653570718a90d7167f7 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 9 Apr 2015 15:47:30 +0100 Subject: [PATCH 02/59] watchdog: bindings: Provide ST bindings for ST's LPC Watchdog device On current ST platforms the LPC controls a number of functions including Watchdog and Real Time Clock. This patch provides the bindings used to configure LPC in Watchdog mode. Acked-by: Guenter Roeck Signed-off-by: Lee Jones --- .../bindings/watchdog/st_lpc_wdt.txt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/st_lpc_wdt.txt diff --git a/Documentation/devicetree/bindings/watchdog/st_lpc_wdt.txt b/Documentation/devicetree/bindings/watchdog/st_lpc_wdt.txt new file mode 100644 index 000000000000..388c88a01222 --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/st_lpc_wdt.txt @@ -0,0 +1,38 @@ +STMicroelectronics Low Power Controller (LPC) - Watchdog +======================================================== + +LPC currently supports Watchdog OR Real Time Clock functionality. + +[See: ../rtc/rtc-st-lpc.txt for RTC options] + +Required properties + +- compatible : Must be one of: "st,stih407-lpc" "st,stih416-lpc" + "st,stih415-lpc" "st,stid127-lpc" +- reg : LPC registers base address + size +- interrupts : LPC interrupt line number and associated flags +- clocks : Clock used by LPC device (See: ../clock/clock-bindings.txt) +- st,lpc-mode : The LPC can run either one of two modes ST_LPC_MODE_RTC [0] or + ST_LPC_MODE_WDT [1]. One (and only one) mode must be + selected. + +Required properties [watchdog mode] + +- st,syscfg : Phandle to syscfg node used to enable watchdog and configure + CPU reset type. +- timeout-sec : Watchdog timeout in seconds + +Optional properties [watchdog mode] + +- st,warm-reset : If present reset type will be 'warm' - if not it will be cold + +Example: + lpc@fde05000 { + compatible = "st,stih407-lpc"; + reg = <0xfde05000 0x1000>; + clocks = <&clk_s_d3_flexgen CLK_LPC_0>; + st,syscfg = <&syscfg_core>; + timeout-sec = <120>; + st,lpc-mode = ; + st,warm-reset; + }; From 49ae77905c56da6d73e8d4a73aa030718a096551 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 9 Apr 2015 15:47:31 +0100 Subject: [PATCH 03/59] watchdog: st_wdt: Add new driver for ST's LPC Watchdog Reviewed-by: Guenter Roeck Signed-off-by: David Paris Signed-off-by: Lee Jones --- drivers/watchdog/Kconfig | 12 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/st_lpc_wdt.c | 344 ++++++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 drivers/watchdog/st_lpc_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index e5e7c5505de7..262647bbc614 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -470,6 +470,18 @@ config SIRFSOC_WATCHDOG Support for CSR SiRFprimaII and SiRFatlasVI watchdog. When the watchdog triggers the system will be reset. +config ST_LPC_WATCHDOG + tristate "STMicroelectronics LPC Watchdog" + depends on ARCH_STI + depends on OF + select WATCHDOG_CORE + help + Say Y here to include STMicroelectronics Low Power Controller + (LPC) based Watchdog timer support. + + To compile this driver as a module, choose M here: the + module will be called st_lpc_wdt. + config TEGRA_WATCHDOG tristate "Tegra watchdog" depends on (ARCH_TEGRA || COMPILE_TEST) && HAS_IOMEM diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5c19294d1c30..d98768c7d928 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o obj-$(CONFIG_BCM2835_WDT) += bcm2835_wdt.o obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o +obj-$(CONFIG_ST_LPC_WATCHDOG) += st_lpc_wdt.o obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o diff --git a/drivers/watchdog/st_lpc_wdt.c b/drivers/watchdog/st_lpc_wdt.c new file mode 100644 index 000000000000..f32be155212a --- /dev/null +++ b/drivers/watchdog/st_lpc_wdt.c @@ -0,0 +1,344 @@ +/* + * ST's LPC Watchdog + * + * Copyright (C) 2014 STMicroelectronics -- All Rights Reserved + * + * Author: David Paris for STMicroelectronics + * Lee Jones for STMicroelectronics + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Low Power Alarm */ +#define LPC_LPA_LSB_OFF 0x410 +#define LPC_LPA_START_OFF 0x418 + +/* LPC as WDT */ +#define LPC_WDT_OFF 0x510 + +static struct watchdog_device st_wdog_dev; + +struct st_wdog_syscfg { + unsigned int reset_type_reg; + unsigned int reset_type_mask; + unsigned int enable_reg; + unsigned int enable_mask; +}; + +struct st_wdog { + void __iomem *base; + struct device *dev; + struct regmap *regmap; + struct st_wdog_syscfg *syscfg; + struct clk *clk; + unsigned long clkrate; + bool warm_reset; +}; + +static struct st_wdog_syscfg stid127_syscfg = { + .reset_type_reg = 0x004, + .reset_type_mask = BIT(2), + .enable_reg = 0x000, + .enable_mask = BIT(2), +}; + +static struct st_wdog_syscfg stih415_syscfg = { + .reset_type_reg = 0x0B8, + .reset_type_mask = BIT(6), + .enable_reg = 0x0B4, + .enable_mask = BIT(7), +}; + +static struct st_wdog_syscfg stih416_syscfg = { + .reset_type_reg = 0x88C, + .reset_type_mask = BIT(6), + .enable_reg = 0x888, + .enable_mask = BIT(7), +}; + +static struct st_wdog_syscfg stih407_syscfg = { + .enable_reg = 0x204, + .enable_mask = BIT(19), +}; + +static const struct of_device_id st_wdog_match[] = { + { + .compatible = "st,stih407-lpc", + .data = &stih407_syscfg, + }, + { + .compatible = "st,stih416-lpc", + .data = &stih416_syscfg, + }, + { + .compatible = "st,stih415-lpc", + .data = &stih415_syscfg, + }, + { + .compatible = "st,stid127-lpc", + .data = &stid127_syscfg, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_wdog_match); + +static void st_wdog_setup(struct st_wdog *st_wdog, bool enable) +{ + /* Type of watchdog reset - 0: Cold 1: Warm */ + if (st_wdog->syscfg->reset_type_reg) + regmap_update_bits(st_wdog->regmap, + st_wdog->syscfg->reset_type_reg, + st_wdog->syscfg->reset_type_mask, + st_wdog->warm_reset); + + /* Mask/unmask watchdog reset */ + regmap_update_bits(st_wdog->regmap, + st_wdog->syscfg->enable_reg, + st_wdog->syscfg->enable_mask, + enable ? 0 : st_wdog->syscfg->enable_mask); +} + +static void st_wdog_load_timer(struct st_wdog *st_wdog, unsigned int timeout) +{ + unsigned long clkrate = st_wdog->clkrate; + + writel_relaxed(timeout * clkrate, st_wdog->base + LPC_LPA_LSB_OFF); + writel_relaxed(1, st_wdog->base + LPC_LPA_START_OFF); +} + +static int st_wdog_start(struct watchdog_device *wdd) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); + + writel_relaxed(1, st_wdog->base + LPC_WDT_OFF); + + return 0; +} + +static int st_wdog_stop(struct watchdog_device *wdd) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); + + writel_relaxed(0, st_wdog->base + LPC_WDT_OFF); + + return 0; +} + +static int st_wdog_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); + + wdd->timeout = timeout; + st_wdog_load_timer(st_wdog, timeout); + + return 0; +} + +static int st_wdog_keepalive(struct watchdog_device *wdd) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); + + st_wdog_load_timer(st_wdog, wdd->timeout); + + return 0; +} + +static const struct watchdog_info st_wdog_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "ST LPC WDT", +}; + +static const struct watchdog_ops st_wdog_ops = { + .owner = THIS_MODULE, + .start = st_wdog_start, + .stop = st_wdog_stop, + .ping = st_wdog_keepalive, + .set_timeout = st_wdog_set_timeout, +}; + +static struct watchdog_device st_wdog_dev = { + .info = &st_wdog_info, + .ops = &st_wdog_ops, +}; + +static int st_wdog_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *np = pdev->dev.of_node; + struct st_wdog *st_wdog; + struct regmap *regmap; + struct resource *res; + struct clk *clk; + void __iomem *base; + uint32_t mode; + int ret; + + ret = of_property_read_u32(np, "st,lpc-mode", &mode); + if (ret) { + dev_err(&pdev->dev, "An LPC mode must be provided\n"); + return -EINVAL; + } + + /* LPC can either run in RTC or WDT mode */ + if (mode != ST_LPC_MODE_WDT) + return -ENODEV; + + st_wdog = devm_kzalloc(&pdev->dev, sizeof(*st_wdog), GFP_KERNEL); + if (!st_wdog) + return -ENOMEM; + + match = of_match_device(st_wdog_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Couldn't match device\n"); + return -ENODEV; + } + st_wdog->syscfg = (struct st_wdog_syscfg *)match->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(regmap)) { + dev_err(&pdev->dev, "No syscfg phandle specified\n"); + return PTR_ERR(regmap); + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Unable to request clock\n"); + return PTR_ERR(clk); + } + + st_wdog->dev = &pdev->dev; + st_wdog->base = base; + st_wdog->clk = clk; + st_wdog->regmap = regmap; + st_wdog->warm_reset = of_property_read_bool(np, "st,warm_reset"); + st_wdog->clkrate = clk_get_rate(st_wdog->clk); + + if (!st_wdog->clkrate) { + dev_err(&pdev->dev, "Unable to fetch clock rate\n"); + return -EINVAL; + } + st_wdog_dev.max_timeout = 0xFFFFFFFF / st_wdog->clkrate; + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock\n"); + return ret; + } + + watchdog_set_drvdata(&st_wdog_dev, st_wdog); + watchdog_set_nowayout(&st_wdog_dev, WATCHDOG_NOWAYOUT); + + /* Init Watchdog timeout with value in DT */ + ret = watchdog_init_timeout(&st_wdog_dev, 0, &pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Unable to initialise watchdog timeout\n"); + clk_disable_unprepare(clk); + return ret; + } + + ret = watchdog_register_device(&st_wdog_dev); + if (ret) { + dev_err(&pdev->dev, "Unable to register watchdog\n"); + clk_disable_unprepare(clk); + return ret; + } + + st_wdog_setup(st_wdog, true); + + dev_info(&pdev->dev, "LPC Watchdog driver registered, reset type is %s", + st_wdog->warm_reset ? "warm" : "cold"); + + return ret; +} + +static int st_wdog_remove(struct platform_device *pdev) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); + + st_wdog_setup(st_wdog, false); + watchdog_unregister_device(&st_wdog_dev); + clk_disable_unprepare(st_wdog->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int st_wdog_suspend(struct device *dev) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); + + if (watchdog_active(&st_wdog_dev)) + st_wdog_stop(&st_wdog_dev); + + st_wdog_setup(st_wdog, false); + + clk_disable(st_wdog->clk); + + return 0; +} + +static int st_wdog_resume(struct device *dev) +{ + struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); + int ret; + + ret = clk_enable(st_wdog->clk); + if (ret) { + dev_err(dev, "Unable to re-enable clock\n"); + watchdog_unregister_device(&st_wdog_dev); + clk_unprepare(st_wdog->clk); + return ret; + } + + st_wdog_setup(st_wdog, true); + + if (watchdog_active(&st_wdog_dev)) { + st_wdog_load_timer(st_wdog, st_wdog_dev.timeout); + st_wdog_start(&st_wdog_dev); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(st_wdog_pm_ops, + st_wdog_suspend, + st_wdog_resume); + +static struct platform_driver st_wdog_driver = { + .driver = { + .name = "st-lpc-wdt", + .pm = &st_wdog_pm_ops, + .of_match_table = st_wdog_match, + }, + .probe = st_wdog_probe, + .remove = st_wdog_remove, +}; +module_platform_driver(st_wdog_driver); + +MODULE_AUTHOR("David Paris "); +MODULE_DESCRIPTION("ST LPC Watchdog Driver"); +MODULE_LICENSE("GPL"); From 5cb69745da35c372b3db001efbd3967b633ba0d1 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 9 Apr 2015 15:47:32 +0100 Subject: [PATCH 04/59] rtc: bindings: Provide ST bindings for ST's LPC RTC device On current ST platforms the LPC controls a number of functions including Watchdog and Real Time Clock. This patch provides the bindings used to configure LPC in RTC mode. Cc: Alessandro Zummo Signed-off-by: Lee Jones --- .../devicetree/bindings/rtc/rtc-st-lpc.txt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/rtc/rtc-st-lpc.txt diff --git a/Documentation/devicetree/bindings/rtc/rtc-st-lpc.txt b/Documentation/devicetree/bindings/rtc/rtc-st-lpc.txt new file mode 100644 index 000000000000..73407f502e4e --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/rtc-st-lpc.txt @@ -0,0 +1,25 @@ +STMicroelectronics Low Power Controller (LPC) - RTC +=================================================== + +LPC currently supports Watchdog OR Real Time Clock functionality. + +[See: ../watchdog/st_lpc_wdt.txt for Watchdog options] + +Required properties + +- compatible : Must be one of: "st,stih407-lpc" "st,stih416-lpc" + "st,stih415-lpc" "st,stid127-lpc" +- reg : LPC registers base address + size +- interrupts : LPC interrupt line number and associated flags +- clocks : Clock used by LPC device (See: ../clock/clock-bindings.txt) +- st,lpc-mode : The LPC can run either one of two modes ST_LPC_MODE_RTC [0] or + ST_LPC_MODE_WDT [1]. One (and only one) mode must be + selected. + +Example: + lpc@fde05000 { + compatible = "st,stih407-lpc"; + reg = <0xfde05000 0x1000>; + clocks = <&clk_s_d3_flexgen CLK_LPC_0>; + st,lpc-mode = ; + }; From b5b2bdfc2893910fdc02d21ec5c535635c896ff7 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 9 Apr 2015 15:47:33 +0100 Subject: [PATCH 05/59] rtc: st: Add new driver for ST's LPC RTC ST's Low Power Controller (LPC) controls two devices; watchdog and RTC. Only one of the devices can be used at any one time. This is enforced by the correlating MFD driver. This portion of the driver-set controls the Real Time Clock. Cc: Alessandro Zummo Signed-off-by: Lee Jones --- drivers/rtc/Kconfig | 11 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-st-lpc.c | 354 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 drivers/rtc/rtc-st-lpc.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 6149ae01e11f..8b8b332efaed 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1510,6 +1510,17 @@ config RTC_DRV_SIRFSOC Say "yes" here to support the real time clock on SiRF SOC chips. This driver can also be built as a module called rtc-sirfsoc. +config RTC_DRV_ST_LPC + tristate "STMicroelectronics LPC RTC" + depends on ARCH_STI + depends on OF + help + Say Y here to include STMicroelectronics Low Power Controller + (LPC) based RTC support. + + To compile this driver as a module, choose M here: the + module will be called rtc-st-lpc. + config RTC_DRV_MOXART tristate "MOXA ART RTC" depends on ARCH_MOXART || COMPILE_TEST diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index c31731c29762..411e630f36b9 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -153,4 +153,5 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o obj-$(CONFIG_RTC_DRV_SIRFSOC) += rtc-sirfsoc.o +obj-$(CONFIG_RTC_DRV_ST_LPC) += rtc-st-lpc.o obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o diff --git a/drivers/rtc/rtc-st-lpc.c b/drivers/rtc/rtc-st-lpc.c new file mode 100644 index 000000000000..3f9d0acb81c7 --- /dev/null +++ b/drivers/rtc/rtc-st-lpc.c @@ -0,0 +1,354 @@ +/* + * rtc-st-lpc.c - ST's LPC RTC, powered by the Low Power Timer + * + * Copyright (C) 2014 STMicroelectronics Limited + * + * Author: David Paris for STMicroelectronics + * Lee Jones for STMicroelectronics + * + * Based on the original driver written by Stuart Menefy. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Low Power Timer */ +#define LPC_LPT_LSB_OFF 0x400 +#define LPC_LPT_MSB_OFF 0x404 +#define LPC_LPT_START_OFF 0x408 + +/* Low Power Alarm */ +#define LPC_LPA_LSB_OFF 0x410 +#define LPC_LPA_MSB_OFF 0x414 +#define LPC_LPA_START_OFF 0x418 + +/* LPC as WDT */ +#define LPC_WDT_OFF 0x510 +#define LPC_WDT_FLAG_OFF 0x514 + +struct st_rtc { + struct rtc_device *rtc_dev; + struct rtc_wkalrm alarm; + struct resource *res; + struct clk *clk; + unsigned long clkrate; + void __iomem *ioaddr; + bool irq_enabled:1; + spinlock_t lock; + short irq; +}; + +static void st_rtc_set_hw_alarm(struct st_rtc *rtc, + unsigned long msb, unsigned long lsb) +{ + unsigned long flags; + + spin_lock_irqsave(&rtc->lock, flags); + + writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); + + writel_relaxed(msb, rtc->ioaddr + LPC_LPA_MSB_OFF); + writel_relaxed(lsb, rtc->ioaddr + LPC_LPA_LSB_OFF); + writel_relaxed(1, rtc->ioaddr + LPC_LPA_START_OFF); + + writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); + + spin_unlock_irqrestore(&rtc->lock, flags); +} + +static irqreturn_t st_rtc_handler(int this_irq, void *data) +{ + struct st_rtc *rtc = (struct st_rtc *)data; + + rtc_update_irq(rtc->rtc_dev, 1, RTC_AF); + + return IRQ_HANDLED; +} + +static int st_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + unsigned long lpt_lsb, lpt_msb; + unsigned long long lpt; + unsigned long flags; + + spin_lock_irqsave(&rtc->lock, flags); + + do { + lpt_msb = readl_relaxed(rtc->ioaddr + LPC_LPT_MSB_OFF); + lpt_lsb = readl_relaxed(rtc->ioaddr + LPC_LPT_LSB_OFF); + } while (readl_relaxed(rtc->ioaddr + LPC_LPT_MSB_OFF) != lpt_msb); + + spin_unlock_irqrestore(&rtc->lock, flags); + + lpt = ((unsigned long long)lpt_msb << 32) | lpt_lsb; + do_div(lpt, rtc->clkrate); + rtc_time_to_tm(lpt, tm); + + return 0; +} + +static int st_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + unsigned long long lpt; + unsigned long secs, flags; + int ret; + + ret = rtc_tm_to_time(tm, &secs); + if (ret) + return ret; + + lpt = (unsigned long long)secs * rtc->clkrate; + + spin_lock_irqsave(&rtc->lock, flags); + + writel_relaxed(lpt >> 32, rtc->ioaddr + LPC_LPT_MSB_OFF); + writel_relaxed(lpt, rtc->ioaddr + LPC_LPT_LSB_OFF); + writel_relaxed(1, rtc->ioaddr + LPC_LPT_START_OFF); + + spin_unlock_irqrestore(&rtc->lock, flags); + + return 0; +} + +static int st_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&rtc->lock, flags); + + memcpy(wkalrm, &rtc->alarm, sizeof(struct rtc_wkalrm)); + + spin_unlock_irqrestore(&rtc->lock, flags); + + return 0; +} + +static int st_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + + if (enabled && !rtc->irq_enabled) { + enable_irq(rtc->irq); + rtc->irq_enabled = true; + } else if (!enabled && rtc->irq_enabled) { + disable_irq(rtc->irq); + rtc->irq_enabled = false; + } + + return 0; +} + +static int st_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + struct rtc_time now; + unsigned long now_secs; + unsigned long alarm_secs; + unsigned long long lpa; + + st_rtc_read_time(dev, &now); + rtc_tm_to_time(&now, &now_secs); + rtc_tm_to_time(&t->time, &alarm_secs); + + /* Invalid alarm time */ + if (now_secs > alarm_secs) + return -EINVAL; + + memcpy(&rtc->alarm, t, sizeof(struct rtc_wkalrm)); + + /* Now many secs to fire */ + alarm_secs -= now_secs; + lpa = (unsigned long long)alarm_secs * rtc->clkrate; + + st_rtc_set_hw_alarm(rtc, lpa >> 32, lpa); + st_rtc_alarm_irq_enable(dev, t->enabled); + + return 0; +} + +static struct rtc_class_ops st_rtc_ops = { + .read_time = st_rtc_read_time, + .set_time = st_rtc_set_time, + .read_alarm = st_rtc_read_alarm, + .set_alarm = st_rtc_set_alarm, + .alarm_irq_enable = st_rtc_alarm_irq_enable, +}; + +static int st_rtc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct st_rtc *rtc; + struct resource *res; + struct rtc_time tm_check; + uint32_t mode; + int ret = 0; + + ret = of_property_read_u32(np, "st,lpc-mode", &mode); + if (ret) { + dev_err(&pdev->dev, "An LPC mode must be provided\n"); + return -EINVAL; + } + + /* LPC can either run in RTC or WDT mode */ + if (mode != ST_LPC_MODE_RTC) + return -ENODEV; + + rtc = devm_kzalloc(&pdev->dev, sizeof(struct st_rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + spin_lock_init(&rtc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rtc->ioaddr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rtc->ioaddr)) + return PTR_ERR(rtc->ioaddr); + + rtc->irq = irq_of_parse_and_map(np, 0); + if (!rtc->irq) { + dev_err(&pdev->dev, "IRQ missing or invalid\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, rtc->irq, st_rtc_handler, 0, + pdev->name, rtc); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %i\n", rtc->irq); + return ret; + } + + enable_irq_wake(rtc->irq); + disable_irq(rtc->irq); + + rtc->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(rtc->clk)) { + dev_err(&pdev->dev, "Unable to request clock\n"); + return PTR_ERR(rtc->clk); + } + + clk_prepare_enable(rtc->clk); + + rtc->clkrate = clk_get_rate(rtc->clk); + if (!rtc->clkrate) { + dev_err(&pdev->dev, "Unable to fetch clock rate\n"); + return -EINVAL; + } + + device_set_wakeup_capable(&pdev->dev, 1); + + platform_set_drvdata(pdev, rtc); + + /* + * The RTC-LPC is able to manage date.year > 2038 + * but currently the kernel can not manage this date! + * If the RTC-LPC has a date.year > 2038 then + * it's set to the epoch "Jan 1st 2000" + */ + st_rtc_read_time(&pdev->dev, &tm_check); + + if (tm_check.tm_year >= (2038 - 1900)) { + memset(&tm_check, 0, sizeof(tm_check)); + tm_check.tm_year = 100; + tm_check.tm_mday = 1; + st_rtc_set_time(&pdev->dev, &tm_check); + } + + rtc->rtc_dev = rtc_device_register("st-lpc-rtc", &pdev->dev, + &st_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc->rtc_dev)) { + clk_disable_unprepare(rtc->clk); + return PTR_ERR(rtc->rtc_dev); + } + + return 0; +} + +static int st_rtc_remove(struct platform_device *pdev) +{ + struct st_rtc *rtc = platform_get_drvdata(pdev); + + if (likely(rtc->rtc_dev)) + rtc_device_unregister(rtc->rtc_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int st_rtc_suspend(struct device *dev) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + return 0; + + writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); + writel_relaxed(0, rtc->ioaddr + LPC_LPA_START_OFF); + writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); + + return 0; +} + +static int st_rtc_resume(struct device *dev) +{ + struct st_rtc *rtc = dev_get_drvdata(dev); + + rtc_alarm_irq_enable(rtc->rtc_dev, 0); + + /* + * clean 'rtc->alarm' to allow a new + * .set_alarm to the upper RTC layer + */ + memset(&rtc->alarm, 0, sizeof(struct rtc_wkalrm)); + + writel_relaxed(0, rtc->ioaddr + LPC_LPA_MSB_OFF); + writel_relaxed(0, rtc->ioaddr + LPC_LPA_LSB_OFF); + writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); + writel_relaxed(1, rtc->ioaddr + LPC_LPA_START_OFF); + writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(st_rtc_pm_ops, st_rtc_suspend, st_rtc_resume); + +static const struct of_device_id st_rtc_match[] = { + { .compatible = "st,stih407-lpc" }, + {} +}; +MODULE_DEVICE_TABLE(of, st_rtc_match); + +static struct platform_driver st_rtc_platform_driver = { + .driver = { + .name = "st-lpc-rtc", + .pm = &st_rtc_pm_ops, + .of_match_table = st_rtc_match, + }, + .probe = st_rtc_probe, + .remove = st_rtc_remove, +}; + +module_platform_driver(st_rtc_platform_driver); + +MODULE_DESCRIPTION("STMicroelectronics LPC RTC driver"); +MODULE_AUTHOR("David Paris "); +MODULE_LICENSE("GPL"); From db4112e6d71526def391d4cdd708b3a826643228 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 9 Apr 2015 15:47:34 +0100 Subject: [PATCH 06/59] MAINTAINERS: Add Watchdog and RTC files to STI's maintainer entry Signed-off-by: Lee Jones --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2e5bbc0d68b2..b514f6b077b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1473,10 +1473,12 @@ F: drivers/phy/phy-stih407-usb.c F: drivers/phy/phy-stih41x-usb.c F: drivers/pinctrl/pinctrl-st.c F: drivers/reset/sti/ +F: drivers/rtc/rtc-st-lpc.c F: drivers/tty/serial/st-asc.c F: drivers/usb/dwc3/dwc3-st.c F: drivers/usb/host/ehci-st.c F: drivers/usb/host/ohci-st.c +F: drivers/watchdog/st_lpc_wdt.c F: drivers/ata/ahci_st.c ARM/TECHNOLOGIC SYSTEMS TS7250 MACHINE SUPPORT From f05be589ff32e87821b86845625ed3d402d37dc7 Mon Sep 17 00:00:00 2001 From: Boris BREZILLON Date: Fri, 10 Apr 2015 12:09:01 +0800 Subject: [PATCH 07/59] mfd: axp20x: Add AXP22x PMIC support Add support for the AXP22x PMIC devices to the existing AXP20x driver. This includes the AXP221 and AXP223, which are identical except for the external data bus. Only AXP221 is added for now. AXP223 will be added after it's Reduced Serial Bus (RSB) interface is supported. AXP22x defines a new set of registers, power supplies and regulators, but most of the API is similar to the AXP20x ones. A new irq chip definition is used, even though the available interrupts on AXP22x is a subset of those on AXP20x. This is done so the interrupt numbers match those on the datasheet. This patch only enables the interrupts, system power-off function, and PEK sub-device. The regulator driver must first support different variants before we enable it from the mfd driver. Signed-off-by: Boris BREZILLON [wens@csie.org: fix interrupts and move regulators to separate patch] Signed-off-by: Chen-Yu Tsai Signed-off-by: Lee Jones --- drivers/mfd/axp20x.c | 98 ++++++++++++++++++++++++++++++++++++++ include/linux/mfd/axp20x.h | 86 +++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index d18029be6a78..cfbb7d7aead6 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -32,6 +32,7 @@ static const char * const axp20x_model_names[] = { "AXP202", "AXP209", + "AXP221", "AXP288", }; @@ -54,6 +55,25 @@ static const struct regmap_access_table axp20x_volatile_table = { .n_yes_ranges = ARRAY_SIZE(axp20x_volatile_ranges), }; +static const struct regmap_range axp22x_writeable_ranges[] = { + regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE), + regmap_reg_range(AXP20X_DCDC_MODE, AXP22X_BATLOW_THRES1), +}; + +static const struct regmap_range axp22x_volatile_ranges[] = { + regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE), +}; + +static const struct regmap_access_table axp22x_writeable_table = { + .yes_ranges = axp22x_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(axp22x_writeable_ranges), +}; + +static const struct regmap_access_table axp22x_volatile_table = { + .yes_ranges = axp22x_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(axp22x_volatile_ranges), +}; + static const struct regmap_range axp288_writeable_ranges[] = { regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ6_STATE), regmap_reg_range(AXP20X_DCDC_MODE, AXP288_FG_TUNE5), @@ -87,6 +107,20 @@ static struct resource axp20x_pek_resources[] = { }, }; +static struct resource axp22x_pek_resources[] = { + { + .name = "PEK_DBR", + .start = AXP22X_IRQ_PEK_RIS_EDGE, + .end = AXP22X_IRQ_PEK_RIS_EDGE, + .flags = IORESOURCE_IRQ, + }, { + .name = "PEK_DBF", + .start = AXP22X_IRQ_PEK_FAL_EDGE, + .end = AXP22X_IRQ_PEK_FAL_EDGE, + .flags = IORESOURCE_IRQ, + }, +}; + static struct resource axp288_fuel_gauge_resources[] = { { .start = AXP288_IRQ_QWBTU, @@ -129,6 +163,15 @@ static const struct regmap_config axp20x_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +static const struct regmap_config axp22x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .wr_table = &axp22x_writeable_table, + .volatile_table = &axp22x_volatile_table, + .max_register = AXP22X_BATLOW_THRES1, + .cache_type = REGCACHE_RBTREE, +}; + static const struct regmap_config axp288_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -181,6 +224,34 @@ static const struct regmap_irq axp20x_regmap_irqs[] = { INIT_REGMAP_IRQ(AXP20X, GPIO0_INPUT, 4, 0), }; +static const struct regmap_irq axp22x_regmap_irqs[] = { + INIT_REGMAP_IRQ(AXP22X, ACIN_OVER_V, 0, 7), + INIT_REGMAP_IRQ(AXP22X, ACIN_PLUGIN, 0, 6), + INIT_REGMAP_IRQ(AXP22X, ACIN_REMOVAL, 0, 5), + INIT_REGMAP_IRQ(AXP22X, VBUS_OVER_V, 0, 4), + INIT_REGMAP_IRQ(AXP22X, VBUS_PLUGIN, 0, 3), + INIT_REGMAP_IRQ(AXP22X, VBUS_REMOVAL, 0, 2), + INIT_REGMAP_IRQ(AXP22X, VBUS_V_LOW, 0, 1), + INIT_REGMAP_IRQ(AXP22X, BATT_PLUGIN, 1, 7), + INIT_REGMAP_IRQ(AXP22X, BATT_REMOVAL, 1, 6), + INIT_REGMAP_IRQ(AXP22X, BATT_ENT_ACT_MODE, 1, 5), + INIT_REGMAP_IRQ(AXP22X, BATT_EXIT_ACT_MODE, 1, 4), + INIT_REGMAP_IRQ(AXP22X, CHARG, 1, 3), + INIT_REGMAP_IRQ(AXP22X, CHARG_DONE, 1, 2), + INIT_REGMAP_IRQ(AXP22X, BATT_TEMP_HIGH, 1, 1), + INIT_REGMAP_IRQ(AXP22X, BATT_TEMP_LOW, 1, 0), + INIT_REGMAP_IRQ(AXP22X, DIE_TEMP_HIGH, 2, 7), + INIT_REGMAP_IRQ(AXP22X, PEK_SHORT, 2, 1), + INIT_REGMAP_IRQ(AXP22X, PEK_LONG, 2, 0), + INIT_REGMAP_IRQ(AXP22X, LOW_PWR_LVL1, 3, 1), + INIT_REGMAP_IRQ(AXP22X, LOW_PWR_LVL2, 3, 0), + INIT_REGMAP_IRQ(AXP22X, TIMER, 4, 7), + INIT_REGMAP_IRQ(AXP22X, PEK_RIS_EDGE, 4, 6), + INIT_REGMAP_IRQ(AXP22X, PEK_FAL_EDGE, 4, 5), + INIT_REGMAP_IRQ(AXP22X, GPIO1_INPUT, 4, 1), + INIT_REGMAP_IRQ(AXP22X, GPIO0_INPUT, 4, 0), +}; + /* some IRQs are compatible with axp20x models */ static const struct regmap_irq axp288_regmap_irqs[] = { INIT_REGMAP_IRQ(AXP288, VBUS_FALL, 0, 2), @@ -224,6 +295,7 @@ static const struct regmap_irq axp288_regmap_irqs[] = { static const struct of_device_id axp20x_of_match[] = { { .compatible = "x-powers,axp202", .data = (void *) AXP202_ID }, { .compatible = "x-powers,axp209", .data = (void *) AXP209_ID }, + { .compatible = "x-powers,axp221", .data = (void *) AXP221_ID }, { }, }; MODULE_DEVICE_TABLE(of, axp20x_of_match); @@ -258,6 +330,18 @@ static const struct regmap_irq_chip axp20x_regmap_irq_chip = { }; +static const struct regmap_irq_chip axp22x_regmap_irq_chip = { + .name = "axp22x_irq_chip", + .status_base = AXP20X_IRQ1_STATE, + .ack_base = AXP20X_IRQ1_STATE, + .mask_base = AXP20X_IRQ1_EN, + .mask_invert = true, + .init_ack_masked = true, + .irqs = axp22x_regmap_irqs, + .num_irqs = ARRAY_SIZE(axp22x_regmap_irqs), + .num_regs = 5, +}; + static const struct regmap_irq_chip axp288_regmap_irq_chip = { .name = "axp288_irq_chip", .status_base = AXP20X_IRQ1_STATE, @@ -281,6 +365,14 @@ static struct mfd_cell axp20x_cells[] = { }, }; +static struct mfd_cell axp22x_cells[] = { + { + .name = "axp20x-pek", + .num_resources = ARRAY_SIZE(axp22x_pek_resources), + .resources = axp22x_pek_resources, + }, +}; + static struct resource axp288_adc_resources[] = { { .name = "GPADC", @@ -426,6 +518,12 @@ static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev) axp20x->regmap_cfg = &axp20x_regmap_config; axp20x->regmap_irq_chip = &axp20x_regmap_irq_chip; break; + case AXP221_ID: + axp20x->nr_cells = ARRAY_SIZE(axp22x_cells); + axp20x->cells = axp22x_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + axp20x->regmap_irq_chip = &axp22x_regmap_irq_chip; + break; case AXP288_ID: axp20x->cells = axp288_cells; axp20x->nr_cells = ARRAY_SIZE(axp288_cells); diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h index dfabd6db7ddf..95568eb798c3 100644 --- a/include/linux/mfd/axp20x.h +++ b/include/linux/mfd/axp20x.h @@ -14,6 +14,7 @@ enum { AXP202_ID = 0, AXP209_ID, + AXP221_ID, AXP288_ID, NR_AXP20X_VARIANTS, }; @@ -45,6 +46,28 @@ enum { #define AXP20X_V_LTF_DISCHRG 0x3c #define AXP20X_V_HTF_DISCHRG 0x3d +#define AXP22X_PWR_OUT_CTRL1 0x10 +#define AXP22X_PWR_OUT_CTRL2 0x12 +#define AXP22X_PWR_OUT_CTRL3 0x13 +#define AXP22X_DLDO1_V_OUT 0x15 +#define AXP22X_DLDO2_V_OUT 0x16 +#define AXP22X_DLDO3_V_OUT 0x17 +#define AXP22X_DLDO4_V_OUT 0x18 +#define AXP22X_ELDO1_V_OUT 0x19 +#define AXP22X_ELDO2_V_OUT 0x1a +#define AXP22X_ELDO3_V_OUT 0x1b +#define AXP22X_DC5LDO_V_OUT 0x1c +#define AXP22X_DCDC1_V_OUT 0x21 +#define AXP22X_DCDC2_V_OUT 0x22 +#define AXP22X_DCDC3_V_OUT 0x23 +#define AXP22X_DCDC4_V_OUT 0x24 +#define AXP22X_DCDC5_V_OUT 0x25 +#define AXP22X_DCDC23_V_RAMP_CTRL 0x27 +#define AXP22X_ALDO1_V_OUT 0x28 +#define AXP22X_ALDO2_V_OUT 0x29 +#define AXP22X_ALDO3_V_OUT 0x2a +#define AXP22X_CHRG_CTRL3 0x35 + /* Interrupt */ #define AXP20X_IRQ1_EN 0x40 #define AXP20X_IRQ2_EN 0x41 @@ -100,6 +123,9 @@ enum { #define AXP20X_VBUS_MON 0x8b #define AXP20X_OVER_TMP 0x8f +#define AXP22X_PWREN_CTRL1 0x8c +#define AXP22X_PWREN_CTRL2 0x8d + /* GPIO */ #define AXP20X_GPIO0_CTRL 0x90 #define AXP20X_LDO5_V_OUT 0x91 @@ -108,6 +134,11 @@ enum { #define AXP20X_GPIO20_SS 0x94 #define AXP20X_GPIO3_CTRL 0x95 +#define AXP22X_LDO_IO0_V_OUT 0x91 +#define AXP22X_LDO_IO1_V_OUT 0x93 +#define AXP22X_GPIO_STATE 0x94 +#define AXP22X_GPIO_PULL_DOWN 0x95 + /* Battery */ #define AXP20X_CHRG_CC_31_24 0xb0 #define AXP20X_CHRG_CC_23_16 0xb1 @@ -120,6 +151,9 @@ enum { #define AXP20X_CC_CTRL 0xb8 #define AXP20X_FG_RES 0xb9 +/* AXP22X specific registers */ +#define AXP22X_BATLOW_THRES1 0xe6 + /* AXP288 specific registers */ #define AXP288_PMIC_ADC_H 0x56 #define AXP288_PMIC_ADC_L 0x57 @@ -158,6 +192,30 @@ enum { AXP20X_REG_ID_MAX, }; +enum { + AXP22X_DCDC1 = 0, + AXP22X_DCDC2, + AXP22X_DCDC3, + AXP22X_DCDC4, + AXP22X_DCDC5, + AXP22X_DC1SW, + AXP22X_DC5LDO, + AXP22X_ALDO1, + AXP22X_ALDO2, + AXP22X_ALDO3, + AXP22X_ELDO1, + AXP22X_ELDO2, + AXP22X_ELDO3, + AXP22X_DLDO1, + AXP22X_DLDO2, + AXP22X_DLDO3, + AXP22X_DLDO4, + AXP22X_RTC_LDO, + AXP22X_LDO_IO0, + AXP22X_LDO_IO1, + AXP22X_REG_ID_MAX, +}; + /* IRQs */ enum { AXP20X_IRQ_ACIN_OVER_V = 1, @@ -199,6 +257,34 @@ enum { AXP20X_IRQ_GPIO0_INPUT, }; +enum axp22x_irqs { + AXP22X_IRQ_ACIN_OVER_V = 1, + AXP22X_IRQ_ACIN_PLUGIN, + AXP22X_IRQ_ACIN_REMOVAL, + AXP22X_IRQ_VBUS_OVER_V, + AXP22X_IRQ_VBUS_PLUGIN, + AXP22X_IRQ_VBUS_REMOVAL, + AXP22X_IRQ_VBUS_V_LOW, + AXP22X_IRQ_BATT_PLUGIN, + AXP22X_IRQ_BATT_REMOVAL, + AXP22X_IRQ_BATT_ENT_ACT_MODE, + AXP22X_IRQ_BATT_EXIT_ACT_MODE, + AXP22X_IRQ_CHARG, + AXP22X_IRQ_CHARG_DONE, + AXP22X_IRQ_BATT_TEMP_HIGH, + AXP22X_IRQ_BATT_TEMP_LOW, + AXP22X_IRQ_DIE_TEMP_HIGH, + AXP22X_IRQ_PEK_SHORT, + AXP22X_IRQ_PEK_LONG, + AXP22X_IRQ_LOW_PWR_LVL1, + AXP22X_IRQ_LOW_PWR_LVL2, + AXP22X_IRQ_TIMER, + AXP22X_IRQ_PEK_RIS_EDGE, + AXP22X_IRQ_PEK_FAL_EDGE, + AXP22X_IRQ_GPIO1_INPUT, + AXP22X_IRQ_GPIO0_INPUT, +}; + enum axp288_irqs { AXP288_IRQ_VBUS_FALL = 2, AXP288_IRQ_VBUS_RISE, From 768a87c8e12e17b44ab2163be70739ec5889f06f Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 10 Apr 2015 12:09:02 +0800 Subject: [PATCH 08/59] mfd: axp20x: Update DT bindings with AXP22x compatibles Add AXP221 to the list of supported devices. Also replace any mention of AXP20x in the document with a generic "PMIC". Signed-off-by: Chen-Yu Tsai Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/axp20x.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/axp20x.txt b/Documentation/devicetree/bindings/mfd/axp20x.txt index 98685f291a72..3914a3f91ff6 100644 --- a/Documentation/devicetree/bindings/mfd/axp20x.txt +++ b/Documentation/devicetree/bindings/mfd/axp20x.txt @@ -1,15 +1,16 @@ -AXP202/AXP209 device tree bindings +AXP family PMIC device tree bindings The axp20x family current members : axp202 (X-Powers) axp209 (X-Powers) +axp221 (X-Powers) Required properties: -- compatible: "x-powers,axp202" or "x-powers,axp209" +- compatible: "x-powers,axp202", "x-powers,axp209", "x-powers,axp221" - reg: The I2C slave address for the AXP chip - interrupt-parent: The parent interrupt controller - interrupts: SoC NMI / GPIO interrupt connected to the PMIC's IRQ pin -- interrupt-controller: axp20x has its own internal IRQs +- interrupt-controller: The PMIC has its own internal IRQs - #interrupt-cells: Should be set to 1 Optional properties: From 866bd951f2205ce36ebf2dd6f88c2ecd3ce245a8 Mon Sep 17 00:00:00 2001 From: Boris BREZILLON Date: Fri, 10 Apr 2015 12:09:03 +0800 Subject: [PATCH 09/59] regulator: axp20x: Prepare support for multiple AXP chip families Rework the AXP20X_ macros and probe function to support the several chip families, so that each family can define it's own set of regulators. Signed-off-by: Boris BREZILLON [wens@csie.org: Support different DC-DC work frequency ranges] Signed-off-by: Chen-Yu Tsai Reviewed-by: Mark Brown Signed-off-by: Lee Jones --- drivers/regulator/axp20x-regulator.c | 145 ++++++++++++++++++--------- 1 file changed, 95 insertions(+), 50 deletions(-) diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c index e4331f5e5d7d..50ae0b5f2c0c 100644 --- a/drivers/regulator/axp20x-regulator.c +++ b/drivers/regulator/axp20x-regulator.c @@ -32,15 +32,15 @@ #define AXP20X_FREQ_DCDC_MASK 0x0f -#define AXP20X_DESC_IO(_id, _match, _supply, _min, _max, _step, _vreg, _vmask, \ - _ereg, _emask, _enable_val, _disable_val) \ - [AXP20X_##_id] = { \ +#define AXP_DESC_IO(_family, _id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask, _enable_val, _disable_val) \ + [_family##_##_id] = { \ .name = #_id, \ .supply_name = (_supply), \ .of_match = of_match_ptr(_match), \ .regulators_node = of_match_ptr("regulators"), \ .type = REGULATOR_VOLTAGE, \ - .id = AXP20X_##_id, \ + .id = _family##_##_id, \ .n_voltages = (((_max) - (_min)) / (_step) + 1), \ .owner = THIS_MODULE, \ .min_uV = (_min) * 1000, \ @@ -54,15 +54,15 @@ .ops = &axp20x_ops, \ } -#define AXP20X_DESC(_id, _match, _supply, _min, _max, _step, _vreg, _vmask, \ - _ereg, _emask) \ - [AXP20X_##_id] = { \ +#define AXP_DESC(_family, _id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask) \ + [_family##_##_id] = { \ .name = #_id, \ .supply_name = (_supply), \ .of_match = of_match_ptr(_match), \ .regulators_node = of_match_ptr("regulators"), \ .type = REGULATOR_VOLTAGE, \ - .id = AXP20X_##_id, \ + .id = _family##_##_id, \ .n_voltages = (((_max) - (_min)) / (_step) + 1), \ .owner = THIS_MODULE, \ .min_uV = (_min) * 1000, \ @@ -74,29 +74,29 @@ .ops = &axp20x_ops, \ } -#define AXP20X_DESC_FIXED(_id, _match, _supply, _volt) \ - [AXP20X_##_id] = { \ +#define AXP_DESC_FIXED(_family, _id, _match, _supply, _volt) \ + [_family##_##_id] = { \ .name = #_id, \ .supply_name = (_supply), \ .of_match = of_match_ptr(_match), \ .regulators_node = of_match_ptr("regulators"), \ .type = REGULATOR_VOLTAGE, \ - .id = AXP20X_##_id, \ + .id = _family##_##_id, \ .n_voltages = 1, \ .owner = THIS_MODULE, \ .min_uV = (_volt) * 1000, \ .ops = &axp20x_ops_fixed \ } -#define AXP20X_DESC_TABLE(_id, _match, _supply, _table, _vreg, _vmask, _ereg, \ - _emask) \ - [AXP20X_##_id] = { \ +#define AXP_DESC_TABLE(_family, _id, _match, _supply, _table, _vreg, _vmask, \ + _ereg, _emask) \ + [_family##_##_id] = { \ .name = #_id, \ .supply_name = (_supply), \ .of_match = of_match_ptr(_match), \ .regulators_node = of_match_ptr("regulators"), \ .type = REGULATOR_VOLTAGE, \ - .id = AXP20X_##_id, \ + .id = _family##_##_id, \ .n_voltages = ARRAY_SIZE(_table), \ .owner = THIS_MODULE, \ .vsel_reg = (_vreg), \ @@ -136,37 +136,57 @@ static struct regulator_ops axp20x_ops = { }; static const struct regulator_desc axp20x_regulators[] = { - AXP20X_DESC(DCDC2, "dcdc2", "vin2", 700, 2275, 25, AXP20X_DCDC2_V_OUT, - 0x3f, AXP20X_PWR_OUT_CTRL, 0x10), - AXP20X_DESC(DCDC3, "dcdc3", "vin3", 700, 3500, 25, AXP20X_DCDC3_V_OUT, - 0x7f, AXP20X_PWR_OUT_CTRL, 0x02), - AXP20X_DESC_FIXED(LDO1, "ldo1", "acin", 1300), - AXP20X_DESC(LDO2, "ldo2", "ldo24in", 1800, 3300, 100, - AXP20X_LDO24_V_OUT, 0xf0, AXP20X_PWR_OUT_CTRL, 0x04), - AXP20X_DESC(LDO3, "ldo3", "ldo3in", 700, 3500, 25, AXP20X_LDO3_V_OUT, - 0x7f, AXP20X_PWR_OUT_CTRL, 0x40), - AXP20X_DESC_TABLE(LDO4, "ldo4", "ldo24in", axp20x_ldo4_data, - AXP20X_LDO24_V_OUT, 0x0f, AXP20X_PWR_OUT_CTRL, 0x08), - AXP20X_DESC_IO(LDO5, "ldo5", "ldo5in", 1800, 3300, 100, - AXP20X_LDO5_V_OUT, 0xf0, AXP20X_GPIO0_CTRL, 0x07, - AXP20X_IO_ENABLED, AXP20X_IO_DISABLED), + AXP_DESC(AXP20X, DCDC2, "dcdc2", "vin2", 700, 2275, 25, + AXP20X_DCDC2_V_OUT, 0x3f, AXP20X_PWR_OUT_CTRL, 0x10), + AXP_DESC(AXP20X, DCDC3, "dcdc3", "vin3", 700, 3500, 25, + AXP20X_DCDC3_V_OUT, 0x7f, AXP20X_PWR_OUT_CTRL, 0x02), + AXP_DESC_FIXED(AXP20X, LDO1, "ldo1", "acin", 1300), + AXP_DESC(AXP20X, LDO2, "ldo2", "ldo24in", 1800, 3300, 100, + AXP20X_LDO24_V_OUT, 0xf0, AXP20X_PWR_OUT_CTRL, 0x04), + AXP_DESC(AXP20X, LDO3, "ldo3", "ldo3in", 700, 3500, 25, + AXP20X_LDO3_V_OUT, 0x7f, AXP20X_PWR_OUT_CTRL, 0x40), + AXP_DESC_TABLE(AXP20X, LDO4, "ldo4", "ldo24in", axp20x_ldo4_data, + AXP20X_LDO24_V_OUT, 0x0f, AXP20X_PWR_OUT_CTRL, 0x08), + AXP_DESC_IO(AXP20X, LDO5, "ldo5", "ldo5in", 1800, 3300, 100, + AXP20X_LDO5_V_OUT, 0xf0, AXP20X_GPIO0_CTRL, 0x07, + AXP20X_IO_ENABLED, AXP20X_IO_DISABLED), }; static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq) { struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + u32 min, max, def, step; - if (dcdcfreq < 750) { - dcdcfreq = 750; - dev_warn(&pdev->dev, "DCDC frequency too low. Set to 750kHz\n"); + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + min = 750; + max = 1875; + def = 1500; + step = 75; + break; + default: + dev_err(&pdev->dev, + "Setting DCDC frequency for unsupported AXP variant\n"); + return -EINVAL; } - if (dcdcfreq > 1875) { - dcdcfreq = 1875; - dev_warn(&pdev->dev, "DCDC frequency too high. Set to 1875kHz\n"); + if (dcdcfreq == 0) + dcdcfreq = def; + + if (dcdcfreq < min) { + dcdcfreq = min; + dev_warn(&pdev->dev, "DCDC frequency too low. Set to %ukHz\n", + min); } - dcdcfreq = (dcdcfreq - 750) / 75; + if (dcdcfreq > max) { + dcdcfreq = max; + dev_warn(&pdev->dev, "DCDC frequency too high. Set to %ukHz\n", + max); + } + + dcdcfreq = (dcdcfreq - min) / step; return regmap_update_bits(axp20x->regmap, AXP20X_DCDC_FREQ, AXP20X_FREQ_DCDC_MASK, dcdcfreq); @@ -176,7 +196,7 @@ static int axp20x_regulator_parse_dt(struct platform_device *pdev) { struct device_node *np, *regulators; int ret; - u32 dcdcfreq; + u32 dcdcfreq = 0; np = of_node_get(pdev->dev.parent->of_node); if (!np) @@ -186,7 +206,6 @@ static int axp20x_regulator_parse_dt(struct platform_device *pdev) if (!regulators) { dev_warn(&pdev->dev, "regulators node not found\n"); } else { - dcdcfreq = 1500; of_property_read_u32(regulators, "x-powers,dcdc-freq", &dcdcfreq); ret = axp20x_set_dcdc_freq(pdev, dcdcfreq); if (ret < 0) { @@ -202,15 +221,27 @@ static int axp20x_regulator_parse_dt(struct platform_device *pdev) static int axp20x_set_dcdc_workmode(struct regulator_dev *rdev, int id, u32 workmode) { - unsigned int mask = AXP20X_WORKMODE_DCDC2_MASK; + struct axp20x_dev *axp20x = rdev_get_drvdata(rdev); + unsigned int mask; - if ((id != AXP20X_DCDC2) && (id != AXP20X_DCDC3)) + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + if ((id != AXP20X_DCDC2) && (id != AXP20X_DCDC3)) + return -EINVAL; + + mask = AXP20X_WORKMODE_DCDC2_MASK; + if (id == AXP20X_DCDC3) + mask = AXP20X_WORKMODE_DCDC3_MASK; + + workmode <<= ffs(mask) - 1; + break; + + default: + /* should not happen */ + WARN_ON(1); return -EINVAL; - - if (id == AXP20X_DCDC3) - mask = AXP20X_WORKMODE_DCDC3_MASK; - - workmode <<= ffs(mask) - 1; + } return regmap_update_bits(rdev->regmap, AXP20X_DCDC_MODE, mask, workmode); } @@ -219,22 +250,36 @@ static int axp20x_regulator_probe(struct platform_device *pdev) { struct regulator_dev *rdev; struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + const struct regulator_desc *regulators; struct regulator_config config = { .dev = pdev->dev.parent, .regmap = axp20x->regmap, + .driver_data = axp20x, }; - int ret, i; + int ret, i, nregulators; u32 workmode; + switch (axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + regulators = axp20x_regulators; + nregulators = AXP20X_REG_ID_MAX; + break; + default: + dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", + axp20x->variant); + return -EINVAL; + } + /* This only sets the dcdc freq. Ignore any errors */ axp20x_regulator_parse_dt(pdev); - for (i = 0; i < AXP20X_REG_ID_MAX; i++) { - rdev = devm_regulator_register(&pdev->dev, &axp20x_regulators[i], + for (i = 0; i < nregulators; i++) { + rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); if (IS_ERR(rdev)) { dev_err(&pdev->dev, "Failed to register %s\n", - axp20x_regulators[i].name); + regulators[i].name); return PTR_ERR(rdev); } @@ -245,7 +290,7 @@ static int axp20x_regulator_probe(struct platform_device *pdev) if (!ret) { if (axp20x_set_dcdc_workmode(rdev, i, workmode)) dev_err(&pdev->dev, "Failed to set workmode on %s\n", - axp20x_regulators[i].name); + rdev->desc->name); } } From 1b82b4e4f9546fbed0b61ee68cdae5db7518dd28 Mon Sep 17 00:00:00 2001 From: Boris BREZILLON Date: Fri, 10 Apr 2015 12:09:04 +0800 Subject: [PATCH 10/59] regulator: axp20x: Add support for AXP22X regulators Add AXP22X regulator definitions and variant id associations. This introduces a new "switch" type output for one of the regulators. It is a switchable secondary output of one regulator, with the same voltage level as the primary output. Signed-off-by: Boris BREZILLON [wens@csie.org: Moved variant choosing to multi family support patch] [wens@csie.org: Add dc-dc work frequency range] [wens@csie.org: Add "switch" type output regulator DC1SW] Signed-off-by: Chen-Yu Tsai Reviewed-by: Mark Brown Signed-off-by: Lee Jones --- drivers/regulator/axp20x-regulator.c | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c index 50ae0b5f2c0c..646829132b59 100644 --- a/drivers/regulator/axp20x-regulator.c +++ b/drivers/regulator/axp20x-regulator.c @@ -27,8 +27,12 @@ #define AXP20X_IO_ENABLED 0x03 #define AXP20X_IO_DISABLED 0x07 +#define AXP22X_IO_ENABLED 0x04 +#define AXP22X_IO_DISABLED 0x03 + #define AXP20X_WORKMODE_DCDC2_MASK BIT(2) #define AXP20X_WORKMODE_DCDC3_MASK BIT(1) +#define AXP22X_WORKMODE_DCDCX_MASK(x) BIT(x) #define AXP20X_FREQ_DCDC_MASK 0x0f @@ -74,6 +78,26 @@ .ops = &axp20x_ops, \ } +#define AXP_DESC_SW(_family, _id, _match, _supply, _min, _max, _step, _vreg, \ + _vmask, _ereg, _emask) \ + [_family##_##_id] = { \ + .name = #_id, \ + .supply_name = (_supply), \ + .of_match = of_match_ptr(_match), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .id = _family##_##_id, \ + .n_voltages = (((_max) - (_min)) / (_step) + 1), \ + .owner = THIS_MODULE, \ + .min_uV = (_min) * 1000, \ + .uV_step = (_step) * 1000, \ + .vsel_reg = (_vreg), \ + .vsel_mask = (_vmask), \ + .enable_reg = (_ereg), \ + .enable_mask = (_emask), \ + .ops = &axp20x_ops_sw, \ + } + #define AXP_DESC_FIXED(_family, _id, _match, _supply, _volt) \ [_family##_##_id] = { \ .name = #_id, \ @@ -135,6 +159,14 @@ static struct regulator_ops axp20x_ops = { .is_enabled = regulator_is_enabled_regmap, }; +static struct regulator_ops axp20x_ops_sw = { + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + static const struct regulator_desc axp20x_regulators[] = { AXP_DESC(AXP20X, DCDC2, "dcdc2", "vin2", 700, 2275, 25, AXP20X_DCDC2_V_OUT, 0x3f, AXP20X_PWR_OUT_CTRL, 0x10), @@ -152,6 +184,52 @@ static const struct regulator_desc axp20x_regulators[] = { AXP20X_IO_ENABLED, AXP20X_IO_DISABLED), }; +static const struct regulator_desc axp22x_regulators[] = { + AXP_DESC(AXP22X, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, + AXP22X_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(1)), + AXP_DESC(AXP22X, DCDC2, "dcdc2", "vin2", 600, 1540, 20, + AXP22X_DCDC2_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(2)), + AXP_DESC(AXP22X, DCDC3, "dcdc3", "vin3", 600, 1860, 20, + AXP22X_DCDC3_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(3)), + AXP_DESC(AXP22X, DCDC4, "dcdc4", "vin4", 600, 1540, 20, + AXP22X_DCDC4_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(3)), + AXP_DESC(AXP22X, DCDC5, "dcdc5", "vin5", 1000, 2550, 50, + AXP22X_DCDC5_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(4)), + /* secondary switchable output of DCDC1 */ + AXP_DESC_SW(AXP22X, DC1SW, "dc1sw", "dcdc1", 1600, 3400, 100, + AXP22X_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(7)), + /* LDO regulator internally chained to DCDC5 */ + AXP_DESC(AXP22X, DC5LDO, "dc5ldo", "dcdc5", 700, 1400, 100, + AXP22X_DC5LDO_V_OUT, 0x7, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP_DESC(AXP22X, ALDO1, "aldo1", "aldoin", 700, 3300, 100, + AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(6)), + AXP_DESC(AXP22X, ALDO2, "aldo2", "aldoin", 700, 3300, 100, + AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(7)), + AXP_DESC(AXP22X, ALDO3, "aldo3", "aldoin", 700, 3300, 100, + AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)), + AXP_DESC(AXP22X, DLDO1, "dldo1", "dldoin", 700, 3300, 100, + AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)), + AXP_DESC(AXP22X, DLDO2, "dldo2", "dldoin", 700, 3300, 100, + AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(4)), + AXP_DESC(AXP22X, DLDO3, "dldo3", "dldoin", 700, 3300, 100, + AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP_DESC(AXP22X, DLDO4, "dldo4", "dldoin", 700, 3300, 100, + AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP_DESC(AXP22X, ELDO1, "eldo1", "eldoin", 700, 3300, 100, + AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP_DESC(AXP22X, ELDO2, "eldo2", "eldoin", 700, 3300, 100, + AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP_DESC(AXP22X, ELDO3, "eldo3", "eldoin", 700, 3300, 100, + AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + AXP_DESC_IO(AXP22X, LDO_IO0, "ldo_io0", "ips", 1800, 3300, 100, + AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_IO(AXP22X, LDO_IO1, "ldo_io1", "ips", 1800, 3300, 100, + AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), + AXP_DESC_FIXED(AXP22X, RTC_LDO, "rtc_ldo", "ips", 3000), +}; + static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq) { struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); @@ -165,6 +243,12 @@ static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq) def = 1500; step = 75; break; + case AXP221_ID: + min = 1800; + max = 4050; + def = 3000; + step = 150; + break; default: dev_err(&pdev->dev, "Setting DCDC frequency for unsupported AXP variant\n"); @@ -237,6 +321,14 @@ static int axp20x_set_dcdc_workmode(struct regulator_dev *rdev, int id, u32 work workmode <<= ffs(mask) - 1; break; + case AXP221_ID: + if (id < AXP22X_DCDC1 || id > AXP22X_DCDC5) + return -EINVAL; + + mask = AXP22X_WORKMODE_DCDCX_MASK(id - AXP22X_DCDC1); + workmode <<= id - AXP22X_DCDC1; + break; + default: /* should not happen */ WARN_ON(1); @@ -265,6 +357,10 @@ static int axp20x_regulator_probe(struct platform_device *pdev) regulators = axp20x_regulators; nregulators = AXP20X_REG_ID_MAX; break; + case AXP221_ID: + regulators = axp22x_regulators; + nregulators = AXP22X_REG_ID_MAX; + break; default: dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", axp20x->variant); From 97ba5ac1f2a5e3e07b67133215b2958bd4f1b41a Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 10 Apr 2015 12:09:05 +0800 Subject: [PATCH 11/59] mfd: axp20x: Add AXP22x regulator information to DT bindings Add the list of regulators for AXP22x to the DT bindings. This includes the names and supply names. Signed-off-by: Chen-Yu Tsai Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/axp20x.txt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/axp20x.txt b/Documentation/devicetree/bindings/mfd/axp20x.txt index 3914a3f91ff6..753f14f46e85 100644 --- a/Documentation/devicetree/bindings/mfd/axp20x.txt +++ b/Documentation/devicetree/bindings/mfd/axp20x.txt @@ -49,6 +49,31 @@ LDO3 : LDO : ldo3in-supply LDO4 : LDO : ldo24in-supply : shared supply LDO5 : LDO : ldo5in-supply +AXP221 regulators, type, and corresponding input supply names: + +Regulator Type Supply Name Notes +--------- ---- ----------- ----- +DCDC1 : DC-DC buck : vin1-supply +DCDC2 : DC-DC buck : vin2-supply +DCDC3 : DC-DC buck : vin3-supply +DCDC4 : DC-DC buck : vin4-supply +DCDC5 : DC-DC buck : vin5-supply +DC1SW : On/Off Switch : dcdc1-supply : DCDC1 secondary output +DC5LDO : LDO : dcdc5-supply : input from DCDC5 +ALDO1 : LDO : aldoin-supply : shared supply +ALDO2 : LDO : aldoin-supply : shared supply +ALDO3 : LDO : aldoin-supply : shared supply +DLDO1 : LDO : dldoin-supply : shared supply +DLDO2 : LDO : dldoin-supply : shared supply +DLDO3 : LDO : dldoin-supply : shared supply +DLDO4 : LDO : dldoin-supply : shared supply +ELDO1 : LDO : eldoin-supply : shared supply +ELDO2 : LDO : eldoin-supply : shared supply +ELDO3 : LDO : eldoin-supply : shared supply +LDO_IO0 : LDO : ips-supply : GPIO 0 +LDO_IO1 : LDO : ips-supply : GPIO 1 +RTC_LDO : LDO : ips-supply : always on + Example: axp209: pmic@34 { From 6d4fa89dcd85e2427da83319ce75e5df5febcc96 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 10 Apr 2015 12:09:06 +0800 Subject: [PATCH 12/59] mfd: axp20x: Enable AXP22X regulators Now that the axp20x-regulators driver supports different variants of the AXP family, we can enable regulator support for AXP22X without the risk of incorrectly configuring regulators. Signed-off-by: Chen-Yu Tsai Signed-off-by: Lee Jones --- drivers/mfd/axp20x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index cfbb7d7aead6..6df91556faf3 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -370,6 +370,8 @@ static struct mfd_cell axp22x_cells[] = { .name = "axp20x-pek", .num_resources = ARRAY_SIZE(axp22x_pek_resources), .resources = axp22x_pek_resources, + }, { + .name = "axp20x-regulator", }, }; From e7b707f96820161820a864d2ee81740a14da6b93 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Wed, 20 May 2015 11:31:27 +0200 Subject: [PATCH 13/59] mfd: cros_ec: Remove parent field Parent and device were pointing to the same device structure. Parent is unused, removed. Signed-off-by: Gwendal Grignou Tested-by: Stephen Barber Tested-by: Heiko Stuebner Tested-by: Gwendal Grignou Reviewed-by: Puthikorn Voravootivat Signed-off-by: Javier Martinez Canillas Signed-off-by: Lee Jones --- drivers/mfd/cros_ec_i2c.c | 1 - drivers/mfd/cros_ec_spi.c | 1 - drivers/platform/chrome/cros_ec_lpc.c | 1 - include/linux/mfd/cros_ec.h | 2 -- 4 files changed, 5 deletions(-) diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index c0c30f4f946f..82b4d6148698 100644 --- a/drivers/mfd/cros_ec_i2c.c +++ b/drivers/mfd/cros_ec_i2c.c @@ -145,7 +145,6 @@ static int cros_ec_i2c_probe(struct i2c_client *client, ec_dev->cmd_xfer = cros_ec_cmd_xfer_i2c; ec_dev->ec_name = client->name; ec_dev->phys_name = client->adapter->name; - ec_dev->parent = &client->dev; err = cros_ec_register(ec_dev); if (err) { diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index bf6e08e8013e..27bd52e5e8b7 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -363,7 +363,6 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_dev->cmd_xfer = cros_ec_cmd_xfer_spi; ec_dev->ec_name = ec_spi->spi->modalias; ec_dev->phys_name = dev_name(&ec_spi->spi->dev); - ec_dev->parent = &ec_spi->spi->dev; ec_dev->din_size = EC_MSG_BYTES + EC_MSG_PREAMBLE_COUNT; ec_dev->dout_size = EC_MSG_BYTES; diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 8f9ac4d7bbd0..860310513cf0 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -214,7 +214,6 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) ec_dev->dev = dev; ec_dev->ec_name = pdev->name; ec_dev->phys_name = dev_name(dev); - ec_dev->parent = dev; ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; ec_dev->cmd_readmem = cros_ec_lpc_readmem; diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 324a34683971..14cf522123dd 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -85,7 +85,6 @@ struct cros_ec_command { * to using dword. * @din_size: size of din buffer to allocate (zero to use static din) * @dout_size: size of dout buffer to allocate (zero to use static dout) - * @parent: pointer to parent device (e.g. i2c or spi device) * @wake_enabled: true if this device can wake the system from sleep * @cmd_xfer: send command to EC and get response * Returns the number of bytes received if the communication succeeded, but @@ -113,7 +112,6 @@ struct cros_ec_device { uint8_t *dout; int din_size; int dout_size; - struct device *parent; bool wake_enabled; int (*cmd_xfer)(struct cros_ec_device *ec, struct cros_ec_command *msg); From bb03ffb96c72418d06e75c7b74ea62e04c78d322 Mon Sep 17 00:00:00 2001 From: Todd Broch Date: Wed, 20 May 2015 11:31:28 +0200 Subject: [PATCH 14/59] mfd: cros_ec: Instantiate sub-devices from device tree If the EC device tree node has sub-nodes, try to instantiate them as MFD sub-devices. We can configure the EC features provided by the board. Signed-off-by: Todd Broch Signed-off-by: Javier Martinez Canillas Reviewed-by: Gwendal Grignou Tested-by: Heiko Stuebner Signed-off-by: Lee Jones --- drivers/mfd/cros_ec.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index c4aecc6f8373..1574a9352a6d 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -17,6 +17,7 @@ * battery charging and regulator control, firmware update. */ +#include #include #include #include @@ -108,19 +109,9 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, EXPORT_SYMBOL(cros_ec_cmd_xfer); static const struct mfd_cell cros_devs[] = { - { - .name = "cros-ec-keyb", - .id = 1, - .of_compatible = "google,cros-ec-keyb", - }, - { - .name = "cros-ec-i2c-tunnel", - .id = 2, - .of_compatible = "google,cros-ec-i2c-tunnel", - }, { .name = "cros-ec-ctl", - .id = 3, + .id = PLATFORM_DEVID_AUTO, }, }; @@ -150,6 +141,15 @@ int cros_ec_register(struct cros_ec_device *ec_dev) return err; } + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + err = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (err) { + mfd_remove_devices(dev); + dev_err(dev, "Failed to register sub-devices\n"); + return err; + } + } + dev_info(dev, "Chrome EC device registered\n"); return 0; From a841178445bb72a3d566b4e6ab9d19e9b002eb47 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 9 Jun 2015 13:04:42 +0200 Subject: [PATCH 15/59] mfd: cros_ec: Use a zero-length array for command data Commit 1b84f2a4cd4a ("mfd: cros_ec: Use fixed size arrays to transfer data with the EC") modified the struct cros_ec_command fields to not use pointers for the input and output buffers and use fixed length arrays instead. This change was made because the cros_ec ioctl API uses that struct cros_ec_command to allow user-space to send commands to the EC and to get data from the EC. So using pointers made the API not 64-bit safe. Unfortunately this approach was not flexible enough for all the use-cases since there may be a need to send larger commands on newer versions of the EC command protocol. So to avoid to choose a constant length that it may be too big for most commands and thus wasting memory and CPU cycles on copy from and to user-space or having a size that is too small for some big commands, use a zero-length array that is both 64-bit safe and flexible. The same buffer is used for both output and input data so the maximum of these values should be used to allocate it. Suggested-by: Gwendal Grignou Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/i2c/busses/i2c-cros-ec-tunnel.c | 45 ++++-- drivers/input/keyboard/cros_ec_keyb.c | 29 ++-- drivers/mfd/cros_ec.c | 28 ++-- drivers/mfd/cros_ec_i2c.c | 4 +- drivers/mfd/cros_ec_spi.c | 2 +- drivers/platform/chrome/cros_ec_dev.c | 67 +++++---- drivers/platform/chrome/cros_ec_lightbar.c | 152 ++++++++++++++------- drivers/platform/chrome/cros_ec_lpc.c | 8 +- drivers/platform/chrome/cros_ec_sysfs.c | 152 +++++++++++++-------- include/linux/mfd/cros_ec.h | 6 +- 10 files changed, 318 insertions(+), 175 deletions(-) diff --git a/drivers/i2c/busses/i2c-cros-ec-tunnel.c b/drivers/i2c/busses/i2c-cros-ec-tunnel.c index fa8dedd8c3a2..a0d95ff682ae 100644 --- a/drivers/i2c/busses/i2c-cros-ec-tunnel.c +++ b/drivers/i2c/busses/i2c-cros-ec-tunnel.c @@ -182,8 +182,9 @@ static int ec_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg i2c_msgs[], const u16 bus_num = bus->remote_bus; int request_len; int response_len; + int alloc_size; int result; - struct cros_ec_command msg = { }; + struct cros_ec_command *msg; request_len = ec_i2c_count_message(i2c_msgs, num); if (request_len < 0) { @@ -198,25 +199,39 @@ static int ec_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg i2c_msgs[], return response_len; } - result = ec_i2c_construct_message(msg.outdata, i2c_msgs, num, bus_num); - if (result) - return result; + alloc_size = max(request_len, response_len); + msg = kmalloc(sizeof(*msg) + alloc_size, GFP_KERNEL); + if (!msg) + return -ENOMEM; - msg.version = 0; - msg.command = EC_CMD_I2C_PASSTHRU; - msg.outsize = request_len; - msg.insize = response_len; + result = ec_i2c_construct_message(msg->data, i2c_msgs, num, bus_num); + if (result) { + dev_err(dev, "Error constructing EC i2c message %d\n", result); + goto exit; + } - result = cros_ec_cmd_xfer(bus->ec, &msg); - if (result < 0) - return result; + msg->version = 0; + msg->command = EC_CMD_I2C_PASSTHRU; + msg->outsize = request_len; + msg->insize = response_len; - result = ec_i2c_parse_response(msg.indata, i2c_msgs, &num); - if (result < 0) - return result; + result = cros_ec_cmd_xfer(bus->ec, msg); + if (result < 0) { + dev_err(dev, "Error transferring EC i2c message %d\n", result); + goto exit; + } + + result = ec_i2c_parse_response(msg->data, i2c_msgs, &num); + if (result < 0) { + dev_err(dev, "Error parsing EC i2c message %d\n", result); + goto exit; + } /* Indicate success by saying how many messages were sent */ - return num; + result = num; +exit: + kfree(msg); + return result; } static u32 ec_i2c_functionality(struct i2c_adapter *adap) diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index b50c5b8b8a4d..974154a74505 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c @@ -148,19 +148,28 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, static int cros_ec_keyb_get_state(struct cros_ec_keyb *ckdev, uint8_t *kb_state) { - int ret; - struct cros_ec_command msg = { - .command = EC_CMD_MKBP_STATE, - .insize = ckdev->cols, - }; + int ret = 0; + struct cros_ec_command *msg; - ret = cros_ec_cmd_xfer(ckdev->ec, &msg); - if (ret < 0) - return ret; + msg = kmalloc(sizeof(*msg) + ckdev->cols, GFP_KERNEL); + if (!msg) + return -ENOMEM; - memcpy(kb_state, msg.indata, ckdev->cols); + msg->version = 0; + msg->command = EC_CMD_MKBP_STATE; + msg->insize = ckdev->cols; + msg->outsize = 0; - return 0; + ret = cros_ec_cmd_xfer(ckdev->ec, msg); + if (ret < 0) { + dev_err(ckdev->dev, "Error transferring EC message %d\n", ret); + goto exit; + } + + memcpy(kb_state, msg->data, ckdev->cols); +exit: + kfree(msg); + return ret; } static irqreturn_t cros_ec_keyb_irq(int irq, void *data) diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index 1574a9352a6d..4a0f6dfcd376 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -41,7 +41,7 @@ int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, out[2] = msg->outsize; csum = out[0] + out[1] + out[2]; for (i = 0; i < msg->outsize; i++) - csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->outdata[i]; + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff); return EC_MSG_TX_PROTO_BYTES + msg->outsize; @@ -75,11 +75,20 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, ret = ec_dev->cmd_xfer(ec_dev, msg); if (msg->result == EC_RES_IN_PROGRESS) { int i; - struct cros_ec_command status_msg = { }; + struct cros_ec_command *status_msg; struct ec_response_get_comms_status *status; - status_msg.command = EC_CMD_GET_COMMS_STATUS; - status_msg.insize = sizeof(*status); + status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), + GFP_KERNEL); + if (!status_msg) { + ret = -ENOMEM; + goto exit; + } + + status_msg->version = 0; + status_msg->command = EC_CMD_GET_COMMS_STATUS; + status_msg->insize = sizeof(*status); + status_msg->outsize = 0; /* * Query the EC's status until it's no longer busy or @@ -88,20 +97,23 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, for (i = 0; i < EC_COMMAND_RETRIES; i++) { usleep_range(10000, 11000); - ret = ec_dev->cmd_xfer(ec_dev, &status_msg); + ret = ec_dev->cmd_xfer(ec_dev, status_msg); if (ret < 0) break; - msg->result = status_msg.result; - if (status_msg.result != EC_RES_SUCCESS) + msg->result = status_msg->result; + if (status_msg->result != EC_RES_SUCCESS) break; status = (struct ec_response_get_comms_status *) - status_msg.indata; + status_msg->data; if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) break; } + + kfree(status_msg); } +exit: mutex_unlock(&ec_dev->lock); return ret; diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index 82b4d6148698..fbf7819f5de5 100644 --- a/drivers/mfd/cros_ec_i2c.c +++ b/drivers/mfd/cros_ec_i2c.c @@ -76,7 +76,7 @@ static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev, /* copy message payload and compute checksum */ sum = out_buf[0] + out_buf[1] + out_buf[2]; for (i = 0; i < msg->outsize; i++) { - out_buf[3 + i] = msg->outdata[i]; + out_buf[3 + i] = msg->data[i]; sum += out_buf[3 + i]; } out_buf[3 + msg->outsize] = sum; @@ -109,7 +109,7 @@ static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev, /* copy response packet payload and compute checksum */ sum = in_buf[0] + in_buf[1]; for (i = 0; i < len; i++) { - msg->indata[i] = in_buf[2 + i]; + msg->data[i] = in_buf[2 + i]; sum += in_buf[2 + i]; } dev_dbg(ec_dev->dev, "packet: %*ph, sum = %02x\n", diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index 27bd52e5e8b7..573730fec947 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -299,7 +299,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, for (i = 0; i < len; i++) { sum += ptr[i + 2]; if (ec_msg->insize) - ec_msg->indata[i] = ptr[i + 2]; + ec_msg->data[i] = ptr[i + 2]; } sum &= 0xff; diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 6090d0b2826f..e91ced1cb8ce 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "cros_ec_dev.h" @@ -36,28 +37,31 @@ static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) static const char * const current_image_name[] = { "unknown", "read-only", "read-write", "invalid", }; - struct cros_ec_command msg = { - .version = 0, - .command = EC_CMD_GET_VERSION, - .outdata = { 0 }, - .outsize = 0, - .indata = { 0 }, - .insize = sizeof(*resp), - }; + struct cros_ec_command *msg; int ret; - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) - return ret; + msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); + if (!msg) + return -ENOMEM; - if (msg.result != EC_RES_SUCCESS) { + msg->version = 0; + msg->command = EC_CMD_GET_VERSION; + msg->insize = sizeof(*resp); + msg->outsize = 0; + + ret = cros_ec_cmd_xfer(ec, msg); + if (ret < 0) + goto exit; + + if (msg->result != EC_RES_SUCCESS) { snprintf(str, maxlen, "%s\nUnknown EC version: EC returned %d\n", - CROS_EC_DEV_VERSION, msg.result); - return 0; + CROS_EC_DEV_VERSION, msg->result); + ret = -EINVAL; + goto exit; } - resp = (struct ec_response_get_version *)msg.indata; + resp = (struct ec_response_get_version *)msg->data; if (resp->current_image >= ARRAY_SIZE(current_image_name)) resp->current_image = 3; /* invalid */ @@ -65,7 +69,10 @@ static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) resp->version_string_ro, resp->version_string_rw, current_image_name[resp->current_image]); - return 0; + ret = 0; +exit: + kfree(msg); + return ret; } /* Device file ops */ @@ -110,20 +117,32 @@ static ssize_t ec_device_read(struct file *filp, char __user *buffer, static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) { long ret; - struct cros_ec_command s_cmd = { }; + struct cros_ec_command u_cmd; + struct cros_ec_command *s_cmd; - if (copy_from_user(&s_cmd, arg, sizeof(s_cmd))) + if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) return -EFAULT; - ret = cros_ec_cmd_xfer(ec, &s_cmd); + s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize), + GFP_KERNEL); + if (!s_cmd) + return -ENOMEM; + + if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) { + ret = -EFAULT; + goto exit; + } + + ret = cros_ec_cmd_xfer(ec, s_cmd); /* Only copy data to userland if data was received. */ if (ret < 0) - return ret; + goto exit; - if (copy_to_user(arg, &s_cmd, sizeof(s_cmd))) - return -EFAULT; - - return 0; + if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + u_cmd.insize)) + ret = -EFAULT; +exit: + kfree(s_cmd); + return ret; } static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index b4ff47a9069a..560e5d41b7ae 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "cros_ec_dev.h" @@ -91,54 +92,79 @@ out: return ret; } -#define INIT_MSG(P, R) { \ - .command = EC_CMD_LIGHTBAR_CMD, \ - .outsize = sizeof(*P), \ - .insize = sizeof(*R), \ - } +static struct cros_ec_command *alloc_lightbar_cmd_msg(void) +{ + struct cros_ec_command *msg; + int len; + + len = max(sizeof(struct ec_params_lightbar), + sizeof(struct ec_response_lightbar)); + + msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL); + if (!msg) + return NULL; + + msg->version = 0; + msg->command = EC_CMD_LIGHTBAR_CMD; + msg->outsize = sizeof(struct ec_params_lightbar); + msg->insize = sizeof(struct ec_response_lightbar); + + return msg; +} static int get_lightbar_version(struct cros_ec_device *ec, uint32_t *ver_ptr, uint32_t *flg_ptr) { struct ec_params_lightbar *param; struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; int ret; - param = (struct ec_params_lightbar *)msg.outdata; - param->cmd = LIGHTBAR_CMD_VERSION; - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) + msg = alloc_lightbar_cmd_msg(); + if (!msg) return 0; - switch (msg.result) { + param = (struct ec_params_lightbar *)msg->data; + param->cmd = LIGHTBAR_CMD_VERSION; + ret = cros_ec_cmd_xfer(ec, msg); + if (ret < 0) { + ret = 0; + goto exit; + } + + switch (msg->result) { case EC_RES_INVALID_PARAM: /* Pixel had no version command. */ if (ver_ptr) *ver_ptr = 0; if (flg_ptr) *flg_ptr = 0; - return 1; + ret = 1; + goto exit; case EC_RES_SUCCESS: - resp = (struct ec_response_lightbar *)msg.indata; + resp = (struct ec_response_lightbar *)msg->data; /* Future devices w/lightbars should implement this command */ if (ver_ptr) *ver_ptr = resp->version.num; if (flg_ptr) *flg_ptr = resp->version.flags; - return 1; + ret = 1; + goto exit; } /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ - return 0; + ret = 0; +exit: + kfree(msg); + return ret; } static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { - uint32_t version, flags; + uint32_t version = 0, flags = 0; struct cros_ec_device *ec = dev_get_drvdata(dev); int ret; @@ -158,8 +184,7 @@ static ssize_t brightness_store(struct device *dev, const char *buf, size_t count) { struct ec_params_lightbar *param; - struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; int ret; unsigned int val; struct cros_ec_device *ec = dev_get_drvdata(dev); @@ -167,21 +192,30 @@ static ssize_t brightness_store(struct device *dev, if (kstrtouint(buf, 0, &val)) return -EINVAL; - param = (struct ec_params_lightbar *)msg.outdata; + msg = alloc_lightbar_cmd_msg(); + if (!msg) + return -ENOMEM; + + param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_BRIGHTNESS; param->brightness.num = val; ret = lb_throttle(); if (ret) - return ret; + goto exit; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return -EINVAL; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto exit; + } - return count; + ret = count; +exit: + kfree(msg); + return ret; } @@ -196,12 +230,15 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ec_params_lightbar *param; - struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; struct cros_ec_device *ec = dev_get_drvdata(dev); unsigned int val[4]; int ret, i = 0, j = 0, ok = 0; + msg = alloc_lightbar_cmd_msg(); + if (!msg) + return -ENOMEM; + do { /* Skip any whitespace */ while (*buf && isspace(*buf)) @@ -215,7 +252,7 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, return -EINVAL; if (i == 4) { - param = (struct ec_params_lightbar *)msg.outdata; + param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_RGB; param->rgb.led = val[0]; param->rgb.red = val[1]; @@ -231,12 +268,14 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, return ret; } - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return -EINVAL; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto exit; + } i = 0; ok = 1; @@ -248,6 +287,8 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, } while (*buf); +exit: + kfree(msg); return (ok && i == 0) ? count : -EINVAL; } @@ -261,42 +302,55 @@ static ssize_t sequence_show(struct device *dev, { struct ec_params_lightbar *param; struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; int ret; struct cros_ec_device *ec = dev_get_drvdata(dev); - param = (struct ec_params_lightbar *)msg.outdata; + msg = alloc_lightbar_cmd_msg(); + if (!msg) + return -ENOMEM; + + param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_GET_SEQ; ret = lb_throttle(); if (ret) - return ret; + goto exit; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) - return ret; + goto exit; - if (msg.result != EC_RES_SUCCESS) - return scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg.result); + if (msg->result != EC_RES_SUCCESS) { + ret = scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg->result); + goto exit; + } - resp = (struct ec_response_lightbar *)msg.indata; + resp = (struct ec_response_lightbar *)msg->data; if (resp->get_seq.num >= ARRAY_SIZE(seqname)) - return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); + ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); else - return scnprintf(buf, PAGE_SIZE, "%s\n", - seqname[resp->get_seq.num]); + ret = scnprintf(buf, PAGE_SIZE, "%s\n", + seqname[resp->get_seq.num]); + +exit: + kfree(msg); + return ret; } static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ec_params_lightbar *param; - struct ec_response_lightbar *resp; - struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_command *msg; unsigned int num; int ret, len; struct cros_ec_device *ec = dev_get_drvdata(dev); + msg = alloc_lightbar_cmd_msg(); + if (!msg) + return -ENOMEM; + for (len = 0; len < count; len++) if (!isalnum(buf[len])) break; @@ -311,18 +365,18 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, return ret; } - param = (struct ec_params_lightbar *)msg.outdata; + param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SEQ; param->seq.num = num; ret = lb_throttle(); if (ret) return ret; - ret = cros_ec_cmd_xfer(ec, &msg); + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) return ret; - if (msg.result != EC_RES_SUCCESS) + if (msg->result != EC_RES_SUCCESS) return -EINVAL; return count; diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 860310513cf0..4c7f0df33bf8 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -73,8 +73,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, /* Copy data and update checksum */ for (i = 0; i < msg->outsize; i++) { - outb(msg->outdata[i], EC_LPC_ADDR_HOST_PARAM + i); - csum += msg->outdata[i]; + outb(msg->data[i], EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->data[i]; } /* Finalize checksum and write args */ @@ -129,8 +129,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, /* Read response and update checksum */ for (i = 0; i < args.data_size; i++) { - msg->indata[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); - csum += msg->indata[i]; + msg->data[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->data[i]; } /* Verify checksum */ diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index fb62ab6cc659..78ba82d670cb 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -66,14 +67,19 @@ static ssize_t store_ec_reboot(struct device *dev, {"hibernate", EC_REBOOT_HIBERNATE, 0}, {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, }; - struct cros_ec_command msg = { 0 }; - struct ec_params_reboot_ec *param = - (struct ec_params_reboot_ec *)msg.outdata; + struct cros_ec_command *msg; + struct ec_params_reboot_ec *param; int got_cmd = 0, offset = 0; int i; int ret; struct cros_ec_device *ec = dev_get_drvdata(dev); + msg = kmalloc(sizeof(*msg) + sizeof(*param), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + param = (struct ec_params_reboot_ec *)msg->data; + param->flags = 0; while (1) { /* Find word to start scanning */ @@ -100,19 +106,26 @@ static ssize_t store_ec_reboot(struct device *dev, offset++; } - if (!got_cmd) - return -EINVAL; - - msg.command = EC_CMD_REBOOT_EC; - msg.outsize = sizeof(param); - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) - return ret; - if (msg.result != EC_RES_SUCCESS) { - dev_dbg(ec->dev, "EC result %d\n", msg.result); - return -EINVAL; + if (!got_cmd) { + count = -EINVAL; + goto exit; } + msg->version = 0; + msg->command = EC_CMD_REBOOT_EC; + msg->outsize = sizeof(*param); + msg->insize = 0; + ret = cros_ec_cmd_xfer(ec, msg); + if (ret < 0) { + count = ret; + goto exit; + } + if (msg->result != EC_RES_SUCCESS) { + dev_dbg(ec->dev, "EC result %d\n", msg->result); + count = -EINVAL; + } +exit: + kfree(msg); return count; } @@ -123,22 +136,32 @@ static ssize_t show_ec_version(struct device *dev, struct ec_response_get_version *r_ver; struct ec_response_get_chip_info *r_chip; struct ec_response_board_version *r_board; - struct cros_ec_command msg = { 0 }; + struct cros_ec_command *msg; int ret; int count = 0; struct cros_ec_device *ec = dev_get_drvdata(dev); - /* Get versions. RW may change. */ - msg.command = EC_CMD_GET_VERSION; - msg.insize = sizeof(*r_ver); - ret = cros_ec_cmd_xfer(ec, &msg); - if (ret < 0) - return ret; - if (msg.result != EC_RES_SUCCESS) - return scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg.result); + msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; - r_ver = (struct ec_response_get_version *)msg.indata; + /* Get versions. RW may change. */ + msg->version = 0; + msg->command = EC_CMD_GET_VERSION; + msg->insize = sizeof(*r_ver); + msg->outsize = 0; + ret = cros_ec_cmd_xfer(ec, msg); + if (ret < 0) { + count = ret; + goto exit; + } + if (msg->result != EC_RES_SUCCESS) { + count = scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg->result); + goto exit; + } + + r_ver = (struct ec_response_get_version *)msg->data; /* Strings should be null-terminated, but let's be sure. */ r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; @@ -152,33 +175,33 @@ static ssize_t show_ec_version(struct device *dev, image_names[r_ver->current_image] : "?")); /* Get build info. */ - msg.command = EC_CMD_GET_BUILD_INFO; - msg.insize = sizeof(msg.indata); - ret = cros_ec_cmd_xfer(ec, &msg); + msg->command = EC_CMD_GET_BUILD_INFO; + msg->insize = EC_HOST_PARAM_SIZE; + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) count += scnprintf(buf + count, PAGE_SIZE - count, "Build info: XFER ERROR %d\n", ret); - else if (msg.result != EC_RES_SUCCESS) + else if (msg->result != EC_RES_SUCCESS) count += scnprintf(buf + count, PAGE_SIZE - count, - "Build info: EC error %d\n", msg.result); + "Build info: EC error %d\n", msg->result); else { - msg.indata[sizeof(msg.indata) - 1] = '\0'; + msg->data[sizeof(msg->data) - 1] = '\0'; count += scnprintf(buf + count, PAGE_SIZE - count, - "Build info: %s\n", msg.indata); + "Build info: %s\n", msg->data); } /* Get chip info. */ - msg.command = EC_CMD_GET_CHIP_INFO; - msg.insize = sizeof(*r_chip); - ret = cros_ec_cmd_xfer(ec, &msg); + msg->command = EC_CMD_GET_CHIP_INFO; + msg->insize = sizeof(*r_chip); + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) count += scnprintf(buf + count, PAGE_SIZE - count, "Chip info: XFER ERROR %d\n", ret); - else if (msg.result != EC_RES_SUCCESS) + else if (msg->result != EC_RES_SUCCESS) count += scnprintf(buf + count, PAGE_SIZE - count, - "Chip info: EC error %d\n", msg.result); + "Chip info: EC error %d\n", msg->result); else { - r_chip = (struct ec_response_get_chip_info *)msg.indata; + r_chip = (struct ec_response_get_chip_info *)msg->data; r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; r_chip->name[sizeof(r_chip->name) - 1] = '\0'; @@ -192,23 +215,25 @@ static ssize_t show_ec_version(struct device *dev, } /* Get board version */ - msg.command = EC_CMD_GET_BOARD_VERSION; - msg.insize = sizeof(*r_board); - ret = cros_ec_cmd_xfer(ec, &msg); + msg->command = EC_CMD_GET_BOARD_VERSION; + msg->insize = sizeof(*r_board); + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) count += scnprintf(buf + count, PAGE_SIZE - count, "Board version: XFER ERROR %d\n", ret); - else if (msg.result != EC_RES_SUCCESS) + else if (msg->result != EC_RES_SUCCESS) count += scnprintf(buf + count, PAGE_SIZE - count, - "Board version: EC error %d\n", msg.result); + "Board version: EC error %d\n", msg->result); else { - r_board = (struct ec_response_board_version *)msg.indata; + r_board = (struct ec_response_board_version *)msg->data; count += scnprintf(buf + count, PAGE_SIZE - count, "Board version: %d\n", r_board->board_version); } +exit: + kfree(msg); return count; } @@ -216,27 +241,38 @@ static ssize_t show_ec_flashinfo(struct device *dev, struct device_attribute *attr, char *buf) { struct ec_response_flash_info *resp; - struct cros_ec_command msg = { 0 }; + struct cros_ec_command *msg; int ret; struct cros_ec_device *ec = dev_get_drvdata(dev); + msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); + if (!msg) + return -ENOMEM; + /* The flash info shouldn't ever change, but ask each time anyway. */ - msg.command = EC_CMD_FLASH_INFO; - msg.insize = sizeof(*resp); - ret = cros_ec_cmd_xfer(ec, &msg); + msg->version = 0; + msg->command = EC_CMD_FLASH_INFO; + msg->insize = sizeof(*resp); + msg->outsize = 0; + ret = cros_ec_cmd_xfer(ec, msg); if (ret < 0) - return ret; - if (msg.result != EC_RES_SUCCESS) - return scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg.result); + goto exit; + if (msg->result != EC_RES_SUCCESS) { + ret = scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg->result); + goto exit; + } - resp = (struct ec_response_flash_info *)msg.indata; + resp = (struct ec_response_flash_info *)msg->data; - return scnprintf(buf, PAGE_SIZE, - "FlashSize %d\nWriteSize %d\n" - "EraseSize %d\nProtectSize %d\n", - resp->flash_size, resp->write_block_size, - resp->erase_block_size, resp->protect_block_size); + ret = scnprintf(buf, PAGE_SIZE, + "FlashSize %d\nWriteSize %d\n" + "EraseSize %d\nProtectSize %d\n", + resp->flash_size, resp->write_block_size, + resp->erase_block_size, resp->protect_block_size); +exit: + kfree(msg); + return ret; } /* Module initialization */ diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 14cf522123dd..7eee38abd02a 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -42,8 +42,7 @@ enum { * @outsize: Outgoing length in bytes * @insize: Max number of bytes to accept from EC * @result: EC's response to the command (separate from communication failure) - * @outdata: Outgoing data to EC - * @indata: Where to put the incoming data from EC + * @data: Where to put the incoming data from EC and outgoing data to EC */ struct cros_ec_command { uint32_t version; @@ -51,8 +50,7 @@ struct cros_ec_command { uint32_t outsize; uint32_t insize; uint32_t result; - uint8_t outdata[EC_PROTO2_MAX_PARAM_SIZE]; - uint8_t indata[EC_PROTO2_MAX_PARAM_SIZE]; + uint8_t data[0]; }; /** From 256ab950bdaa8797b7bac8fc11a567030d486304 Mon Sep 17 00:00:00 2001 From: Stephen Barber Date: Tue, 9 Jun 2015 13:04:43 +0200 Subject: [PATCH 16/59] mfd: cros_ec: rev cros_ec_commands.h Update cros_ec_commands.h to the latest version in the EC firmware sources and add power domain and passthru commands. Also, update lightbar to use new command names. Signed-off-by: Stephen Barber Reviewed-by: Randall Spangler Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Reviewed-by: Gwendal Grignou Tested-by: Gwendal Grignou Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/platform/chrome/cros_ec_lightbar.c | 14 +- include/linux/mfd/cros_ec_commands.h | 277 +++++++++++++++++++-- 2 files changed, 262 insertions(+), 29 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 560e5d41b7ae..6e1986a2dce1 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -197,8 +197,8 @@ static ssize_t brightness_store(struct device *dev, return -ENOMEM; param = (struct ec_params_lightbar *)msg->data; - param->cmd = LIGHTBAR_CMD_BRIGHTNESS; - param->brightness.num = val; + param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS; + param->set_brightness.num = val; ret = lb_throttle(); if (ret) goto exit; @@ -253,11 +253,11 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, if (i == 4) { param = (struct ec_params_lightbar *)msg->data; - param->cmd = LIGHTBAR_CMD_RGB; - param->rgb.led = val[0]; - param->rgb.red = val[1]; - param->rgb.green = val[2]; - param->rgb.blue = val[3]; + param->cmd = LIGHTBAR_CMD_SET_RGB; + param->set_rgb.led = val[0]; + param->set_rgb.red = val[1]; + param->set_rgb.green = val[2]; + param->set_rgb.blue = val[3]; /* * Throttle only the first of every four transactions, * so that the user can update all four LEDs at once. diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h index a49cd41feea7..13b630c10d4c 100644 --- a/include/linux/mfd/cros_ec_commands.h +++ b/include/linux/mfd/cros_ec_commands.h @@ -515,7 +515,7 @@ struct ec_host_response { /* * Notes on commands: * - * Each command is an 8-byte command value. Commands which take params or + * Each command is an 16-bit command value. Commands which take params or * return response data specify structs for that data. If no struct is * specified, the command does not input or output data, respectively. * Parameter/response length is implicit in the structs. Some underlying @@ -966,7 +966,7 @@ struct rgb_s { /* List of tweakable parameters. NOTE: It's __packed so it can be sent in a * host command, but the alignment is the same regardless. Keep it that way. */ -struct lightbar_params { +struct lightbar_params_v0 { /* Timing */ int32_t google_ramp_up; int32_t google_ramp_down; @@ -1000,32 +1000,81 @@ struct lightbar_params { struct rgb_s color[8]; /* 0-3 are Google colors */ } __packed; +struct lightbar_params_v1 { + /* Timing */ + int32_t google_ramp_up; + int32_t google_ramp_down; + int32_t s3s0_ramp_up; + int32_t s0_tick_delay[2]; /* AC=0/1 */ + int32_t s0a_tick_delay[2]; /* AC=0/1 */ + int32_t s0s3_ramp_down; + int32_t s3_sleep_for; + int32_t s3_ramp_up; + int32_t s3_ramp_down; + int32_t tap_tick_delay; + int32_t tap_display_time; + + /* Tap-for-battery params */ + uint8_t tap_pct_red; + uint8_t tap_pct_green; + uint8_t tap_seg_min_on; + uint8_t tap_seg_max_on; + uint8_t tap_seg_osc; + uint8_t tap_idx[3]; + + /* Oscillation */ + uint8_t osc_min[2]; /* AC=0/1 */ + uint8_t osc_max[2]; /* AC=0/1 */ + uint8_t w_ofs[2]; /* AC=0/1 */ + + /* Brightness limits based on the backlight and AC. */ + uint8_t bright_bl_off_fixed[2]; /* AC=0/1 */ + uint8_t bright_bl_on_min[2]; /* AC=0/1 */ + uint8_t bright_bl_on_max[2]; /* AC=0/1 */ + + /* Battery level thresholds */ + uint8_t battery_threshold[LB_BATTERY_LEVELS - 1]; + + /* Map [AC][battery_level] to color index */ + uint8_t s0_idx[2][LB_BATTERY_LEVELS]; /* AP is running */ + uint8_t s3_idx[2][LB_BATTERY_LEVELS]; /* AP is sleeping */ + + /* Color palette */ + struct rgb_s color[8]; /* 0-3 are Google colors */ +} __packed; + struct ec_params_lightbar { uint8_t cmd; /* Command (see enum lightbar_command) */ union { struct { /* no args */ - } dump, off, on, init, get_seq, get_params, version; + } dump, off, on, init, get_seq, get_params_v0, get_params_v1, + version, get_brightness, get_demo; - struct num { + struct { uint8_t num; - } brightness, seq, demo; + } set_brightness, seq, demo; - struct reg { + struct { uint8_t ctrl, reg, value; } reg; - struct rgb { + struct { uint8_t led, red, green, blue; - } rgb; + } set_rgb; - struct lightbar_params set_params; + struct { + uint8_t led; + } get_rgb; + + struct lightbar_params_v0 set_params_v0; + struct lightbar_params_v1 set_params_v1; }; } __packed; struct ec_response_lightbar { union { - struct dump { + struct { struct { uint8_t reg; uint8_t ic0; @@ -1033,20 +1082,26 @@ struct ec_response_lightbar { } vals[23]; } dump; - struct get_seq { + struct { uint8_t num; - } get_seq; + } get_seq, get_brightness, get_demo; - struct lightbar_params get_params; + struct lightbar_params_v0 get_params_v0; + struct lightbar_params_v1 get_params_v1; - struct version { + struct { uint32_t num; uint32_t flags; } version; + struct { + uint8_t red, green, blue; + } get_rgb; + struct { /* no return params */ - } off, on, init, brightness, seq, reg, rgb, demo, set_params; + } off, on, init, set_brightness, seq, reg, set_rgb, + demo, set_params_v0, set_params_v1; }; } __packed; @@ -1056,15 +1111,20 @@ enum lightbar_command { LIGHTBAR_CMD_OFF = 1, LIGHTBAR_CMD_ON = 2, LIGHTBAR_CMD_INIT = 3, - LIGHTBAR_CMD_BRIGHTNESS = 4, + LIGHTBAR_CMD_SET_BRIGHTNESS = 4, LIGHTBAR_CMD_SEQ = 5, LIGHTBAR_CMD_REG = 6, - LIGHTBAR_CMD_RGB = 7, + LIGHTBAR_CMD_SET_RGB = 7, LIGHTBAR_CMD_GET_SEQ = 8, LIGHTBAR_CMD_DEMO = 9, - LIGHTBAR_CMD_GET_PARAMS = 10, - LIGHTBAR_CMD_SET_PARAMS = 11, + LIGHTBAR_CMD_GET_PARAMS_V0 = 10, + LIGHTBAR_CMD_SET_PARAMS_V0 = 11, LIGHTBAR_CMD_VERSION = 12, + LIGHTBAR_CMD_GET_BRIGHTNESS = 13, + LIGHTBAR_CMD_GET_RGB = 14, + LIGHTBAR_CMD_GET_DEMO = 15, + LIGHTBAR_CMD_GET_PARAMS_V1 = 16, + LIGHTBAR_CMD_SET_PARAMS_V1 = 17, LIGHTBAR_NUM_CMDS }; @@ -1421,8 +1481,40 @@ struct ec_response_rtc { /*****************************************************************************/ /* Port80 log access */ +/* Maximum entries that can be read/written in a single command */ +#define EC_PORT80_SIZE_MAX 32 + /* Get last port80 code from previous boot */ #define EC_CMD_PORT80_LAST_BOOT 0x48 +#define EC_CMD_PORT80_READ 0x48 + +enum ec_port80_subcmd { + EC_PORT80_GET_INFO = 0, + EC_PORT80_READ_BUFFER, +}; + +struct ec_params_port80_read { + uint16_t subcmd; + union { + struct { + uint32_t offset; + uint32_t num_entries; + } read_buffer; + }; +} __packed; + +struct ec_response_port80_read { + union { + struct { + uint32_t writes; + uint32_t history_size; + uint32_t last_boot; + } get_info; + struct { + uint16_t codes[EC_PORT80_SIZE_MAX]; + } data; + }; +} __packed; struct ec_response_port80_last_boot { uint16_t code; @@ -1782,6 +1874,7 @@ struct ec_params_gpio_set { /* Get GPIO value */ #define EC_CMD_GPIO_GET 0x93 +/* Version 0 of input params and response */ struct ec_params_gpio_get { char name[32]; } __packed; @@ -1789,6 +1882,38 @@ struct ec_response_gpio_get { uint8_t val; } __packed; +/* Version 1 of input params and response */ +struct ec_params_gpio_get_v1 { + uint8_t subcmd; + union { + struct { + char name[32]; + } get_value_by_name; + struct { + uint8_t index; + } get_info; + }; +} __packed; + +struct ec_response_gpio_get_v1 { + union { + struct { + uint8_t val; + } get_value_by_name, get_count; + struct { + uint8_t val; + char name[32]; + uint32_t flags; + } get_info; + }; +} __packed; + +enum gpio_get_subcmd { + EC_GPIO_GET_BY_NAME = 0, + EC_GPIO_GET_COUNT = 1, + EC_GPIO_GET_INFO = 2, +}; + /*****************************************************************************/ /* I2C commands. Only available when flash write protect is unlocked. */ @@ -1857,13 +1982,21 @@ struct ec_params_charge_control { /*****************************************************************************/ /* - * Cut off battery power output if the battery supports. + * Cut off battery power immediately or after the host has shut down. * - * For unsupported battery, just don't implement this command and lets EC - * return EC_RES_INVALID_COMMAND. + * return EC_RES_INVALID_COMMAND if unsupported by a board/battery. + * EC_RES_SUCCESS if the command was successful. + * EC_RES_ERROR if the cut off command failed. */ + #define EC_CMD_BATTERY_CUT_OFF 0x99 +#define EC_BATTERY_CUTOFF_FLAG_AT_SHUTDOWN (1 << 0) + +struct ec_params_battery_cutoff { + uint8_t flags; +} __packed; + /*****************************************************************************/ /* USB port mux control. */ @@ -2141,6 +2274,32 @@ struct ec_params_sb_wr_block { uint16_t data[32]; } __packed; +/*****************************************************************************/ +/* Battery vendor parameters + * + * Get or set vendor-specific parameters in the battery. Implementations may + * differ between boards or batteries. On a set operation, the response + * contains the actual value set, which may be rounded or clipped from the + * requested value. + */ + +#define EC_CMD_BATTERY_VENDOR_PARAM 0xb4 + +enum ec_battery_vendor_param_mode { + BATTERY_VENDOR_PARAM_MODE_GET = 0, + BATTERY_VENDOR_PARAM_MODE_SET, +}; + +struct ec_params_battery_vendor_param { + uint32_t param; + uint32_t value; + uint8_t mode; +} __packed; + +struct ec_response_battery_vendor_param { + uint32_t value; +} __packed; + /*****************************************************************************/ /* System commands */ @@ -2336,6 +2495,80 @@ struct ec_params_reboot_ec { #endif /* !__ACPI__ */ +/*****************************************************************************/ +/* + * PD commands + * + * These commands are for PD MCU communication. + */ + +/* EC to PD MCU exchange status command */ +#define EC_CMD_PD_EXCHANGE_STATUS 0x100 + +/* Status of EC being sent to PD */ +struct ec_params_pd_status { + int8_t batt_soc; /* battery state of charge */ +} __packed; + +/* Status of PD being sent back to EC */ +struct ec_response_pd_status { + int8_t status; /* PD MCU status */ + uint32_t curr_lim_ma; /* input current limit */ +} __packed; + +/* Set USB type-C port role and muxes */ +#define EC_CMD_USB_PD_CONTROL 0x101 + +enum usb_pd_control_role { + USB_PD_CTRL_ROLE_NO_CHANGE = 0, + USB_PD_CTRL_ROLE_TOGGLE_ON = 1, /* == AUTO */ + USB_PD_CTRL_ROLE_TOGGLE_OFF = 2, + USB_PD_CTRL_ROLE_FORCE_SINK = 3, + USB_PD_CTRL_ROLE_FORCE_SOURCE = 4, +}; + +enum usb_pd_control_mux { + USB_PD_CTRL_MUX_NO_CHANGE = 0, + USB_PD_CTRL_MUX_NONE = 1, + USB_PD_CTRL_MUX_USB = 2, + USB_PD_CTRL_MUX_DP = 3, + USB_PD_CTRL_MUX_DOCK = 4, + USB_PD_CTRL_MUX_AUTO = 5, +}; + +struct ec_params_usb_pd_control { + uint8_t port; + uint8_t role; + uint8_t mux; +} __packed; + +/*****************************************************************************/ +/* + * Passthru commands + * + * Some platforms have sub-processors chained to each other. For example. + * + * AP <--> EC <--> PD MCU + * + * The top 2 bits of the command number are used to indicate which device the + * command is intended for. Device 0 is always the device receiving the + * command; other device mapping is board-specific. + * + * When a device receives a command to be passed to a sub-processor, it passes + * it on with the device number set back to 0. This allows the sub-processor + * to remain blissfully unaware of whether the command originated on the next + * device up the chain, or was passed through from the AP. + * + * In the above example, if the AP wants to send command 0x0002 to the PD MCU, + * AP sends command 0x4002 to the EC + * EC sends command 0x0002 to the PD MCU + * EC forwards PD MCU response back to the AP + */ + +/* Offset and max command number for sub-device n */ +#define EC_CMD_PASSTHRU_OFFSET(n) (0x4000 * (n)) +#define EC_CMD_PASSTHRU_MAX(n) (EC_CMD_PASSTHRU_OFFSET(n) + 0x3fff) + /*****************************************************************************/ /* * Deprecated constants. These constants have been renamed for clarity. The From 062476f24aa7cf714169342cc50626fd9bbb93da Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 9 Jun 2015 13:04:44 +0200 Subject: [PATCH 17/59] mfd: cros_ec: Move protocol helpers out of the MFD driver The MFD driver should only have the logic to instantiate its child devices and setup any shared resources that will be used by the subdevices drivers. The cros_ec MFD is more complex than expected since it also has helpers to communicate with the EC. So the driver will only get more bigger as other protocols are supported in the future. So move the communication protocol helpers to its own driver as drivers/platform/chrome/cros_ec_proto.c. Suggested-by: Lee Jones Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/i2c/busses/Kconfig | 2 +- drivers/input/keyboard/Kconfig | 2 +- drivers/mfd/Kconfig | 6 +- drivers/mfd/cros_ec.c | 96 -------------------- drivers/platform/chrome/Kconfig | 9 +- drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_ec_proto.c | 115 ++++++++++++++++++++++++ 7 files changed, 129 insertions(+), 102 deletions(-) create mode 100644 drivers/platform/chrome/cros_ec_proto.c diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 2255af23b9c7..5f1c1c4f5d87 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1103,7 +1103,7 @@ config I2C_SIBYTE config I2C_CROS_EC_TUNNEL tristate "ChromeOS EC tunnel I2C bus" - depends on MFD_CROS_EC + depends on CROS_EC_PROTO help If you say yes here you get an I2C bus that will tunnel i2c commands through to the other side of the ChromeOS EC to the i2c bus diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 106fbac7f8c5..e8eb60c6d83e 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -677,7 +677,7 @@ config KEYBOARD_W90P910 config KEYBOARD_CROS_EC tristate "ChromeOS EC keyboard" select INPUT_MATRIXKMAP - depends on MFD_CROS_EC + depends on CROS_EC_PROTO help Say Y here to enable the matrix keyboard used by ChromeOS devices and implemented on the ChromeOS EC. You must enable one bus option diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d5ad04dad081..cf3b86d441f7 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -94,6 +94,8 @@ config MFD_AXP20X config MFD_CROS_EC tristate "ChromeOS Embedded Controller" select MFD_CORE + select CHROME_PLATFORMS + select CROS_EC_PROTO help If you say Y here you get support for the ChromeOS Embedded Controller (EC) providing keyboard, battery and power services. @@ -102,7 +104,7 @@ config MFD_CROS_EC config MFD_CROS_EC_I2C tristate "ChromeOS Embedded Controller (I2C)" - depends on MFD_CROS_EC && I2C + depends on MFD_CROS_EC && CROS_EC_PROTO && I2C help If you say Y here, you get support for talking to the ChromeOS @@ -112,7 +114,7 @@ config MFD_CROS_EC_I2C config MFD_CROS_EC_SPI tristate "ChromeOS Embedded Controller (SPI)" - depends on MFD_CROS_EC && SPI && OF + depends on MFD_CROS_EC && CROS_EC_PROTO && SPI && OF ---help--- If you say Y here, you get support for talking to the ChromeOS EC diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index 4a0f6dfcd376..d857f6a2b57b 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -23,102 +23,6 @@ #include #include #include -#include -#include - -#define EC_COMMAND_RETRIES 50 - -int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) -{ - uint8_t *out; - int csum, i; - - BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); - out = ec_dev->dout; - out[0] = EC_CMD_VERSION0 + msg->version; - out[1] = msg->command; - out[2] = msg->outsize; - csum = out[0] + out[1] + out[2]; - for (i = 0; i < msg->outsize; i++) - csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; - out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff); - - return EC_MSG_TX_PROTO_BYTES + msg->outsize; -} -EXPORT_SYMBOL(cros_ec_prepare_tx); - -int cros_ec_check_result(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) -{ - switch (msg->result) { - case EC_RES_SUCCESS: - return 0; - case EC_RES_IN_PROGRESS: - dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", - msg->command); - return -EAGAIN; - default: - dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", - msg->command, msg->result); - return 0; - } -} -EXPORT_SYMBOL(cros_ec_check_result); - -int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) -{ - int ret; - - mutex_lock(&ec_dev->lock); - ret = ec_dev->cmd_xfer(ec_dev, msg); - if (msg->result == EC_RES_IN_PROGRESS) { - int i; - struct cros_ec_command *status_msg; - struct ec_response_get_comms_status *status; - - status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), - GFP_KERNEL); - if (!status_msg) { - ret = -ENOMEM; - goto exit; - } - - status_msg->version = 0; - status_msg->command = EC_CMD_GET_COMMS_STATUS; - status_msg->insize = sizeof(*status); - status_msg->outsize = 0; - - /* - * Query the EC's status until it's no longer busy or - * we encounter an error. - */ - for (i = 0; i < EC_COMMAND_RETRIES; i++) { - usleep_range(10000, 11000); - - ret = ec_dev->cmd_xfer(ec_dev, status_msg); - if (ret < 0) - break; - - msg->result = status_msg->result; - if (status_msg->result != EC_RES_SUCCESS) - break; - - status = (struct ec_response_get_comms_status *) - status_msg->data; - if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) - break; - } - - kfree(status_msg); - } -exit: - mutex_unlock(&ec_dev->lock); - - return ret; -} -EXPORT_SYMBOL(cros_ec_cmd_xfer); static const struct mfd_cell cros_devs[] = { { diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 2a6531a5fde8..cb1329919527 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -40,7 +40,7 @@ config CHROMEOS_PSTORE config CROS_EC_CHARDEV tristate "Chrome OS Embedded Controller userspace device interface" - depends on MFD_CROS_EC + depends on CROS_EC_PROTO ---help--- This driver adds support to talk with the ChromeOS EC from userspace. @@ -49,7 +49,7 @@ config CROS_EC_CHARDEV config CROS_EC_LPC tristate "ChromeOS Embedded Controller (LPC)" - depends on MFD_CROS_EC && (X86 || COMPILE_TEST) + depends on MFD_CROS_EC && CROS_EC_PROTO && (X86 || COMPILE_TEST) help If you say Y here, you get support for talking to the ChromeOS EC over an LPC bus. This uses a simple byte-level protocol with a @@ -59,4 +59,9 @@ config CROS_EC_LPC To compile this driver as a module, choose M here: the module will be called cros_ec_lpc. +config CROS_EC_PROTO + bool + help + ChromeOS EC communication protocol helpers. + endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index bd8d8601e875..4a11b010f5d8 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o cros_ec_lightbar.o obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o +obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c new file mode 100644 index 000000000000..58e98a24fd08 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -0,0 +1,115 @@ +/* + * ChromeOS EC communication protocol helper functions + * + * Copyright (C) 2015 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#define EC_COMMAND_RETRIES 50 + +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + uint8_t *out; + int csum, i; + + BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); + out = ec_dev->dout; + out[0] = EC_CMD_VERSION0 + msg->version; + out[1] = msg->command; + out[2] = msg->outsize; + csum = out[0] + out[1] + out[2]; + for (i = 0; i < msg->outsize; i++) + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; + out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff); + + return EC_MSG_TX_PROTO_BYTES + msg->outsize; +} +EXPORT_SYMBOL(cros_ec_prepare_tx); + +int cros_ec_check_result(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + switch (msg->result) { + case EC_RES_SUCCESS: + return 0; + case EC_RES_IN_PROGRESS: + dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", + msg->command); + return -EAGAIN; + default: + dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + return 0; + } +} +EXPORT_SYMBOL(cros_ec_check_result); + +int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + mutex_lock(&ec_dev->lock); + ret = ec_dev->cmd_xfer(ec_dev, msg); + if (msg->result == EC_RES_IN_PROGRESS) { + int i; + struct cros_ec_command *status_msg; + struct ec_response_get_comms_status *status; + + status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), + GFP_KERNEL); + if (!status_msg) { + ret = -ENOMEM; + goto exit; + } + + status_msg->version = 0; + status_msg->command = EC_CMD_GET_COMMS_STATUS; + status_msg->insize = sizeof(*status); + status_msg->outsize = 0; + + /* + * Query the EC's status until it's no longer busy or + * we encounter an error. + */ + for (i = 0; i < EC_COMMAND_RETRIES; i++) { + usleep_range(10000, 11000); + + ret = ec_dev->cmd_xfer(ec_dev, status_msg); + if (ret < 0) + break; + + msg->result = status_msg->result; + if (status_msg->result != EC_RES_SUCCESS) + break; + + status = (struct ec_response_get_comms_status *) + status_msg->data; + if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) + break; + } + + kfree(status_msg); + } +exit: + mutex_unlock(&ec_dev->lock); + + return ret; +} +EXPORT_SYMBOL(cros_ec_cmd_xfer); From 2c7589af3c4dee844e6a4174f2aa8996cf837604 Mon Sep 17 00:00:00 2001 From: Stephen Barber Date: Tue, 9 Jun 2015 13:04:45 +0200 Subject: [PATCH 18/59] mfd: cros_ec: add proto v3 skeleton Add support in cros_ec.c to handle EC host command protocol v3. For v3+, probe for maximum shared protocol version and max request, response, and passthrough sizes. For now, this will always fall back to v2, since there is no bus-specific code for handling proto v3 packets. Signed-off-by: Stephen Barber Signed-off-by: Javier Martinez Canillas Reviewed-by: Gwendal Grignou Tested-by: Gwendal Grignou Tested-by: Heiko Stuebner Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/mfd/cros_ec.c | 23 +- drivers/mfd/cros_ec_i2c.c | 4 + drivers/mfd/cros_ec_spi.c | 7 +- drivers/platform/chrome/cros_ec_lpc.c | 4 + drivers/platform/chrome/cros_ec_proto.c | 345 +++++++++++++++++++++--- include/linux/mfd/cros_ec.h | 28 +- 6 files changed, 358 insertions(+), 53 deletions(-) diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index d857f6a2b57b..08d82bfc5268 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -36,19 +36,22 @@ int cros_ec_register(struct cros_ec_device *ec_dev) struct device *dev = ec_dev->dev; int err = 0; - if (ec_dev->din_size) { - ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); - if (!ec_dev->din) - return -ENOMEM; - } - if (ec_dev->dout_size) { - ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); - if (!ec_dev->dout) - return -ENOMEM; - } + ec_dev->max_request = sizeof(struct ec_params_hello); + ec_dev->max_response = sizeof(struct ec_response_get_protocol_info); + ec_dev->max_passthru = 0; + + ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) + return -ENOMEM; + + ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); + if (!ec_dev->dout) + return -ENOMEM; mutex_init(&ec_dev->lock); + cros_ec_query_all(ec_dev); + err = mfd_add_devices(dev, 0, cros_devs, ARRAY_SIZE(cros_devs), NULL, ec_dev->irq, NULL); diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index fbf7819f5de5..b400bfa2772a 100644 --- a/drivers/mfd/cros_ec_i2c.c +++ b/drivers/mfd/cros_ec_i2c.c @@ -143,8 +143,12 @@ static int cros_ec_i2c_probe(struct i2c_client *client, ec_dev->priv = client; ec_dev->irq = client->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_i2c; + ec_dev->pkt_xfer = NULL; ec_dev->ec_name = client->name; ec_dev->phys_name = client->adapter->name; + ec_dev->din_size = sizeof(struct ec_host_response) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct ec_host_request); err = cros_ec_register(ec_dev); if (err) { diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index 573730fec947..04da2f288ef8 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -361,10 +361,13 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_dev->priv = ec_spi; ec_dev->irq = spi->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_spi; + ec_dev->pkt_xfer = NULL; ec_dev->ec_name = ec_spi->spi->modalias; ec_dev->phys_name = dev_name(&ec_spi->spi->dev); - ec_dev->din_size = EC_MSG_BYTES + EC_MSG_PREAMBLE_COUNT; - ec_dev->dout_size = EC_MSG_BYTES; + ec_dev->din_size = EC_MSG_PREAMBLE_COUNT + + sizeof(struct ec_host_response) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct ec_host_request); err = cros_ec_register(ec_dev); if (err) { diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 4c7f0df33bf8..05aeb559275f 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -215,7 +215,11 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) ec_dev->ec_name = pdev->name; ec_dev->phys_name = dev_name(dev); ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; + ec_dev->pkt_xfer = NULL; ec_dev->cmd_readmem = cros_ec_lpc_readmem; + ec_dev->din_size = sizeof(struct ec_host_response) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct ec_host_request); ret = cros_ec_register(ec_dev); if (ret) { diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 58e98a24fd08..990308ca384f 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -22,51 +22,49 @@ #define EC_COMMAND_RETRIES 50 -int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) +static int prepare_packet(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) { - uint8_t *out; - int csum, i; + struct ec_host_request *request; + u8 *out; + int i; + u8 csum = 0; + + BUG_ON(ec_dev->proto_version != EC_HOST_REQUEST_VERSION); + BUG_ON(msg->outsize + sizeof(*request) > ec_dev->dout_size); - BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); out = ec_dev->dout; - out[0] = EC_CMD_VERSION0 + msg->version; - out[1] = msg->command; - out[2] = msg->outsize; - csum = out[0] + out[1] + out[2]; + request = (struct ec_host_request *)out; + request->struct_version = EC_HOST_REQUEST_VERSION; + request->checksum = 0; + request->command = msg->command; + request->command_version = msg->version; + request->reserved = 0; + request->data_len = msg->outsize; + + for (i = 0; i < sizeof(*request); i++) + csum += out[i]; + + /* Copy data and update checksum */ + memcpy(out + sizeof(*request), msg->data, msg->outsize); for (i = 0; i < msg->outsize; i++) - csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; - out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff); + csum += msg->data[i]; - return EC_MSG_TX_PROTO_BYTES + msg->outsize; + request->checksum = -csum; + + return sizeof(*request) + msg->outsize; } -EXPORT_SYMBOL(cros_ec_prepare_tx); -int cros_ec_check_result(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) -{ - switch (msg->result) { - case EC_RES_SUCCESS: - return 0; - case EC_RES_IN_PROGRESS: - dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", - msg->command); - return -EAGAIN; - default: - dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", - msg->command, msg->result); - return 0; - } -} -EXPORT_SYMBOL(cros_ec_check_result); - -int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, - struct cros_ec_command *msg) +static int send_command(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) { int ret; - mutex_lock(&ec_dev->lock); - ret = ec_dev->cmd_xfer(ec_dev, msg); + if (ec_dev->proto_version > 2) + ret = ec_dev->pkt_xfer(ec_dev, msg); + else + ret = ec_dev->cmd_xfer(ec_dev, msg); + if (msg->result == EC_RES_IN_PROGRESS) { int i; struct cros_ec_command *status_msg; @@ -74,10 +72,8 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), GFP_KERNEL); - if (!status_msg) { - ret = -ENOMEM; - goto exit; - } + if (!status_msg) + return -ENOMEM; status_msg->version = 0; status_msg->command = EC_CMD_GET_COMMS_STATUS; @@ -107,7 +103,278 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, kfree(status_msg); } + + return ret; +} + +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + u8 *out; + u8 csum; + int i; + + if (ec_dev->proto_version > 2) + return prepare_packet(ec_dev, msg); + + BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); + out = ec_dev->dout; + out[0] = EC_CMD_VERSION0 + msg->version; + out[1] = msg->command; + out[2] = msg->outsize; + csum = out[0] + out[1] + out[2]; + for (i = 0; i < msg->outsize; i++) + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; + out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = csum; + + return EC_MSG_TX_PROTO_BYTES + msg->outsize; +} +EXPORT_SYMBOL(cros_ec_prepare_tx); + +int cros_ec_check_result(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + switch (msg->result) { + case EC_RES_SUCCESS: + return 0; + case EC_RES_IN_PROGRESS: + dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", + msg->command); + return -EAGAIN; + default: + dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + return 0; + } +} +EXPORT_SYMBOL(cros_ec_check_result); + +static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev, + int devidx, + struct cros_ec_command *msg) +{ + /* + * Try using v3+ to query for supported protocols. If this + * command fails, fall back to v2. Returns the highest protocol + * supported by the EC. + * Also sets the max request/response/passthru size. + */ + int ret; + + if (!ec_dev->pkt_xfer) + return -EPROTONOSUPPORT; + + memset(msg, 0, sizeof(*msg)); + msg->command = EC_CMD_PASSTHRU_OFFSET(devidx) | EC_CMD_GET_PROTOCOL_INFO; + msg->insize = sizeof(struct ec_response_get_protocol_info); + + ret = send_command(ec_dev, msg); + + if (ret < 0) { + dev_dbg(ec_dev->dev, + "failed to check for EC[%d] protocol version: %d\n", + devidx, ret); + return ret; + } + + if (devidx > 0 && msg->result == EC_RES_INVALID_COMMAND) + return -ENODEV; + else if (msg->result != EC_RES_SUCCESS) + return msg->result; + + return 0; +} + +static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev) +{ + struct cros_ec_command *msg; + struct ec_params_hello *hello_params; + struct ec_response_hello *hello_response; + int ret; + int len = max(sizeof(*hello_params), sizeof(*hello_response)); + + msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->version = 0; + msg->command = EC_CMD_HELLO; + hello_params = (struct ec_params_hello *)msg->data; + msg->outsize = sizeof(*hello_params); + hello_response = (struct ec_response_hello *)msg->data; + msg->insize = sizeof(*hello_response); + + hello_params->in_data = 0xa0b0c0d0; + + ret = send_command(ec_dev, msg); + + if (ret < 0) { + dev_dbg(ec_dev->dev, + "EC failed to respond to v2 hello: %d\n", + ret); + goto exit; + } else if (msg->result != EC_RES_SUCCESS) { + dev_err(ec_dev->dev, + "EC responded to v2 hello with error: %d\n", + msg->result); + ret = msg->result; + goto exit; + } else if (hello_response->out_data != 0xa1b2c3d4) { + dev_err(ec_dev->dev, + "EC responded to v2 hello with bad result: %u\n", + hello_response->out_data); + ret = -EBADMSG; + goto exit; + } + + ret = 0; + + exit: + kfree(msg); + return ret; +} + +int cros_ec_query_all(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + struct cros_ec_command *proto_msg; + struct ec_response_get_protocol_info *proto_info; + int ret; + + proto_msg = kzalloc(sizeof(*proto_msg) + sizeof(*proto_info), + GFP_KERNEL); + if (!proto_msg) + return -ENOMEM; + + /* First try sending with proto v3. */ + ec_dev->proto_version = 3; + ret = cros_ec_host_command_proto_query(ec_dev, 0, proto_msg); + + if (ret == 0) { + proto_info = (struct ec_response_get_protocol_info *) + proto_msg->data; + ec_dev->max_request = proto_info->max_request_packet_size - + sizeof(struct ec_host_request); + ec_dev->max_response = proto_info->max_response_packet_size - + sizeof(struct ec_host_response); + ec_dev->proto_version = + min(EC_HOST_REQUEST_VERSION, + fls(proto_info->protocol_versions) - 1); + dev_dbg(ec_dev->dev, + "using proto v%u\n", + ec_dev->proto_version); + + ec_dev->din_size = ec_dev->max_response + + sizeof(struct ec_host_response) + + EC_MAX_RESPONSE_OVERHEAD; + ec_dev->dout_size = ec_dev->max_request + + sizeof(struct ec_host_request) + + EC_MAX_REQUEST_OVERHEAD; + + /* + * Check for PD + */ + ret = cros_ec_host_command_proto_query(ec_dev, 1, proto_msg); + + if (ret) { + dev_dbg(ec_dev->dev, "no PD chip found: %d\n", ret); + ec_dev->max_passthru = 0; + } else { + dev_dbg(ec_dev->dev, "found PD chip\n"); + ec_dev->max_passthru = + proto_info->max_request_packet_size - + sizeof(struct ec_host_request); + } + } else { + /* Try querying with a v2 hello message. */ + ec_dev->proto_version = 2; + ret = cros_ec_host_command_proto_query_v2(ec_dev); + + if (ret == 0) { + /* V2 hello succeeded. */ + dev_dbg(ec_dev->dev, "falling back to proto v2\n"); + + ec_dev->max_request = EC_PROTO2_MAX_PARAM_SIZE; + ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE; + ec_dev->max_passthru = 0; + ec_dev->pkt_xfer = NULL; + ec_dev->din_size = EC_MSG_BYTES; + ec_dev->dout_size = EC_MSG_BYTES; + } else { + /* + * It's possible for a test to occur too early when + * the EC isn't listening. If this happens, we'll + * test later when the first command is run. + */ + ec_dev->proto_version = EC_PROTO_VERSION_UNKNOWN; + dev_dbg(ec_dev->dev, "EC query failed: %d\n", ret); + goto exit; + } + } + + devm_kfree(dev, ec_dev->din); + devm_kfree(dev, ec_dev->dout); + + ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) { + ret = -ENOMEM; + goto exit; + } + + ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); + if (!ec_dev->dout) { + devm_kfree(dev, ec_dev->din); + ret = -ENOMEM; + goto exit; + } + exit: + kfree(proto_msg); + return ret; +} +EXPORT_SYMBOL(cros_ec_query_all); + +int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + mutex_lock(&ec_dev->lock); + if (ec_dev->proto_version == EC_PROTO_VERSION_UNKNOWN) { + ret = cros_ec_query_all(ec_dev); + if (ret) { + dev_err(ec_dev->dev, + "EC version unknown and query failed; aborting command\n"); + mutex_unlock(&ec_dev->lock); + return ret; + } + } + + if (msg->insize > ec_dev->max_response) { + dev_dbg(ec_dev->dev, "clamping message receive buffer\n"); + msg->insize = ec_dev->max_response; + } + + if (msg->command < EC_CMD_PASSTHRU_OFFSET(1)) { + if (msg->outsize > ec_dev->max_request) { + dev_err(ec_dev->dev, + "request of size %u is too big (max: %u)\n", + msg->outsize, + ec_dev->max_request); + mutex_unlock(&ec_dev->lock); + return -EMSGSIZE; + } + } else { + if (msg->outsize > ec_dev->max_passthru) { + dev_err(ec_dev->dev, + "passthru rq of size %u is too big (max: %u)\n", + msg->outsize, + ec_dev->max_passthru); + mutex_unlock(&ec_dev->lock); + return -EMSGSIZE; + } + } + ret = send_command(ec_dev, msg); mutex_unlock(&ec_dev->lock); return ret; diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 7eee38abd02a..59d909434efd 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -21,6 +21,15 @@ #include #include +/* + * Max bus-specific overhead incurred by request/responses. + * I2C requires 1 additional byte for requests. + * I2C requires 2 additional bytes for responses. + * */ +#define EC_PROTO_VERSION_UNKNOWN 0 +#define EC_MAX_REQUEST_OVERHEAD 1 +#define EC_MAX_RESPONSE_OVERHEAD 2 + /* * Command interface between EC and AP, for LPC, I2C and SPI interfaces. */ @@ -88,6 +97,7 @@ struct cros_ec_command { * Returns the number of bytes received if the communication succeeded, but * that doesn't mean the EC was happy with the command. The caller * should check msg.result for the EC's result code. + * @pkt_xfer: send packet to EC and get response * @lock: one transaction at a time */ struct cros_ec_device { @@ -104,15 +114,21 @@ struct cros_ec_device { unsigned int bytes, void *dest); /* These are used to implement the platform-specific interface */ + u16 max_request; + u16 max_response; + u16 max_passthru; + u16 proto_version; void *priv; int irq; - uint8_t *din; - uint8_t *dout; + u8 *din; + u8 *dout; int din_size; int dout_size; bool wake_enabled; int (*cmd_xfer)(struct cros_ec_device *ec, struct cros_ec_command *msg); + int (*pkt_xfer)(struct cros_ec_device *ec, + struct cros_ec_command *msg); struct mutex lock; }; @@ -194,4 +210,12 @@ int cros_ec_remove(struct cros_ec_device *ec_dev); */ int cros_ec_register(struct cros_ec_device *ec_dev); +/** + * cros_ec_register - Query the protocol version supported by the ChromeOS EC + * + * @ec_dev: Device to register + * @return 0 if ok, -ve on error + */ +int cros_ec_query_all(struct cros_ec_device *ec_dev); + #endif /* __LINUX_MFD_CROS_EC_H */ From d365407079d33106f76bd486a863de05eb5ae95d Mon Sep 17 00:00:00 2001 From: Stephen Barber Date: Tue, 9 Jun 2015 13:04:46 +0200 Subject: [PATCH 19/59] mfd: cros_ec: add bus-specific proto v3 code Add proto v3 support to the SPI, I2C, and LPC. Signed-off-by: Stephen Barber Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Reviewed-by: Gwendal Grignou Tested-by: Gwendal Grignou Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/mfd/cros_ec_i2c.c | 166 ++++++++++- drivers/mfd/cros_ec_spi.c | 382 ++++++++++++++++++++++---- drivers/platform/chrome/cros_ec_lpc.c | 73 ++++- include/linux/mfd/cros_ec.h | 6 + 4 files changed, 569 insertions(+), 58 deletions(-) diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index b400bfa2772a..22e8a4ae1711 100644 --- a/drivers/mfd/cros_ec_i2c.c +++ b/drivers/mfd/cros_ec_i2c.c @@ -13,6 +13,7 @@ * GNU General Public License for more details. */ +#include #include #include #include @@ -22,6 +23,32 @@ #include #include +/** + * Request format for protocol v3 + * byte 0 0xda (EC_COMMAND_PROTOCOL_3) + * byte 1-8 struct ec_host_request + * byte 10- response data + */ +struct ec_host_request_i2c { + /* Always 0xda to backward compatible with v2 struct */ + uint8_t command_protocol; + struct ec_host_request ec_request; +} __packed; + + +/* + * Response format for protocol v3 + * byte 0 result code + * byte 1 packet_length + * byte 2-9 struct ec_host_response + * byte 10- response data + */ +struct ec_host_response_i2c { + uint8_t result; + uint8_t packet_length; + struct ec_host_response ec_response; +} __packed; + static inline struct cros_ec_device *to_ec_dev(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -29,6 +56,134 @@ static inline struct cros_ec_device *to_ec_dev(struct device *dev) return i2c_get_clientdata(client); } +static int cros_ec_pkt_xfer_i2c(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + struct i2c_client *client = ec_dev->priv; + int ret = -ENOMEM; + int i; + int packet_len; + u8 *out_buf = NULL; + u8 *in_buf = NULL; + u8 sum; + struct i2c_msg i2c_msg[2]; + struct ec_host_response *ec_response; + struct ec_host_request_i2c *ec_request_i2c; + struct ec_host_response_i2c *ec_response_i2c; + int request_header_size = sizeof(struct ec_host_request_i2c); + int response_header_size = sizeof(struct ec_host_response_i2c); + + i2c_msg[0].addr = client->addr; + i2c_msg[0].flags = 0; + i2c_msg[1].addr = client->addr; + i2c_msg[1].flags = I2C_M_RD; + + packet_len = msg->insize + response_header_size; + BUG_ON(packet_len > ec_dev->din_size); + in_buf = ec_dev->din; + i2c_msg[1].len = packet_len; + i2c_msg[1].buf = (char *) in_buf; + + packet_len = msg->outsize + request_header_size; + BUG_ON(packet_len > ec_dev->dout_size); + out_buf = ec_dev->dout; + i2c_msg[0].len = packet_len; + i2c_msg[0].buf = (char *) out_buf; + + /* create request data */ + ec_request_i2c = (struct ec_host_request_i2c *) out_buf; + ec_request_i2c->command_protocol = EC_COMMAND_PROTOCOL_3; + + ec_dev->dout++; + ret = cros_ec_prepare_tx(ec_dev, msg); + ec_dev->dout--; + + /* send command to EC and read answer */ + ret = i2c_transfer(client->adapter, i2c_msg, 2); + if (ret < 0) { + dev_dbg(ec_dev->dev, "i2c transfer failed: %d\n", ret); + goto done; + } else if (ret != 2) { + dev_err(ec_dev->dev, "failed to get response: %d\n", ret); + ret = -EIO; + goto done; + } + + ec_response_i2c = (struct ec_host_response_i2c *) in_buf; + msg->result = ec_response_i2c->result; + ec_response = &ec_response_i2c->ec_response; + + switch (msg->result) { + case EC_RES_SUCCESS: + break; + case EC_RES_IN_PROGRESS: + ret = -EAGAIN; + dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", + msg->command); + goto done; + + default: + dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + /* + * When we send v3 request to v2 ec, ec won't recognize the + * 0xda (EC_COMMAND_PROTOCOL_3) and will return with status + * EC_RES_INVALID_COMMAND with zero data length. + * + * In case of invalid command for v3 protocol the data length + * will be at least sizeof(struct ec_host_response) + */ + if (ec_response_i2c->result == EC_RES_INVALID_COMMAND && + ec_response_i2c->packet_length == 0) { + ret = -EPROTONOSUPPORT; + goto done; + } + } + + if (ec_response_i2c->packet_length < sizeof(struct ec_host_response)) { + dev_err(ec_dev->dev, + "response of %u bytes too short; not a full header\n", + ec_response_i2c->packet_length); + ret = -EBADMSG; + goto done; + } + + if (msg->insize < ec_response->data_len) { + dev_err(ec_dev->dev, + "response data size is too large: expected %u, got %u\n", + msg->insize, + ec_response->data_len); + ret = -EMSGSIZE; + goto done; + } + + /* copy response packet payload and compute checksum */ + sum = 0; + for (i = 0; i < sizeof(struct ec_host_response); i++) + sum += ((u8 *)ec_response)[i]; + + memcpy(msg->data, + in_buf + response_header_size, + ec_response->data_len); + for (i = 0; i < ec_response->data_len; i++) + sum += msg->data[i]; + + /* All bytes should sum to zero */ + if (sum) { + dev_err(ec_dev->dev, "bad packet checksum\n"); + ret = -EBADMSG; + goto done; + } + + ret = ec_response->data_len; + +done: + if (msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + return ret; +} + static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) { @@ -121,9 +276,12 @@ static int cros_ec_cmd_xfer_i2c(struct cros_ec_device *ec_dev, } ret = len; - done: +done: kfree(in_buf); kfree(out_buf); + if (msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + return ret; } @@ -143,12 +301,12 @@ static int cros_ec_i2c_probe(struct i2c_client *client, ec_dev->priv = client; ec_dev->irq = client->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_i2c; - ec_dev->pkt_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_i2c; ec_dev->ec_name = client->name; ec_dev->phys_name = client->adapter->name; - ec_dev->din_size = sizeof(struct ec_host_response) + + ec_dev->din_size = sizeof(struct ec_host_response_i2c) + sizeof(struct ec_response_get_protocol_info); - ec_dev->dout_size = sizeof(struct ec_host_request); + ec_dev->dout_size = sizeof(struct ec_host_request_i2c); err = cros_ec_register(ec_dev); if (err) { diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index 04da2f288ef8..4e6f2f6b1095 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -65,12 +65,6 @@ */ #define EC_SPI_RECOVERY_TIME_NS (200 * 1000) -/* - * The EC is unresponsive for a time after a reboot command. Add a - * simple delay to make sure that the bus stays locked. - */ -#define EC_REBOOT_DELAY_MS 50 - /** * struct cros_ec_spi - information about a SPI-connected EC * @@ -87,7 +81,7 @@ struct cros_ec_spi { }; static void debug_packet(struct device *dev, const char *name, u8 *ptr, - int len) + int len) { #ifdef DEBUG int i; @@ -100,6 +94,172 @@ static void debug_packet(struct device *dev, const char *name, u8 *ptr, #endif } +static int terminate_request(struct cros_ec_device *ec_dev) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_message msg; + struct spi_transfer trans; + int ret; + + /* + * Turn off CS, possibly adding a delay to ensure the rising edge + * doesn't come too soon after the end of the data. + */ + spi_message_init(&msg); + memset(&trans, 0, sizeof(trans)); + trans.delay_usecs = ec_spi->end_of_msg_delay; + spi_message_add_tail(&trans, &msg); + + ret = spi_sync(ec_spi->spi, &msg); + + /* Reset end-of-response timer */ + ec_spi->last_transfer_ns = ktime_get_ns(); + if (ret < 0) { + dev_err(ec_dev->dev, + "cs-deassert spi transfer failed: %d\n", + ret); + } + + return ret; +} + +/** + * receive_n_bytes - receive n bytes from the EC. + * + * Assumes buf is a pointer into the ec_dev->din buffer + */ +static int receive_n_bytes(struct cros_ec_device *ec_dev, u8 *buf, int n) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + int ret; + + BUG_ON(buf - ec_dev->din + n > ec_dev->din_size); + + memset(&trans, 0, sizeof(trans)); + trans.cs_change = 1; + trans.rx_buf = buf; + trans.len = n; + + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + if (ret < 0) + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + + return ret; +} + +/** + * cros_ec_spi_receive_packet - Receive a packet from the EC. + * + * This function has two phases: reading the preamble bytes (since if we read + * data from the EC before it is ready to send, we just get preamble) and + * reading the actual message. + * + * The received data is placed into ec_dev->din. + * + * @ec_dev: ChromeOS EC device + * @need_len: Number of message bytes we need to read + */ +static int cros_ec_spi_receive_packet(struct cros_ec_device *ec_dev, + int need_len) +{ + struct ec_host_response *response; + u8 *ptr, *end; + int ret; + unsigned long deadline; + int todo; + + BUG_ON(EC_MSG_PREAMBLE_COUNT > ec_dev->din_size); + + /* Receive data until we see the header byte */ + deadline = jiffies + msecs_to_jiffies(EC_MSG_DEADLINE_MS); + while (true) { + unsigned long start_jiffies = jiffies; + + ret = receive_n_bytes(ec_dev, + ec_dev->din, + EC_MSG_PREAMBLE_COUNT); + if (ret < 0) + return ret; + + ptr = ec_dev->din; + for (end = ptr + EC_MSG_PREAMBLE_COUNT; ptr != end; ptr++) { + if (*ptr == EC_SPI_FRAME_START) { + dev_dbg(ec_dev->dev, "msg found at %zd\n", + ptr - ec_dev->din); + break; + } + } + if (ptr != end) + break; + + /* + * Use the time at the start of the loop as a timeout. This + * gives us one last shot at getting the transfer and is useful + * in case we got context switched out for a while. + */ + if (time_after(start_jiffies, deadline)) { + dev_warn(ec_dev->dev, "EC failed to respond in time\n"); + return -ETIMEDOUT; + } + } + + /* + * ptr now points to the header byte. Copy any valid data to the + * start of our buffer + */ + todo = end - ++ptr; + BUG_ON(todo < 0 || todo > ec_dev->din_size); + todo = min(todo, need_len); + memmove(ec_dev->din, ptr, todo); + ptr = ec_dev->din + todo; + dev_dbg(ec_dev->dev, "need %d, got %d bytes from preamble\n", + need_len, todo); + need_len -= todo; + + /* If the entire response struct wasn't read, get the rest of it. */ + if (todo < sizeof(*response)) { + ret = receive_n_bytes(ec_dev, ptr, sizeof(*response) - todo); + if (ret < 0) + return -EBADMSG; + ptr += (sizeof(*response) - todo); + todo = sizeof(*response); + } + + response = (struct ec_host_response *)ec_dev->din; + + /* Abort if data_len is too large. */ + if (response->data_len > ec_dev->din_size) + return -EMSGSIZE; + + /* Receive data until we have it all */ + while (need_len > 0) { + /* + * We can't support transfers larger than the SPI FIFO size + * unless we have DMA. We don't have DMA on the ISP SPI ports + * for Exynos. We need a way of asking SPI driver for + * maximum-supported transfer size. + */ + todo = min(need_len, 256); + dev_dbg(ec_dev->dev, "loop, todo=%d, need_len=%d, ptr=%zd\n", + todo, need_len, ptr - ec_dev->din); + + ret = receive_n_bytes(ec_dev, ptr, todo); + if (ret < 0) + return ret; + + ptr += todo; + need_len -= todo; + } + + dev_dbg(ec_dev->dev, "loop done, ptr=%zd\n", ptr - ec_dev->din); + + return 0; +} + /** * cros_ec_spi_receive_response - Receive a response from the EC. * @@ -115,34 +275,27 @@ static void debug_packet(struct device *dev, const char *name, u8 *ptr, static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, int need_len) { - struct cros_ec_spi *ec_spi = ec_dev->priv; - struct spi_transfer trans; - struct spi_message msg; u8 *ptr, *end; int ret; unsigned long deadline; int todo; + BUG_ON(EC_MSG_PREAMBLE_COUNT > ec_dev->din_size); + /* Receive data until we see the header byte */ deadline = jiffies + msecs_to_jiffies(EC_MSG_DEADLINE_MS); while (true) { unsigned long start_jiffies = jiffies; - memset(&trans, 0, sizeof(trans)); - trans.cs_change = 1; - trans.rx_buf = ptr = ec_dev->din; - trans.len = EC_MSG_PREAMBLE_COUNT; - - spi_message_init(&msg); - spi_message_add_tail(&trans, &msg); - ret = spi_sync(ec_spi->spi, &msg); - if (ret < 0) { - dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + ret = receive_n_bytes(ec_dev, + ec_dev->din, + EC_MSG_PREAMBLE_COUNT); + if (ret < 0) return ret; - } + ptr = ec_dev->din; for (end = ptr + EC_MSG_PREAMBLE_COUNT; ptr != end; ptr++) { - if (*ptr == EC_MSG_HEADER) { + if (*ptr == EC_SPI_FRAME_START) { dev_dbg(ec_dev->dev, "msg found at %zd\n", ptr - ec_dev->din); break; @@ -187,21 +340,9 @@ static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, dev_dbg(ec_dev->dev, "loop, todo=%d, need_len=%d, ptr=%zd\n", todo, need_len, ptr - ec_dev->din); - memset(&trans, 0, sizeof(trans)); - trans.cs_change = 1; - trans.rx_buf = ptr; - trans.len = todo; - spi_message_init(&msg); - spi_message_add_tail(&trans, &msg); - - /* send command to EC and read answer */ - BUG_ON((u8 *)trans.rx_buf - ec_dev->din + todo > - ec_dev->din_size); - ret = spi_sync(ec_spi->spi, &msg); - if (ret < 0) { - dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + ret = receive_n_bytes(ec_dev, ptr, todo); + if (ret < 0) return ret; - } debug_packet(ec_dev->dev, "interim", ptr, todo); ptr += todo; @@ -213,6 +354,128 @@ static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, return 0; } +/** + * cros_ec_pkt_xfer_spi - Transfer a packet over SPI and receive the reply + * + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + */ +static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + struct ec_host_request *request; + struct ec_host_response *response; + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct spi_transfer trans; + struct spi_message msg; + int i, len; + u8 *ptr; + u8 *rx_buf; + u8 sum; + int ret = 0, final_ret; + + len = cros_ec_prepare_tx(ec_dev, ec_msg); + request = (struct ec_host_request *)ec_dev->dout; + dev_dbg(ec_dev->dev, "prepared, len=%d\n", len); + + /* If it's too soon to do another transaction, wait */ + if (ec_spi->last_transfer_ns) { + unsigned long delay; /* The delay completed so far */ + + delay = ktime_get_ns() - ec_spi->last_transfer_ns; + if (delay < EC_SPI_RECOVERY_TIME_NS) + ndelay(EC_SPI_RECOVERY_TIME_NS - delay); + } + + rx_buf = kzalloc(len, GFP_KERNEL); + if (!rx_buf) { + ret = -ENOMEM; + goto exit; + } + + /* Transmit phase - send our message */ + memset(&trans, 0, sizeof(trans)); + trans.tx_buf = ec_dev->dout; + trans.rx_buf = rx_buf; + trans.len = len; + trans.cs_change = 1; + spi_message_init(&msg); + spi_message_add_tail(&trans, &msg); + ret = spi_sync(ec_spi->spi, &msg); + + /* Get the response */ + if (!ret) { + /* Verify that EC can process command */ + for (i = 0; i < len; i++) { + switch (rx_buf[i]) { + case EC_SPI_PAST_END: + case EC_SPI_RX_BAD_DATA: + case EC_SPI_NOT_READY: + ret = -EAGAIN; + ec_msg->result = EC_RES_IN_PROGRESS; + default: + break; + } + if (ret) + break; + } + if (!ret) + ret = cros_ec_spi_receive_packet(ec_dev, + ec_msg->insize + sizeof(*response)); + } else { + dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + } + + final_ret = terminate_request(ec_dev); + if (!ret) + ret = final_ret; + if (ret < 0) + goto exit; + + ptr = ec_dev->din; + + /* check response error code */ + response = (struct ec_host_response *)ptr; + ec_msg->result = response->result; + + ret = cros_ec_check_result(ec_dev, ec_msg); + if (ret) + goto exit; + + len = response->data_len; + sum = 0; + if (len > ec_msg->insize) { + dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)", + len, ec_msg->insize); + ret = -EMSGSIZE; + goto exit; + } + + for (i = 0; i < sizeof(*response); i++) + sum += ptr[i]; + + /* copy response packet payload and compute checksum */ + memcpy(ec_msg->data, ptr + sizeof(*response), len); + for (i = 0; i < len; i++) + sum += ec_msg->data[i]; + + if (sum) { + dev_err(ec_dev->dev, + "bad packet checksum, calculated %x\n", + sum); + ret = -EBADMSG; + goto exit; + } + + ret = len; +exit: + kfree(rx_buf); + if (ec_msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + return ret; +} + /** * cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply * @@ -227,6 +490,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, struct spi_message msg; int i, len; u8 *ptr; + u8 *rx_buf; int sum; int ret = 0, final_ret; @@ -242,10 +506,17 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, ndelay(EC_SPI_RECOVERY_TIME_NS - delay); } + rx_buf = kzalloc(len, GFP_KERNEL); + if (!rx_buf) { + ret = -ENOMEM; + goto exit; + } + /* Transmit phase - send our message */ debug_packet(ec_dev->dev, "out", ec_dev->dout, len); memset(&trans, 0, sizeof(trans)); trans.tx_buf = ec_dev->dout; + trans.rx_buf = rx_buf; trans.len = len; trans.cs_change = 1; spi_message_init(&msg); @@ -254,29 +525,32 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, /* Get the response */ if (!ret) { - ret = cros_ec_spi_receive_response(ec_dev, - ec_msg->insize + EC_MSG_TX_PROTO_BYTES); + /* Verify that EC can process command */ + for (i = 0; i < len; i++) { + switch (rx_buf[i]) { + case EC_SPI_PAST_END: + case EC_SPI_RX_BAD_DATA: + case EC_SPI_NOT_READY: + ret = -EAGAIN; + ec_msg->result = EC_RES_IN_PROGRESS; + default: + break; + } + if (ret) + break; + } + if (!ret) + ret = cros_ec_spi_receive_response(ec_dev, + ec_msg->insize + EC_MSG_TX_PROTO_BYTES); } else { dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); } - /* - * Turn off CS, possibly adding a delay to ensure the rising edge - * doesn't come too soon after the end of the data. - */ - spi_message_init(&msg); - memset(&trans, 0, sizeof(trans)); - trans.delay_usecs = ec_spi->end_of_msg_delay; - spi_message_add_tail(&trans, &msg); - - final_ret = spi_sync(ec_spi->spi, &msg); - ec_spi->last_transfer_ns = ktime_get_ns(); + final_ret = terminate_request(ec_dev); if (!ret) ret = final_ret; - if (ret < 0) { - dev_err(ec_dev->dev, "spi transfer failed: %d\n", ret); + if (ret < 0) goto exit; - } ptr = ec_dev->din; @@ -315,6 +589,7 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, ret = len; exit: + kfree(rx_buf); if (ec_msg->command == EC_CMD_REBOOT_EC) msleep(EC_REBOOT_DELAY_MS); @@ -361,7 +636,7 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_dev->priv = ec_spi; ec_dev->irq = spi->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_spi; - ec_dev->pkt_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_spi; ec_dev->ec_name = ec_spi->spi->modalias; ec_dev->phys_name = dev_name(&ec_spi->spi->dev); ec_dev->din_size = EC_MSG_PREAMBLE_COUNT + @@ -369,6 +644,7 @@ static int cros_ec_spi_probe(struct spi_device *spi) sizeof(struct ec_response_get_protocol_info); ec_dev->dout_size = sizeof(struct ec_host_request); + err = cros_ec_register(ec_dev); if (err) { dev_err(dev, "cannot register EC\n"); diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 05aeb559275f..cac24d356c91 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -46,6 +46,77 @@ static int ec_response_timed_out(void) return 1; } +static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, + struct cros_ec_command *msg) +{ + struct ec_host_request *request; + struct ec_host_response response; + u8 sum = 0; + int i; + int ret = 0; + u8 *dout; + + ret = cros_ec_prepare_tx(ec, msg); + + /* Write buffer */ + for (i = 0; i < ret; i++) + outb(ec->dout[i], EC_LPC_ADDR_HOST_PACKET + i); + + request = (struct ec_host_request *)ec->dout; + + /* Here we go */ + outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD); + + if (ec_response_timed_out()) { + dev_warn(ec->dev, "EC responsed timed out\n"); + ret = -EIO; + goto done; + } + + /* Check result */ + msg->result = inb(EC_LPC_ADDR_HOST_DATA); + ret = cros_ec_check_result(ec, msg); + if (ret) + goto done; + + /* Read back response */ + dout = (u8 *)&response; + for (i = 0; i < sizeof(response); i++) { + dout[i] = inb(EC_LPC_ADDR_HOST_PACKET + i); + sum += dout[i]; + } + + msg->result = response.result; + + if (response.data_len > msg->insize) { + dev_err(ec->dev, + "packet too long (%d bytes, expected %d)", + response.data_len, msg->insize); + ret = -EMSGSIZE; + goto done; + } + + /* Read response and process checksum */ + for (i = 0; i < response.data_len; i++) { + msg->data[i] = + inb(EC_LPC_ADDR_HOST_PACKET + sizeof(response) + i); + sum += msg->data[i]; + } + + if (sum) { + dev_err(ec->dev, + "bad packet checksum %02x\n", + response.checksum); + ret = -EBADMSG; + goto done; + } + + /* Return actual amount of data received */ + ret = response.data_len; +done: + return ret; +} + static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, struct cros_ec_command *msg) { @@ -215,7 +286,7 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) ec_dev->ec_name = pdev->name; ec_dev->phys_name = dev_name(dev); ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; - ec_dev->pkt_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_lpc; ec_dev->cmd_readmem = cros_ec_lpc_readmem; ec_dev->din_size = sizeof(struct ec_host_response) + sizeof(struct ec_response_get_protocol_info); diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 59d909434efd..92e13aaa450c 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -21,6 +21,12 @@ #include #include +/* + * The EC is unresponsive for a time after a reboot command. Add a + * simple delay to make sure that the bus stays locked. + */ +#define EC_REBOOT_DELAY_MS 50 + /* * Max bus-specific overhead incurred by request/responses. * I2C requires 1 additional byte for requests. From 57b33ff077beebb68481a2b6b8e5fe58ca998169 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Tue, 9 Jun 2015 13:04:47 +0200 Subject: [PATCH 20/59] mfd: cros_ec: Support multiple EC in a system Chromebooks can have more than one Embedded Controller so the cros_ec device id has to be incremented for each EC registered. Add a new structure to represent multiple EC as different char devices (e.g: /dev/cros_ec, /dev/cros_pd). It connects to cros_ec_device and allows sysfs inferface for cros_pd. Also reduce number of allocated objects, make chromeos sysfs class object a static and add refcounting to prevent object deletion while command is in progress. Signed-off-by: Gwendal Grignou Reviewed-by: Dmitry Torokhov Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/input/keyboard/cros_ec_keyb.c | 2 +- drivers/mfd/cros_ec.c | 54 +++++++-- drivers/mfd/cros_ec_i2c.c | 1 - drivers/mfd/cros_ec_spi.c | 1 - drivers/platform/chrome/cros_ec_dev.c | 130 ++++++++++++++------- drivers/platform/chrome/cros_ec_dev.h | 7 -- drivers/platform/chrome/cros_ec_lightbar.c | 75 ++++++------ drivers/platform/chrome/cros_ec_lpc.c | 1 - drivers/platform/chrome/cros_ec_sysfs.c | 48 +++----- include/linux/mfd/cros_ec.h | 44 ++++++- 10 files changed, 236 insertions(+), 127 deletions(-) diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index 974154a74505..b01966dc7eb3 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c @@ -275,7 +275,7 @@ static int cros_ec_keyb_probe(struct platform_device *pdev) ckdev->dev = dev; dev_set_drvdata(&pdev->dev, ckdev); - idev->name = ec->ec_name; + idev->name = CROS_EC_DEV_NAME; idev->phys = ec->phys_name; __set_bit(EV_REP, idev->evbit); diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index 08d82bfc5268..11b1884bce62 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -24,11 +24,29 @@ #include #include -static const struct mfd_cell cros_devs[] = { - { - .name = "cros-ec-ctl", - .id = PLATFORM_DEVID_AUTO, - }, +#define CROS_EC_DEV_EC_INDEX 0 +#define CROS_EC_DEV_PD_INDEX 1 + +struct cros_ec_platform ec_p = { + .ec_name = CROS_EC_DEV_NAME, + .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX), +}; + +struct cros_ec_platform pd_p = { + .ec_name = CROS_EC_DEV_PD_NAME, + .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), +}; + +struct mfd_cell ec_cell = { + .name = "cros-ec-ctl", + .platform_data = &ec_p, + .pdata_size = sizeof(ec_p), +}; + +struct mfd_cell ec_pd_cell = { + .name = "cros-ec-ctl", + .platform_data = &pd_p, + .pdata_size = sizeof(pd_p), }; int cros_ec_register(struct cros_ec_device *ec_dev) @@ -52,14 +70,34 @@ int cros_ec_register(struct cros_ec_device *ec_dev) cros_ec_query_all(ec_dev); - err = mfd_add_devices(dev, 0, cros_devs, - ARRAY_SIZE(cros_devs), + err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, &ec_cell, 1, NULL, ec_dev->irq, NULL); if (err) { - dev_err(dev, "failed to add mfd devices\n"); + dev_err(dev, + "Failed to register Embedded Controller subdevice %d\n", + err); return err; } + if (ec_dev->max_passthru) { + /* + * Register a PD device as well on top of this device. + * We make the following assumptions: + * - behind an EC, we have a pd + * - only one device added. + * - the EC is responsive at init time (it is not true for a + * sensor hub. + */ + err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, + &ec_pd_cell, 1, NULL, ec_dev->irq, NULL); + if (err) { + dev_err(dev, + "Failed to register Power Delivery subdevice %d\n", + err); + return err; + } + } + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { err = of_platform_populate(dev->of_node, NULL, NULL, dev); if (err) { diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c index 22e8a4ae1711..b9a0963ca5c3 100644 --- a/drivers/mfd/cros_ec_i2c.c +++ b/drivers/mfd/cros_ec_i2c.c @@ -302,7 +302,6 @@ static int cros_ec_i2c_probe(struct i2c_client *client, ec_dev->irq = client->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_i2c; ec_dev->pkt_xfer = cros_ec_pkt_xfer_i2c; - ec_dev->ec_name = client->name; ec_dev->phys_name = client->adapter->name; ec_dev->din_size = sizeof(struct ec_host_response_i2c) + sizeof(struct ec_response_get_protocol_info); diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index 4e6f2f6b1095..faba03e2f1ef 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -637,7 +637,6 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_dev->irq = spi->irq; ec_dev->cmd_xfer = cros_ec_cmd_xfer_spi; ec_dev->pkt_xfer = cros_ec_pkt_xfer_spi; - ec_dev->ec_name = ec_spi->spi->modalias; ec_dev->phys_name = dev_name(&ec_spi->spi->dev); ec_dev->din_size = EC_MSG_PREAMBLE_COUNT + sizeof(struct ec_host_response) + diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index e91ced1cb8ce..e8fcdc237029 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -27,11 +27,22 @@ /* Device variables */ #define CROS_MAX_DEV 128 -static struct class *cros_class; static int ec_major; +static const struct attribute_group *cros_ec_groups[] = { + &cros_ec_attr_group, + &cros_ec_lightbar_attr_group, + NULL, +}; + +static struct class cros_class = { + .owner = THIS_MODULE, + .name = "chromeos", + .dev_groups = cros_ec_groups, +}; + /* Basic communication */ -static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) +static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen) { struct ec_response_get_version *resp; static const char * const current_image_name[] = { @@ -45,11 +56,11 @@ static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) return -ENOMEM; msg->version = 0; - msg->command = EC_CMD_GET_VERSION; + msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; msg->insize = sizeof(*resp); msg->outsize = 0; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; @@ -78,8 +89,10 @@ exit: /* Device file ops */ static int ec_device_open(struct inode *inode, struct file *filp) { - filp->private_data = container_of(inode->i_cdev, - struct cros_ec_device, cdev); + struct cros_ec_dev *ec = container_of(inode->i_cdev, + struct cros_ec_dev, cdev); + filp->private_data = ec; + nonseekable_open(inode, filp); return 0; } @@ -91,7 +104,7 @@ static int ec_device_release(struct inode *inode, struct file *filp) static ssize_t ec_device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) { - struct cros_ec_device *ec = filp->private_data; + struct cros_ec_dev *ec = filp->private_data; char msg[sizeof(struct ec_response_get_version) + sizeof(CROS_EC_DEV_VERSION)]; size_t count; @@ -114,7 +127,7 @@ static ssize_t ec_device_read(struct file *filp, char __user *buffer, } /* Ioctls */ -static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) +static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) { long ret; struct cros_ec_command u_cmd; @@ -133,7 +146,8 @@ static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) goto exit; } - ret = cros_ec_cmd_xfer(ec, s_cmd); + s_cmd->command += ec->cmd_offset; + ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd); /* Only copy data to userland if data was received. */ if (ret < 0) goto exit; @@ -145,19 +159,21 @@ exit: return ret; } -static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) +static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg) { + struct cros_ec_device *ec_dev = ec->ec_dev; struct cros_ec_readmem s_mem = { }; long num; /* Not every platform supports direct reads */ - if (!ec->cmd_readmem) + if (!ec_dev->cmd_readmem) return -ENOTTY; if (copy_from_user(&s_mem, arg, sizeof(s_mem))) return -EFAULT; - num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer); + num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, + s_mem.buffer); if (num <= 0) return num; @@ -170,7 +186,7 @@ static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) static long ec_device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { - struct cros_ec_device *ec = filp->private_data; + struct cros_ec_dev *ec = filp->private_data; if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) return -ENOTTY; @@ -193,45 +209,81 @@ static const struct file_operations fops = { .unlocked_ioctl = ec_device_ioctl, }; +static void __remove(struct device *dev) +{ + struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, + class_dev); + kfree(ec); +} + static int ec_device_probe(struct platform_device *pdev) { - struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); - int retval = -ENOTTY; - dev_t devno = MKDEV(ec_major, 0); + int retval = -ENOMEM; + struct device *dev = &pdev->dev; + struct cros_ec_platform *ec_platform = dev_get_platdata(dev); + dev_t devno = MKDEV(ec_major, pdev->id); + struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); - /* Instantiate it (and remember the EC) */ + if (!ec) + return retval; + + dev_set_drvdata(dev, ec); + ec->ec_dev = dev_get_drvdata(dev->parent); + ec->dev = dev; + ec->cmd_offset = ec_platform->cmd_offset; + device_initialize(&ec->class_dev); cdev_init(&ec->cdev, &fops); + /* + * Add the character device + * Link cdev to the class device to be sure device is not used + * before unbinding it. + */ + ec->cdev.kobj.parent = &ec->class_dev.kobj; retval = cdev_add(&ec->cdev, devno, 1); if (retval) { - dev_err(&pdev->dev, ": failed to add character device\n"); - return retval; + dev_err(dev, ": failed to add character device\n"); + goto cdev_add_failed; } - ec->vdev = device_create(cros_class, NULL, devno, ec, - CROS_EC_DEV_NAME); - if (IS_ERR(ec->vdev)) { - retval = PTR_ERR(ec->vdev); - dev_err(&pdev->dev, ": failed to create device\n"); - cdev_del(&ec->cdev); - return retval; + /* + * Add the class device + * Link to the character device for creating the /dev entry + * in devtmpfs. + */ + ec->class_dev.devt = ec->cdev.dev; + ec->class_dev.class = &cros_class; + ec->class_dev.parent = dev; + ec->class_dev.release = __remove; + + retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); + if (retval) { + dev_err(dev, "dev_set_name failed => %d\n", retval); + goto set_named_failed; } - /* Initialize extra interfaces */ - ec_dev_sysfs_init(ec); - ec_dev_lightbar_init(ec); + retval = device_add(&ec->class_dev); + if (retval) { + dev_err(dev, "device_register failed => %d\n", retval); + goto dev_reg_failed; + } return 0; + +dev_reg_failed: +set_named_failed: + dev_set_drvdata(dev, NULL); + cdev_del(&ec->cdev); +cdev_add_failed: + kfree(ec); + return retval; } static int ec_device_remove(struct platform_device *pdev) { - struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); - - ec_dev_lightbar_remove(ec); - ec_dev_sysfs_remove(ec); - device_destroy(cros_class, MKDEV(ec_major, 0)); + struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); cdev_del(&ec->cdev); + device_unregister(&ec->class_dev); return 0; } @@ -248,10 +300,10 @@ static int __init cros_ec_dev_init(void) int ret; dev_t dev = 0; - cros_class = class_create(THIS_MODULE, "chromeos"); - if (IS_ERR(cros_class)) { + ret = class_register(&cros_class); + if (ret) { pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); - return PTR_ERR(cros_class); + return ret; } /* Get a range of minor numbers (starting with 0) to work with */ @@ -273,7 +325,7 @@ static int __init cros_ec_dev_init(void) failed_devreg: unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); failed_chrdevreg: - class_destroy(cros_class); + class_unregister(&cros_class); return ret; } @@ -281,7 +333,7 @@ static void __exit cros_ec_dev_exit(void) { platform_driver_unregister(&cros_ec_dev_driver); unregister_chrdev(ec_major, CROS_EC_DEV_NAME); - class_destroy(cros_class); + class_unregister(&cros_class); } module_init(cros_ec_dev_init); diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index 45d67f7e518c..bfd2c84c3571 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -24,7 +24,6 @@ #include #include -#define CROS_EC_DEV_NAME "cros_ec" #define CROS_EC_DEV_VERSION "1.0.0" /* @@ -44,10 +43,4 @@ struct cros_ec_readmem { #define CROS_EC_DEV_IOCXCMD _IOWR(CROS_EC_DEV_IOC, 0, struct cros_ec_command) #define CROS_EC_DEV_IOCRDMEM _IOWR(CROS_EC_DEV_IOC, 1, struct cros_ec_readmem) -void ec_dev_sysfs_init(struct cros_ec_device *); -void ec_dev_sysfs_remove(struct cros_ec_device *); - -void ec_dev_lightbar_init(struct cros_ec_device *); -void ec_dev_lightbar_remove(struct cros_ec_device *); - #endif /* _CROS_EC_DEV_H_ */ diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 6e1986a2dce1..144e09df9b84 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -92,7 +92,7 @@ out: return ret; } -static struct cros_ec_command *alloc_lightbar_cmd_msg(void) +static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec) { struct cros_ec_command *msg; int len; @@ -105,14 +105,14 @@ static struct cros_ec_command *alloc_lightbar_cmd_msg(void) return NULL; msg->version = 0; - msg->command = EC_CMD_LIGHTBAR_CMD; + msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset; msg->outsize = sizeof(struct ec_params_lightbar); msg->insize = sizeof(struct ec_response_lightbar); return msg; } -static int get_lightbar_version(struct cros_ec_device *ec, +static int get_lightbar_version(struct cros_ec_dev *ec, uint32_t *ver_ptr, uint32_t *flg_ptr) { struct ec_params_lightbar *param; @@ -120,13 +120,13 @@ static int get_lightbar_version(struct cros_ec_device *ec, struct cros_ec_command *msg; int ret; - msg = alloc_lightbar_cmd_msg(); + msg = alloc_lightbar_cmd_msg(ec); if (!msg) return 0; param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_VERSION; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) { ret = 0; goto exit; @@ -165,7 +165,8 @@ static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { uint32_t version = 0, flags = 0; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); int ret; ret = lb_throttle(); @@ -187,12 +188,13 @@ static ssize_t brightness_store(struct device *dev, struct cros_ec_command *msg; int ret; unsigned int val; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); if (kstrtouint(buf, 0, &val)) return -EINVAL; - msg = alloc_lightbar_cmd_msg(); + msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; @@ -203,7 +205,7 @@ static ssize_t brightness_store(struct device *dev, if (ret) goto exit; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; @@ -231,11 +233,12 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, { struct ec_params_lightbar *param; struct cros_ec_command *msg; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); unsigned int val[4]; int ret, i = 0, j = 0, ok = 0; - msg = alloc_lightbar_cmd_msg(); + msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; @@ -268,7 +271,7 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, return ret; } - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; @@ -304,9 +307,10 @@ static ssize_t sequence_show(struct device *dev, struct ec_response_lightbar *resp; struct cros_ec_command *msg; int ret; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); - msg = alloc_lightbar_cmd_msg(); + msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; @@ -316,7 +320,7 @@ static ssize_t sequence_show(struct device *dev, if (ret) goto exit; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; @@ -345,9 +349,10 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, struct cros_ec_command *msg; unsigned int num; int ret, len; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); - msg = alloc_lightbar_cmd_msg(); + msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; @@ -372,7 +377,7 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, if (ret) return ret; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) return ret; @@ -397,25 +402,27 @@ static struct attribute *__lb_cmds_attrs[] = { &dev_attr_sequence.attr, NULL, }; -static struct attribute_group lb_cmds_attr_group = { - .name = "lightbar", - .attrs = __lb_cmds_attrs, -}; -void ec_dev_lightbar_init(struct cros_ec_device *ec) +static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) { - int ret = 0; + struct device *dev = container_of(kobj, struct device, kobj); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); + struct platform_device *pdev = container_of(ec->dev, + struct platform_device, dev); + if (pdev->id != 0) + return 0; /* Only instantiate this stuff if the EC has a lightbar */ - if (!get_lightbar_version(ec, NULL, NULL)) - return; - - ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); - if (ret) - pr_warn("sysfs_create_group() failed: %d\n", ret); + if (get_lightbar_version(ec, NULL, NULL)) + return a->mode; + else + return 0; } -void ec_dev_lightbar_remove(struct cros_ec_device *ec) -{ - sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); -} +struct attribute_group cros_ec_lightbar_attr_group = { + .name = "lightbar", + .attrs = __lb_cmds_attrs, + .is_visible = cros_ec_lightbar_attrs_are_visible, +}; diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index cac24d356c91..bdd77ce45f05 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -283,7 +283,6 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ec_dev); ec_dev->dev = dev; - ec_dev->ec_name = pdev->name; ec_dev->phys_name = dev_name(dev); ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; ec_dev->pkt_xfer = cros_ec_pkt_xfer_lpc; diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index 78ba82d670cb..f3baf9973989 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -72,7 +72,8 @@ static ssize_t store_ec_reboot(struct device *dev, int got_cmd = 0, offset = 0; int i; int ret; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); msg = kmalloc(sizeof(*msg) + sizeof(*param), GFP_KERNEL); if (!msg) @@ -112,10 +113,10 @@ static ssize_t store_ec_reboot(struct device *dev, } msg->version = 0; - msg->command = EC_CMD_REBOOT_EC; + msg->command = EC_CMD_REBOOT_EC + ec->cmd_offset; msg->outsize = sizeof(*param); msg->insize = 0; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) { count = ret; goto exit; @@ -139,7 +140,8 @@ static ssize_t show_ec_version(struct device *dev, struct cros_ec_command *msg; int ret; int count = 0; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL); if (!msg) @@ -147,10 +149,10 @@ static ssize_t show_ec_version(struct device *dev, /* Get versions. RW may change. */ msg->version = 0; - msg->command = EC_CMD_GET_VERSION; + msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; msg->insize = sizeof(*r_ver); msg->outsize = 0; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) { count = ret; goto exit; @@ -175,9 +177,9 @@ static ssize_t show_ec_version(struct device *dev, image_names[r_ver->current_image] : "?")); /* Get build info. */ - msg->command = EC_CMD_GET_BUILD_INFO; + msg->command = EC_CMD_GET_BUILD_INFO + ec->cmd_offset; msg->insize = EC_HOST_PARAM_SIZE; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) count += scnprintf(buf + count, PAGE_SIZE - count, "Build info: XFER ERROR %d\n", ret); @@ -191,9 +193,9 @@ static ssize_t show_ec_version(struct device *dev, } /* Get chip info. */ - msg->command = EC_CMD_GET_CHIP_INFO; + msg->command = EC_CMD_GET_CHIP_INFO + ec->cmd_offset; msg->insize = sizeof(*r_chip); - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) count += scnprintf(buf + count, PAGE_SIZE - count, "Chip info: XFER ERROR %d\n", ret); @@ -215,9 +217,9 @@ static ssize_t show_ec_version(struct device *dev, } /* Get board version */ - msg->command = EC_CMD_GET_BOARD_VERSION; + msg->command = EC_CMD_GET_BOARD_VERSION + ec->cmd_offset; msg->insize = sizeof(*r_board); - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) count += scnprintf(buf + count, PAGE_SIZE - count, "Board version: XFER ERROR %d\n", ret); @@ -243,7 +245,8 @@ static ssize_t show_ec_flashinfo(struct device *dev, struct ec_response_flash_info *resp; struct cros_ec_command *msg; int ret; - struct cros_ec_device *ec = dev_get_drvdata(dev); + struct cros_ec_dev *ec = container_of(dev, + struct cros_ec_dev, class_dev); msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); if (!msg) @@ -251,10 +254,10 @@ static ssize_t show_ec_flashinfo(struct device *dev, /* The flash info shouldn't ever change, but ask each time anyway. */ msg->version = 0; - msg->command = EC_CMD_FLASH_INFO; + msg->command = EC_CMD_FLASH_INFO + ec->cmd_offset; msg->insize = sizeof(*resp); msg->outsize = 0; - ret = cros_ec_cmd_xfer(ec, msg); + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; if (msg->result != EC_RES_SUCCESS) { @@ -288,20 +291,7 @@ static struct attribute *__ec_attrs[] = { NULL, }; -static struct attribute_group ec_attr_group = { +struct attribute_group cros_ec_attr_group = { .attrs = __ec_attrs, }; -void ec_dev_sysfs_init(struct cros_ec_device *ec) -{ - int error; - - error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); - if (error) - pr_warn("failed to create group: %d\n", error); -} - -void ec_dev_sysfs_remove(struct cros_ec_device *ec) -{ - sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); -} diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 92e13aaa450c..da72671a42fa 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -17,10 +17,14 @@ #define __LINUX_MFD_CROS_EC_H #include +#include #include #include #include +#define CROS_EC_DEV_NAME "cros_ec" +#define CROS_EC_DEV_PD_NAME "cros_pd" + /* * The EC is unresponsive for a time after a reboot command. Add a * simple delay to make sure that the bus stays locked. @@ -71,11 +75,8 @@ struct cros_ec_command { /** * struct cros_ec_device - Information about a ChromeOS EC device * - * @ec_name: name of EC device (e.g. 'chromeos-ec') * @phys_name: name of physical comms layer (e.g. 'i2c-4') * @dev: Device pointer for physical comms device - * @vdev: Device pointer for virtual comms device - * @cdev: Character device structure for virtual comms device * @was_wake_device: true if this device was set to wake the system from * sleep at the last suspend * @cmd_readmem: direct read of the EC memory-mapped region, if supported @@ -87,6 +88,7 @@ struct cros_ec_command { * * @priv: Private data * @irq: Interrupt to use + * @id: Device id * @din: input buffer (for data from EC) * @dout: output buffer (for data to EC) * \note @@ -109,11 +111,8 @@ struct cros_ec_command { struct cros_ec_device { /* These are used by other drivers that want to talk to the EC */ - const char *ec_name; const char *phys_name; struct device *dev; - struct device *vdev; - struct cdev cdev; bool was_wake_device; struct class *cros_class; int (*cmd_readmem)(struct cros_ec_device *ec, unsigned int offset, @@ -138,6 +137,35 @@ struct cros_ec_device { struct mutex lock; }; +/* struct cros_ec_platform - ChromeOS EC platform information + * + * @ec_name: name of EC device (e.g. 'cros-ec', 'cros-pd', ...) + * used in /dev/ and sysfs. + * @cmd_offset: offset to apply for each command. Set when + * registering a devicde behind another one. + */ +struct cros_ec_platform { + const char *ec_name; + u16 cmd_offset; +}; + +/* + * struct cros_ec_dev - ChromeOS EC device entry point + * + * @class_dev: Device structure used in sysfs + * @cdev: Character device structure in /dev + * @ec_dev: cros_ec_device structure to talk to the physical device + * @dev: pointer to the platform device + * @cmd_offset: offset to apply for each command. + */ +struct cros_ec_dev { + struct device class_dev; + struct cdev cdev; + struct cros_ec_device *ec_dev; + struct device *dev; + u16 cmd_offset; +}; + /** * cros_ec_suspend - Handle a suspend operation for the ChromeOS EC device * @@ -224,4 +252,8 @@ int cros_ec_register(struct cros_ec_device *ec_dev); */ int cros_ec_query_all(struct cros_ec_device *ec_dev); +/* sysfs stuff */ +extern struct attribute_group cros_ec_attr_group; +extern struct attribute_group cros_ec_lightbar_attr_group; + #endif /* __LINUX_MFD_CROS_EC_H */ From f44c21ff6d2dc76957ddbf26fdf442ee5a9cf3c0 Mon Sep 17 00:00:00 2001 From: Alexandru M Stan Date: Tue, 9 Jun 2015 13:04:48 +0200 Subject: [PATCH 21/59] mfd: cros_ec: spi: Add a DT property to delay asserting the CS Some ECs need a little time for waking up before they can accept SPI data at a high speed. Add a "google,cros-ec-spi-pre-delay" property to the DT binding to configure this. If this property isn't set, then no delay will be added. However, if set it will cause a delay equal to the value passed to it to be inserted at the beginning of a transaction. Signed-off-by: Alexandru M Stan Reviewed-by: Doug Anderson Signed-off-by: Chris Zhong Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/cros-ec.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/cros-ec.txt b/Documentation/devicetree/bindings/mfd/cros-ec.txt index 8009c3d87f33..1777916e9e28 100644 --- a/Documentation/devicetree/bindings/mfd/cros-ec.txt +++ b/Documentation/devicetree/bindings/mfd/cros-ec.txt @@ -18,6 +18,10 @@ Required properties (SPI): - reg: SPI chip select Optional properties (SPI): +- google,cros-ec-spi-pre-delay: Some implementations of the EC need a little + time to wake up from sleep before they can receive SPI transfers at a high + clock rate. This property specifies the delay, in usecs, between the + assertion of the CS to the start of the first clock pulse. - google,cros-ec-spi-msg-delay: Some implementations of the EC require some additional processing time in order to accept new transactions. If the delay between transactions is not long enough the EC may not be able to respond From ff4378f4b813d5aa26bbf814a9060638dab1fbbf Mon Sep 17 00:00:00 2001 From: Alexandru M Stan Date: Tue, 9 Jun 2015 13:04:49 +0200 Subject: [PATCH 22/59] mfd: cros_ec: spi: Add delay for asserting CS Some ECs need a little time for waking up before they can accept SPI data at a high speed. This is configurable via a DT property "google,cros-ec-spi-pre-delay". This patch makes the cros_ec_spi driver to cause a delay before the beginning of a SPI transaction, to make sure that the EC has already woken up, if the property has been defined in the DTS. Signed-off-by: Alexandru M Stan Reviewed-by: Doug Anderson Signed-off-by: Chris Zhong Signed-off-by: Javier Martinez Canillas Tested-by: Heiko Stuebner Acked-by: Lee Jones Acked-by: Olof Johansson Signed-off-by: Lee Jones --- drivers/mfd/cros_ec_spi.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index faba03e2f1ef..16f228dc243f 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -71,12 +71,15 @@ * @spi: SPI device we are connected to * @last_transfer_ns: time that we last finished a transfer, or 0 if there * if no record + * @start_of_msg_delay: used to set the delay_usecs on the spi_transfer that + * is sent when we want to turn on CS at the start of a transaction. * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that * is sent when we want to turn off CS at the end of a transaction. */ struct cros_ec_spi { struct spi_device *spi; s64 last_transfer_ns; + unsigned int start_of_msg_delay; unsigned int end_of_msg_delay; }; @@ -366,7 +369,7 @@ static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, struct ec_host_request *request; struct ec_host_response *response; struct cros_ec_spi *ec_spi = ec_dev->priv; - struct spi_transfer trans; + struct spi_transfer trans, trans_delay; struct spi_message msg; int i, len; u8 *ptr; @@ -393,13 +396,23 @@ static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, goto exit; } + /* + * Leave a gap between CS assertion and clocking of data to allow the + * EC time to wakeup. + */ + spi_message_init(&msg); + if (ec_spi->start_of_msg_delay) { + memset(&trans_delay, 0, sizeof(trans_delay)); + trans_delay.delay_usecs = ec_spi->start_of_msg_delay; + spi_message_add_tail(&trans_delay, &msg); + } + /* Transmit phase - send our message */ memset(&trans, 0, sizeof(trans)); trans.tx_buf = ec_dev->dout; trans.rx_buf = rx_buf; trans.len = len; trans.cs_change = 1; - spi_message_init(&msg); spi_message_add_tail(&trans, &msg); ret = spi_sync(ec_spi->spi, &msg); @@ -602,6 +615,10 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) u32 val; int ret; + ret = of_property_read_u32(np, "google,cros-ec-spi-pre-delay", &val); + if (!ret) + ec_spi->start_of_msg_delay = val; + ret = of_property_read_u32(np, "google,cros-ec-spi-msg-delay", &val); if (!ret) ec_spi->end_of_msg_delay = val; From cf649e00769a401d317352c4fe405169ddf61e1f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Mon, 15 Jun 2015 21:39:39 +0800 Subject: [PATCH 23/59] mfd: cros_ec: Staticise some newly introduced structures Signed-off-by: Lee Jones --- drivers/mfd/cros_ec.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index 11b1884bce62..0eee63542038 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -27,23 +27,23 @@ #define CROS_EC_DEV_EC_INDEX 0 #define CROS_EC_DEV_PD_INDEX 1 -struct cros_ec_platform ec_p = { +static struct cros_ec_platform ec_p = { .ec_name = CROS_EC_DEV_NAME, .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX), }; -struct cros_ec_platform pd_p = { +static struct cros_ec_platform pd_p = { .ec_name = CROS_EC_DEV_PD_NAME, .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), }; -struct mfd_cell ec_cell = { +static const struct mfd_cell ec_cell = { .name = "cros-ec-ctl", .platform_data = &ec_p, .pdata_size = sizeof(ec_p), }; -struct mfd_cell ec_pd_cell = { +static const struct mfd_cell ec_pd_cell = { .name = "cros-ec-ctl", .platform_data = &pd_p, .pdata_size = sizeof(pd_p), From 7ce7b26f84cfcbcb04f526f56f685a56ccddf355 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Apr 2015 21:54:13 +0900 Subject: [PATCH 24/59] mfd: Constify regmap and irq configuration data Constify in various drivers configuration data which is not modified: - regmap_irq_chip, - individual regmap_irq's in array, - regmap_config, - irq_domain_ops, Signed-off-by: Krzysztof Kozlowski Signed-off-by: Lee Jones --- drivers/mfd/88pm860x-core.c | 2 +- drivers/mfd/ab8500-core.c | 2 +- drivers/mfd/arizona-irq.c | 2 +- drivers/mfd/da9052-irq.c | 4 ++-- drivers/mfd/da9055-core.c | 6 +++--- drivers/mfd/da9063-irq.c | 4 ++-- drivers/mfd/da9150-core.c | 4 ++-- drivers/mfd/db8500-prcmu.c | 2 +- drivers/mfd/intel_soc_pmic_core.h | 2 +- drivers/mfd/intel_soc_pmic_crc.c | 2 +- drivers/mfd/lp8788-irq.c | 2 +- drivers/mfd/max8925-core.c | 2 +- drivers/mfd/max8997-irq.c | 2 +- drivers/mfd/max8998-irq.c | 2 +- drivers/mfd/mt6397-core.c | 2 +- drivers/mfd/stmpe.c | 2 +- drivers/mfd/tc3589x.c | 2 +- drivers/mfd/tps6586x.c | 2 +- drivers/mfd/twl6030-irq.c | 2 +- drivers/mfd/wm831x-irq.c | 2 +- drivers/mfd/wm8994-irq.c | 6 +++--- include/linux/mfd/da9055/core.h | 2 +- 22 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index d2a85cde68da..e03b7f45b8f7 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -566,7 +566,7 @@ static int pm860x_irq_domain_map(struct irq_domain *d, unsigned int virq, return 0; } -static struct irq_domain_ops pm860x_irq_domain_ops = { +static const struct irq_domain_ops pm860x_irq_domain_ops = { .map = pm860x_irq_domain_map, .xlate = irq_domain_xlate_onetwocell, }; diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index c80a2925f8e5..000da72a0ae9 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -574,7 +574,7 @@ static int ab8500_irq_map(struct irq_domain *d, unsigned int virq, return 0; } -static struct irq_domain_ops ab8500_irq_ops = { +static const struct irq_domain_ops ab8500_irq_ops = { .map = ab8500_irq_map, .xlate = irq_domain_xlate_twocell, }; diff --git a/drivers/mfd/arizona-irq.c b/drivers/mfd/arizona-irq.c index d063b94b94b5..2b9965d53e4e 100644 --- a/drivers/mfd/arizona-irq.c +++ b/drivers/mfd/arizona-irq.c @@ -186,7 +186,7 @@ static int arizona_irq_map(struct irq_domain *h, unsigned int virq, return 0; } -static struct irq_domain_ops arizona_domain_ops = { +static const struct irq_domain_ops arizona_domain_ops = { .map = arizona_irq_map, .xlate = irq_domain_xlate_twocell, }; diff --git a/drivers/mfd/da9052-irq.c b/drivers/mfd/da9052-irq.c index e65ca194fa98..f4cb4613140b 100644 --- a/drivers/mfd/da9052-irq.c +++ b/drivers/mfd/da9052-irq.c @@ -35,7 +35,7 @@ #define DA9052_IRQ_MASK_POS_7 0x40 #define DA9052_IRQ_MASK_POS_8 0x80 -static struct regmap_irq da9052_irqs[] = { +static const struct regmap_irq da9052_irqs[] = { [DA9052_IRQ_DCIN] = { .reg_offset = 0, .mask = DA9052_IRQ_MASK_POS_1, @@ -166,7 +166,7 @@ static struct regmap_irq da9052_irqs[] = { }, }; -static struct regmap_irq_chip da9052_regmap_irq_chip = { +static const struct regmap_irq_chip da9052_regmap_irq_chip = { .name = "da9052_irq", .status_base = DA9052_EVENT_A_REG, .mask_base = DA9052_IRQ_MASK_A_REG, diff --git a/drivers/mfd/da9055-core.c b/drivers/mfd/da9055-core.c index b4d920c1ead1..177e65a12c12 100644 --- a/drivers/mfd/da9055-core.c +++ b/drivers/mfd/da9055-core.c @@ -222,7 +222,7 @@ static bool da9055_register_volatile(struct device *dev, unsigned int reg) } } -static struct regmap_irq da9055_irqs[] = { +static const struct regmap_irq da9055_irqs[] = { [DA9055_IRQ_NONKEY] = { .reg_offset = 0, .mask = DA9055_IRQ_NONKEY_MASK, @@ -245,7 +245,7 @@ static struct regmap_irq da9055_irqs[] = { }, }; -struct regmap_config da9055_regmap_config = { +const struct regmap_config da9055_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -367,7 +367,7 @@ static const struct mfd_cell da9055_devs[] = { }, }; -static struct regmap_irq_chip da9055_regmap_irq_chip = { +static const struct regmap_irq_chip da9055_regmap_irq_chip = { .name = "da9055_irq", .status_base = DA9055_REG_EVENT_A, .mask_base = DA9055_REG_IRQ_MASK_A, diff --git a/drivers/mfd/da9063-irq.c b/drivers/mfd/da9063-irq.c index 822922602ce9..eaf1ec9208b2 100644 --- a/drivers/mfd/da9063-irq.c +++ b/drivers/mfd/da9063-irq.c @@ -34,7 +34,7 @@ struct da9063_irq_data { u8 mask; }; -static struct regmap_irq da9063_irqs[] = { +static const struct regmap_irq da9063_irqs[] = { /* DA9063 event A register */ [DA9063_IRQ_ONKEY] = { .reg_offset = DA9063_REG_EVENT_A_OFFSET, @@ -153,7 +153,7 @@ static struct regmap_irq da9063_irqs[] = { }, }; -static struct regmap_irq_chip da9063_irq_chip = { +static const struct regmap_irq_chip da9063_irq_chip = { .name = "da9063-irq", .irqs = da9063_irqs, .num_irqs = DA9063_NUM_IRQ, diff --git a/drivers/mfd/da9150-core.c b/drivers/mfd/da9150-core.c index 5549817df32e..94b9bbd1a69b 100644 --- a/drivers/mfd/da9150-core.c +++ b/drivers/mfd/da9150-core.c @@ -164,7 +164,7 @@ void da9150_bulk_write(struct da9150 *da9150, u16 reg, int count, const u8 *buf) } EXPORT_SYMBOL_GPL(da9150_bulk_write); -static struct regmap_irq da9150_irqs[] = { +static const struct regmap_irq da9150_irqs[] = { [DA9150_IRQ_VBUS] = { .reg_offset = 0, .mask = DA9150_E_VBUS_MASK, @@ -251,7 +251,7 @@ static struct regmap_irq da9150_irqs[] = { }, }; -static struct regmap_irq_chip da9150_regmap_irq_chip = { +static const struct regmap_irq_chip da9150_regmap_irq_chip = { .name = "da9150_irq", .status_base = DA9150_EVENT_E, .mask_base = DA9150_IRQ_MASK_E, diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index cc1a404328c2..8b14740f9fca 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -2659,7 +2659,7 @@ static int db8500_irq_map(struct irq_domain *d, unsigned int virq, return 0; } -static struct irq_domain_ops db8500_irq_ops = { +static const struct irq_domain_ops db8500_irq_ops = { .map = db8500_irq_map, .xlate = irq_domain_xlate_twocell, }; diff --git a/drivers/mfd/intel_soc_pmic_core.h b/drivers/mfd/intel_soc_pmic_core.h index 9498d6719847..ff2464bc172f 100644 --- a/drivers/mfd/intel_soc_pmic_core.h +++ b/drivers/mfd/intel_soc_pmic_core.h @@ -24,7 +24,7 @@ struct intel_soc_pmic_config { struct mfd_cell *cell_dev; int n_cell_devs; const struct regmap_config *regmap_config; - struct regmap_irq_chip *irq_chip; + const struct regmap_irq_chip *irq_chip; }; extern struct intel_soc_pmic_config intel_soc_pmic_config_crc; diff --git a/drivers/mfd/intel_soc_pmic_crc.c b/drivers/mfd/intel_soc_pmic_crc.c index 4cc1b324e971..7436075e8983 100644 --- a/drivers/mfd/intel_soc_pmic_crc.c +++ b/drivers/mfd/intel_soc_pmic_crc.c @@ -143,7 +143,7 @@ static const struct regmap_irq crystal_cove_irqs[] = { }, }; -static struct regmap_irq_chip crystal_cove_irq_chip = { +static const struct regmap_irq_chip crystal_cove_irq_chip = { .name = "Crystal Cove", .irqs = crystal_cove_irqs, .num_irqs = ARRAY_SIZE(crystal_cove_irqs), diff --git a/drivers/mfd/lp8788-irq.c b/drivers/mfd/lp8788-irq.c index 23982dbf014d..a87f2b548f71 100644 --- a/drivers/mfd/lp8788-irq.c +++ b/drivers/mfd/lp8788-irq.c @@ -151,7 +151,7 @@ static int lp8788_irq_map(struct irq_domain *d, unsigned int virq, return 0; } -static struct irq_domain_ops lp8788_domain_ops = { +static const struct irq_domain_ops lp8788_domain_ops = { .map = lp8788_irq_map, }; diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index 97a787ab3d51..8520bd68c1ff 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -658,7 +658,7 @@ static int max8925_irq_domain_map(struct irq_domain *d, unsigned int virq, return 0; } -static struct irq_domain_ops max8925_irq_domain_ops = { +static const struct irq_domain_ops max8925_irq_domain_ops = { .map = max8925_irq_domain_map, .xlate = irq_domain_xlate_onetwocell, }; diff --git a/drivers/mfd/max8997-irq.c b/drivers/mfd/max8997-irq.c index 43fa61413e93..d3025be57f39 100644 --- a/drivers/mfd/max8997-irq.c +++ b/drivers/mfd/max8997-irq.c @@ -303,7 +303,7 @@ static int max8997_irq_domain_map(struct irq_domain *d, unsigned int irq, return 0; } -static struct irq_domain_ops max8997_irq_domain_ops = { +static const struct irq_domain_ops max8997_irq_domain_ops = { .map = max8997_irq_domain_map, }; diff --git a/drivers/mfd/max8998-irq.c b/drivers/mfd/max8998-irq.c index c469477eb778..3702056628a8 100644 --- a/drivers/mfd/max8998-irq.c +++ b/drivers/mfd/max8998-irq.c @@ -214,7 +214,7 @@ static int max8998_irq_domain_map(struct irq_domain *d, unsigned int irq, return 0; } -static struct irq_domain_ops max8998_irq_domain_ops = { +static const struct irq_domain_ops max8998_irq_domain_ops = { .map = max8998_irq_domain_map, }; diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index 09bc7804952a..32775ebbce18 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -130,7 +130,7 @@ static int mt6397_irq_domain_map(struct irq_domain *d, unsigned int irq, return 0; } -static struct irq_domain_ops mt6397_irq_domain_ops = { +static const struct irq_domain_ops mt6397_irq_domain_ops = { .map = mt6397_irq_domain_map, }; diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 2d7fae94c861..18c4d72d1d2a 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -989,7 +989,7 @@ static void stmpe_irq_unmap(struct irq_domain *d, unsigned int virq) irq_set_chip_data(virq, NULL); } -static struct irq_domain_ops stmpe_irq_ops = { +static const struct irq_domain_ops stmpe_irq_ops = { .map = stmpe_irq_map, .unmap = stmpe_irq_unmap, .xlate = irq_domain_xlate_twocell, diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index cf356395c9e9..96d420dfc15d 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -233,7 +233,7 @@ static void tc3589x_irq_unmap(struct irq_domain *d, unsigned int virq) irq_set_chip_data(virq, NULL); } -static struct irq_domain_ops tc3589x_irq_ops = { +static const struct irq_domain_ops tc3589x_irq_ops = { .map = tc3589x_irq_map, .unmap = tc3589x_irq_unmap, .xlate = irq_domain_xlate_onecell, diff --git a/drivers/mfd/tps6586x.c b/drivers/mfd/tps6586x.c index 8e1dbc469580..e0a2583916ce 100644 --- a/drivers/mfd/tps6586x.c +++ b/drivers/mfd/tps6586x.c @@ -311,7 +311,7 @@ static int tps6586x_irq_map(struct irq_domain *h, unsigned int virq, return 0; } -static struct irq_domain_ops tps6586x_domain_ops = { +static const struct irq_domain_ops tps6586x_domain_ops = { .map = tps6586x_irq_map, .xlate = irq_domain_xlate_twocell, }; diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c index 2807e1a95663..20fb58179ada 100644 --- a/drivers/mfd/twl6030-irq.c +++ b/drivers/mfd/twl6030-irq.c @@ -376,7 +376,7 @@ static void twl6030_irq_unmap(struct irq_domain *d, unsigned int virq) irq_set_chip_data(virq, NULL); } -static struct irq_domain_ops twl6030_irq_domain_ops = { +static const struct irq_domain_ops twl6030_irq_domain_ops = { .map = twl6030_irq_map, .unmap = twl6030_irq_unmap, .xlate = irq_domain_xlate_onetwocell, diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 64e512eadf17..3da81263c764 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -564,7 +564,7 @@ static int wm831x_irq_map(struct irq_domain *h, unsigned int virq, return 0; } -static struct irq_domain_ops wm831x_irq_domain_ops = { +static const struct irq_domain_ops wm831x_irq_domain_ops = { .map = wm831x_irq_map, .xlate = irq_domain_xlate_twocell, }; diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c index a14407edbd89..55c380a67686 100644 --- a/drivers/mfd/wm8994-irq.c +++ b/drivers/mfd/wm8994-irq.c @@ -28,7 +28,7 @@ #include -static struct regmap_irq wm8994_irqs[] = { +static const struct regmap_irq wm8994_irqs[] = { [WM8994_IRQ_TEMP_SHUT] = { .reg_offset = 1, .mask = WM8994_TEMP_SHUT_EINT, @@ -128,7 +128,7 @@ static struct regmap_irq wm8994_irqs[] = { }, }; -static struct regmap_irq_chip wm8994_irq_chip = { +static const struct regmap_irq_chip wm8994_irq_chip = { .name = "wm8994", .irqs = wm8994_irqs, .num_irqs = ARRAY_SIZE(wm8994_irqs), @@ -184,7 +184,7 @@ static int wm8994_edge_irq_map(struct irq_domain *h, unsigned int virq, return 0; } -static struct irq_domain_ops wm8994_edge_irq_ops = { +static const struct irq_domain_ops wm8994_edge_irq_ops = { .map = wm8994_edge_irq_map, .xlate = irq_domain_xlate_twocell, }; diff --git a/include/linux/mfd/da9055/core.h b/include/linux/mfd/da9055/core.h index 956afa445998..5dc743fd63a6 100644 --- a/include/linux/mfd/da9055/core.h +++ b/include/linux/mfd/da9055/core.h @@ -89,6 +89,6 @@ static inline int da9055_reg_update(struct da9055 *da9055, unsigned char reg, int da9055_device_init(struct da9055 *da9055); void da9055_device_exit(struct da9055 *da9055); -extern struct regmap_config da9055_regmap_config; +extern const struct regmap_config da9055_regmap_config; #endif /* __DA9055_CORE_H */ From 891ee7aa19b0b0d54609e29636dbe0b0b348c458 Mon Sep 17 00:00:00 2001 From: Jacek Anaszewski Date: Tue, 28 Apr 2015 09:18:42 +0200 Subject: [PATCH 25/59] mfd: documentation: dt: Add documentation for the mfd Maxim max77693 This patch adds device tree binding documentation for the flash cell of the Maxim max77693 multifunctional device. Signed-off-by: Jacek Anaszewski Signed-off-by: Andrzej Hajda Acked-by: Kyungmin Park Acked-by: Sakari Ailus Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/max77693.txt | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt b/Documentation/devicetree/bindings/mfd/max77693.txt index 38e64405e98d..d3425846aa5b 100644 --- a/Documentation/devicetree/bindings/mfd/max77693.txt +++ b/Documentation/devicetree/bindings/mfd/max77693.txt @@ -76,7 +76,60 @@ Optional properties: Valid values: 4300000, 4700000, 4800000, 4900000 Default: 4300000 +- led : the LED submodule device node + +There are two LED outputs available - FLED1 and FLED2. Each of them can +control a separate LED or they can be connected together to double +the maximum current for a single connected LED. One LED is represented +by one child node. + +Required properties: +- compatible : Must be "maxim,max77693-led". + +Optional properties: +- maxim,boost-mode : + In boost mode the device can produce up to 1.2A of total current + on both outputs. The maximum current on each output is reduced + to 625mA then. If not enabled explicitly, boost setting defaults to + LEDS_BOOST_FIXED in case both current sources are used. + Possible values: + LEDS_BOOST_OFF (0) - no boost, + LEDS_BOOST_ADAPTIVE (1) - adaptive mode, + LEDS_BOOST_FIXED (2) - fixed mode. +- maxim,boost-mvout : Output voltage of the boost module in millivolts. + Valid values: 3300 - 5500, step by 25 (rounded down) + Default: 3300 +- maxim,mvsys-min : Low input voltage level in millivolts. Flash is not fired + if chip estimates that system voltage could drop below this level due + to flash power consumption. + Valid values: 2400 - 3400, step by 33 (rounded down) + Default: 2400 + +Required properties for the LED child node: +- led-sources : see Documentation/devicetree/bindings/leds/common.txt; + device current output identifiers: 0 - FLED1, 1 - FLED2 +- led-max-microamp : see Documentation/devicetree/bindings/leds/common.txt + Valid values for a LED connected to one FLED output: + 15625 - 250000, step by 15625 (rounded down) + Valid values for a LED connected to both FLED outputs: + 15625 - 500000, step by 15625 (rounded down) +- flash-max-microamp : see Documentation/devicetree/bindings/leds/common.txt + Valid values for a single LED connected to one FLED output + (boost mode must be turned off): + 15625 - 1000000, step by 15625 (rounded down) + Valid values for a single LED connected to both FLED outputs: + 15625 - 1250000, step by 15625 (rounded down) + Valid values for two LEDs case: + 15625 - 625000, step by 15625 (rounded down) +- flash-max-timeout-us : see Documentation/devicetree/bindings/leds/common.txt + Valid values: 62500 - 1000000, step by 62500 (rounded down) + +Optional properties for the LED child node: +- label : see Documentation/devicetree/bindings/leds/common.txt + Example: +#include + max77693@66 { compatible = "maxim,max77693"; reg = <0x66>; @@ -117,5 +170,19 @@ Example: maxim,thermal-regulation-celsius = <75>; maxim,battery-overcurrent-microamp = <3000000>; maxim,charge-input-threshold-microvolt = <4300000>; + + led { + compatible = "maxim,max77693-led"; + maxim,boost-mode = ; + maxim,boost-mvout = <5000>; + maxim,mvsys-min = <2400>; + + camera_flash: flash-led { + label = "max77693-flash"; + led-sources = <0>, <1>; + led-max-microamp = <500000>; + flash-max-microamp = <1250000>; + flash-max-timeout-us = <1000000>; + }; }; }; From b375e9f90ff8dc645c9d6a57f08d59f0db6aaab3 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 22 Apr 2015 14:34:48 +0100 Subject: [PATCH 26/59] mfd: arizona: Remove redundant register sync This soft reset used to be located after the register patch had been applied, but has since moved to before the patch is applied. At the new location there is no requirement to do a register sync as no register writes will have happened yet. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 6ca6dfab50eb..aed43a549f77 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -777,8 +777,6 @@ int arizona_dev_init(struct arizona *arizona) /* If we have a /RESET GPIO we'll already be reset */ if (!arizona->pdata.reset) { - regcache_mark_dirty(arizona->regmap); - ret = regmap_write(arizona->regmap, ARIZONA_SOFTWARE_RESET, 0); if (ret != 0) { dev_err(dev, "Failed to reset device: %d\n", ret); @@ -786,12 +784,6 @@ int arizona_dev_init(struct arizona *arizona) } msleep(1); - - ret = regcache_sync(arizona->regmap); - if (ret != 0) { - dev_err(dev, "Failed to sync device: %d\n", ret); - goto err_reset; - } } /* Ensure device startup is complete */ From 53960e2bff0107231d21f9ee7b31502b1daed344 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 20 Apr 2015 16:17:37 -0300 Subject: [PATCH 27/59] mfd: documentation: dt: max77686: Fix typo Fix typo in 'Multifunction'. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/max77686.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/max77686.txt b/Documentation/devicetree/bindings/mfd/max77686.txt index e39f0bc1f55e..163bd81a4607 100644 --- a/Documentation/devicetree/bindings/mfd/max77686.txt +++ b/Documentation/devicetree/bindings/mfd/max77686.txt @@ -1,6 +1,6 @@ Maxim MAX77686 multi-function device -MAX77686 is a Mulitifunction device with PMIC, RTC and Charger on chip. It is +MAX77686 is a Multifunction device with PMIC, RTC and Charger on chip. It is interfaced to host controller using i2c interface. PMIC and Charger submodules are addressed using same i2c slave address whereas RTC submodule uses different i2c slave address,presently for which we are statically creating i2c From 5c188d748216f67c928d67a42f14b5569b6404a5 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 27 Apr 2015 10:18:14 -0700 Subject: [PATCH 28/59] mfd: twl4030-power: Fix pmic for boards that need AC charger disabled I noticed the PMIC configuration on 37xx-evm won't actually shut down the voltages during off-idle. Turns out 37xx-evm needs the AC charger state transitions disabled like we are doing for SDP and LDP in the legacy booting case. Let's fix this for device tree based booting by setting up the quirk flag based on the compatible flag. And let's also use the existing define for STARTON_CHG. Note that SDP and EVM do not have the PMIC clken wired to gate the the oscillator while LDP has. Signed-off-by: Tony Lindgren Signed-off-by: Lee Jones --- drivers/mfd/twl4030-power.c | 45 +++++++++++++++++++++++++++++++------ include/linux/i2c/twl.h | 1 + 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index f440aed61305..04b539850e72 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -264,7 +264,9 @@ out: return err; } -static int twl4030_config_wakeup12_sequence(u8 address) +static int +twl4030_config_wakeup12_sequence(const struct twl4030_power_data *pdata, + u8 address) { int err = 0; u8 data; @@ -293,13 +295,14 @@ static int twl4030_config_wakeup12_sequence(u8 address) if (err) goto out; - if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { + if (pdata->ac_charger_quirk || machine_is_omap_3430sdp() || + machine_is_omap_ldp()) { /* Disabling AC charger effect on sleep-active transitions */ err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_CFG_P1_TRANSITION); if (err) goto out; - data &= ~(1<<1); + data &= ~STARTON_CHG; err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_CFG_P1_TRANSITION); if (err) @@ -459,8 +462,9 @@ static int twl4030_configure_resource(struct twl4030_resconfig *rconfig) return 0; } -static int load_twl4030_script(struct twl4030_script *tscript, - u8 address) +static int load_twl4030_script(const struct twl4030_power_data *pdata, + struct twl4030_script *tscript, + u8 address) { int err; static int order; @@ -487,7 +491,7 @@ static int load_twl4030_script(struct twl4030_script *tscript, if (err) goto out; - err = twl4030_config_wakeup12_sequence(address); + err = twl4030_config_wakeup12_sequence(pdata, address); if (err) goto out; order = 1; @@ -567,7 +571,7 @@ twl4030_power_configure_scripts(const struct twl4030_power_data *pdata) u8 address = twl4030_start_script_address; for (i = 0; i < pdata->num; i++) { - err = load_twl4030_script(pdata->scripts[i], address); + err = load_twl4030_script(pdata, pdata->scripts[i], address); if (err) return err; address += pdata->scripts[i]->size; @@ -829,6 +833,21 @@ static struct twl4030_power_data osc_off_idle = { .board_config = osc_off_rconfig, }; +static struct twl4030_power_data omap3_idle_ac_quirk = { + .scripts = omap3_idle_scripts, + .num = ARRAY_SIZE(omap3_idle_scripts), + .resource_config = omap3_idle_rconfig, + .ac_charger_quirk = true, +}; + +static struct twl4030_power_data omap3_idle_ac_quirk_osc_off = { + .scripts = omap3_idle_scripts, + .num = ARRAY_SIZE(omap3_idle_scripts), + .resource_config = omap3_idle_rconfig, + .board_config = osc_off_rconfig, + .ac_charger_quirk = true, +}; + static const struct of_device_id twl4030_power_of_match[] = { { .compatible = "ti,twl4030-power", @@ -845,6 +864,18 @@ static const struct of_device_id twl4030_power_of_match[] = { .compatible = "ti,twl4030-power-idle-osc-off", .data = &osc_off_idle, }, + { + .compatible = "ti,twl4030-power-omap3-sdp", + .data = &omap3_idle_ac_quirk, + }, + { + .compatible = "ti,twl4030-power-omap3-ldp", + .data = &omap3_idle_ac_quirk_osc_off, + }, + { + .compatible = "ti,twl4030-power-omap3-evm", + .data = &omap3_idle_ac_quirk, + }, { }, }; MODULE_DEVICE_TABLE(of, twl4030_power_of_match); diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h index 0bc03f100d04..9ad7828d9d34 100644 --- a/include/linux/i2c/twl.h +++ b/include/linux/i2c/twl.h @@ -675,6 +675,7 @@ struct twl4030_power_data { struct twl4030_resconfig *board_config; #define TWL4030_RESCONFIG_UNDEF ((u8)-1) bool use_poweroff; /* Board is wired for TWL poweroff */ + bool ac_charger_quirk; /* Disable AC charger on board */ }; extern int twl4030_remove_script(u8 flags); From 8be4efad81d814b607cbdad47176f426be83ba75 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 6 May 2015 19:07:16 +0200 Subject: [PATCH 29/59] mfd: max77686: Remove unused struct max77686_opmode_data The defined struct max77686_opmode_data isn't used neither by the max77686 mfd driver nor the drivers for its sub-devices. Signed-off-by: Javier Martinez Canillas Reviewed-by: Chanwoo Choi Reviewed-by: Krzysztof Kozlowski Signed-off-by: Lee Jones --- include/linux/mfd/max77686.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/linux/mfd/max77686.h b/include/linux/mfd/max77686.h index bb995ab9a575..d4b72d519115 100644 --- a/include/linux/mfd/max77686.h +++ b/include/linux/mfd/max77686.h @@ -125,9 +125,4 @@ enum max77686_opmode { MAX77686_OPMODE_STANDBY, }; -struct max77686_opmode_data { - int id; - int mode; -}; - #endif /* __LINUX_MFD_MAX77686_H */ From 6fafecc12ddfd5d02be531de081e4ea9cb8db02f Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 5 May 2015 18:32:27 +0200 Subject: [PATCH 30/59] mfd: Allow compile test of GPIO consumers if !GPIOLIB The GPIO subsystem provides dummy GPIO consumer functions if GPIOLIB is not enabled. Hence drivers that depend on GPIOLIB, but use GPIO consumer functionality only, can still be compiled if GPIOLIB is not enabled. Relax the dependency on GPIOLIB if COMPILE_TEST is enabled, where appropriate. Signed-off-by: Geert Uytterhoeven Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index cf3b86d441f7..653815950aa2 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -52,7 +52,8 @@ config PMIC_ADP5520 config MFD_AAT2870_CORE bool "AnalogicTech AAT2870" select MFD_CORE - depends on I2C=y && GPIOLIB + depends on I2C=y + depends on GPIOLIB || COMPILE_TEST help If you say yes here you get support for the AAT2870. This driver provides common support for accessing the device, @@ -1117,7 +1118,8 @@ config MFD_TPS6586X config MFD_TPS65910 bool "TI TPS65910 Power Management chip" - depends on I2C=y && GPIOLIB + depends on I2C=y + depends on GPIOLIB || COMPILE_TEST select MFD_CORE select REGMAP_I2C select REGMAP_IRQ From b40f0632c9101eb93994ce4ba0d10164b48d6a27 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 6 May 2015 14:19:36 +0900 Subject: [PATCH 31/59] MAINTAINERS: Extend Samsung MFD drivers entry and add Krzysztof Kozlowski Extend the entry for Samsung MFD drivers for PMIC devices (Power Management Integrated Circuit) with bindings documentation, clock (clk-s2mps11.c) and RTC drivers (rtc-s5m.c). These PMIC devices are used on many Exynos-based boards like Arndale Octa (S2MPS11), Gear 2 (S2MPS14). Add Krzysztof Kozlowski as a supporter for reviewing them. I am not the author of these drivers. However I have recently contributed to most of them and I have access to datasheets and hardware. Cc: Sangbeom Kim CC: Mike Turquette CC: Stephen Boyd CC: Alessandro Zummo CC: Alexandre Belloni CC: Liam Girdwood CC: Mark Brown Signed-off-by: Krzysztof Kozlowski Signed-off-by: Lee Jones --- MAINTAINERS | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index e476ff756f22..2ece1fbf5367 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8509,14 +8509,20 @@ L: linux-fbdev@vger.kernel.org S: Maintained F: drivers/video/fbdev/s3c-fb.c -SAMSUNG MULTIFUNCTION DEVICE DRIVERS +SAMSUNG MULTIFUNCTION PMIC DEVICE DRIVERS M: Sangbeom Kim +M: Krzysztof Kozlowski L: linux-kernel@vger.kernel.org +L: linux-samsung-soc@vger.kernel.org S: Supported F: drivers/mfd/sec*.c F: drivers/regulator/s2m*.c F: drivers/regulator/s5m*.c +F: drivers/clk/clk-s2mps11.c +F: drivers/rtc/rtc-s5m.c F: include/linux/mfd/samsung/ +F: Documentation/devicetree/bindings/regulator/s5m8767-regulator.txt +F: Documentation/devicetree/bindings/mfd/s2mp*.txt SAMSUNG S5P/EXYNOS4 SOC SERIES CAMERA SUBSYSTEM DRIVERS M: Kyungmin Park From 6c284c9a343a16d0a31d689558e1da174f37a0b0 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 12 May 2015 14:13:58 +0100 Subject: [PATCH 32/59] MAINTAINERS: Add Arnd Bergmann as Syscon reviewer The System Configuration (syscon) driver is an important one, which effects many ARM-SoC based platforms. I'm adding Arnd as a primary reviewer as he is highly competent in this area and has a keen interest in ARM-SoC. Acked-by: Arnd Bergmann Signed-off-by: Lee Jones --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2ece1fbf5367..fdeda6ae81a8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9558,6 +9558,13 @@ F: arch/arc/ F: Documentation/devicetree/bindings/arc/ F: drivers/tty/serial/arc_uart.c +SYSTEM CONFIGURATION (SYSCON) +M: Lee Jones +M: Arnd Bergmann +T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git +S: Supported +F: drivers/mfd/syscon.c + SYSV FILESYSTEM M: Christoph Hellwig S: Maintained From 5f056bf04201dc533f5d10acf028435ede0a1442 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:01 +0100 Subject: [PATCH 33/59] mfd: arizona: Use devres to manage reset GPIO This also handily fixes a leak of the GPIO in arizona_dev_exit. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index aed43a549f77..915cc28585d2 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -728,9 +728,9 @@ int arizona_dev_init(struct arizona *arizona) if (arizona->pdata.reset) { /* Start out with /RESET low to put the chip into reset */ - ret = gpio_request_one(arizona->pdata.reset, - GPIOF_DIR_OUT | GPIOF_INIT_LOW, - "arizona /RESET"); + ret = devm_gpio_request_one(arizona->dev, arizona->pdata.reset, + GPIOF_DIR_OUT | GPIOF_INIT_LOW, + "arizona /RESET"); if (ret != 0) { dev_err(dev, "Failed to request /RESET: %d\n", ret); goto err_dcvdd; @@ -1046,10 +1046,8 @@ int arizona_dev_init(struct arizona *arizona) err_irq: arizona_irq_exit(arizona); err_reset: - if (arizona->pdata.reset) { + if (arizona->pdata.reset) gpio_set_value_cansleep(arizona->pdata.reset, 0); - gpio_free(arizona->pdata.reset); - } regulator_disable(arizona->dcvdd); err_enable: regulator_bulk_disable(arizona->num_core_supplies, From 2229875dc00f38b95d04fecfc67858ba2221a563 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:02 +0100 Subject: [PATCH 34/59] mfd: arizona: Factor out hard reset into helper functions This patch adds functions for enabling and disabling the physical reset line. This will be helpful in future refactoring. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 915cc28585d2..f30d92538584 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -250,6 +250,20 @@ static int arizona_wait_for_boot(struct arizona *arizona) return ret; } +static inline void arizona_enable_reset(struct arizona *arizona) +{ + if (arizona->pdata.reset) + gpio_set_value_cansleep(arizona->pdata.reset, 0); +} + +static void arizona_disable_reset(struct arizona *arizona) +{ + if (arizona->pdata.reset) { + gpio_set_value_cansleep(arizona->pdata.reset, 1); + msleep(1); + } +} + static int arizona_apply_hardware_patch(struct arizona* arizona) { unsigned int fll, sysclk; @@ -751,10 +765,7 @@ int arizona_dev_init(struct arizona *arizona) goto err_enable; } - if (arizona->pdata.reset) { - gpio_set_value_cansleep(arizona->pdata.reset, 1); - msleep(1); - } + arizona_disable_reset(arizona); regcache_cache_only(arizona->regmap, false); @@ -1046,8 +1057,7 @@ int arizona_dev_init(struct arizona *arizona) err_irq: arizona_irq_exit(arizona); err_reset: - if (arizona->pdata.reset) - gpio_set_value_cansleep(arizona->pdata.reset, 0); + arizona_enable_reset(arizona); regulator_disable(arizona->dcvdd); err_enable: regulator_bulk_disable(arizona->num_core_supplies, @@ -1072,8 +1082,7 @@ int arizona_dev_exit(struct arizona *arizona) arizona_free_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, arizona); arizona_free_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, arizona); arizona_irq_exit(arizona); - if (arizona->pdata.reset) - gpio_set_value_cansleep(arizona->pdata.reset, 0); + arizona_enable_reset(arizona); regulator_bulk_disable(arizona->num_core_supplies, arizona->core_supplies); From 1c1c6bba57f51331f64f32d0ce9a0f9883041205 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:03 +0100 Subject: [PATCH 35/59] mfd: wm5102: Ensure we always boot the device fully The wm5102 uses a custom boot sequence and the standard boot sequence is disabled. However, the standard boot sequence must be run at least once after a cold boot (caused by either a hard reset or removal of AVDD). Unfortunately the register WRITE_SEQUENCER_CTRL_3 is not affected by the hardware reset. This means if the device has been previously booted but the AVDD supply has never been removed, arizona_dev_init will reset the chip but the boot sequence will not run, which can cause numerous problems. The solution is to manually clear this register and then boot the chip again, which is what this patch does. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 50 ++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index f30d92538584..0df12995ed1b 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -344,6 +344,33 @@ err_fll: return err; } +static int wm5102_clear_write_sequencer(struct arizona *arizona) +{ + int ret; + + ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_3, + 0x0); + if (ret) { + dev_err(arizona->dev, + "Failed to clear write sequencer state: %d\n", ret); + return ret; + } + + arizona_enable_reset(arizona); + regulator_disable(arizona->dcvdd); + + msleep(20); + + ret = regulator_enable(arizona->dcvdd); + if (ret) { + dev_err(arizona->dev, "Failed to re-enable DCVDD: %d\n", ret); + return ret; + } + arizona_disable_reset(arizona); + + return 0; +} + #ifdef CONFIG_PM static int arizona_runtime_resume(struct device *dev) { @@ -802,21 +829,24 @@ int arizona_dev_init(struct arizona *arizona) case WM5102: ret = regmap_read(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_3, &val); - if (ret != 0) + if (ret) { dev_err(dev, "Failed to check write sequencer state: %d\n", ret); - else if (val & 0x01) - break; - /* Fall through */ - default: - ret = arizona_wait_for_boot(arizona); - if (ret != 0) { - dev_err(arizona->dev, - "Device failed initial boot: %d\n", ret); - goto err_reset; + } else if (val & 0x01) { + ret = wm5102_clear_write_sequencer(arizona); + if (ret) + return ret; } break; + default: + break; + } + + ret = arizona_wait_for_boot(arizona); + if (ret) { + dev_err(arizona->dev, "Device failed initial boot: %d\n", ret); + goto err_reset; } /* Read the device ID information & do device specific stuff */ From 0be068a077a06fa3382ab6d8d290078967bb8f64 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:04 +0100 Subject: [PATCH 36/59] mfd: arizona: Fix formating/style issues in arizona_apply_hardware_patch In preparation for some refactoring fixup some minor style, formating and code clarity issues. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 0df12995ed1b..326a4afdd832 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -264,20 +264,20 @@ static void arizona_disable_reset(struct arizona *arizona) } } -static int arizona_apply_hardware_patch(struct arizona* arizona) +static int wm5102_apply_hardware_patch(struct arizona *arizona) { unsigned int fll, sysclk; int ret, err; /* Cache existing FLL and SYSCLK settings */ ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &fll); - if (ret != 0) { + if (ret) { dev_err(arizona->dev, "Failed to cache FLL settings: %d\n", ret); return ret; } ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &sysclk); - if (ret != 0) { + if (ret) { dev_err(arizona->dev, "Failed to cache SYSCLK settings: %d\n", ret); return ret; @@ -286,7 +286,7 @@ static int arizona_apply_hardware_patch(struct arizona* arizona) /* Start up SYSCLK using the FLL in free running mode */ ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, ARIZONA_FLL1_ENA | ARIZONA_FLL1_FREERUN); - if (ret != 0) { + if (ret) { dev_err(arizona->dev, "Failed to start FLL in freerunning mode: %d\n", ret); @@ -295,36 +295,36 @@ static int arizona_apply_hardware_patch(struct arizona* arizona) ret = arizona_poll_reg(arizona, 25, ARIZONA_INTERRUPT_RAW_STATUS_5, ARIZONA_FLL1_CLOCK_OK_STS, ARIZONA_FLL1_CLOCK_OK_STS); - if (ret != 0) { + if (ret) { ret = -ETIMEDOUT; goto err_fll; } ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, 0x0144); - if (ret != 0) { + if (ret) { dev_err(arizona->dev, "Failed to start SYSCLK: %d\n", ret); goto err_fll; } /* Start the write sequencer and wait for it to finish */ ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, - ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160); - if (ret != 0) { + ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160); + if (ret) { dev_err(arizona->dev, "Failed to start write sequencer: %d\n", ret); goto err_sysclk; } ret = arizona_poll_reg(arizona, 5, ARIZONA_WRITE_SEQUENCER_CTRL_1, ARIZONA_WSEQ_BUSY, 0); - if (ret != 0) { + if (ret) { regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, - ARIZONA_WSEQ_ABORT); + ARIZONA_WSEQ_ABORT); ret = -ETIMEDOUT; } err_sysclk: err = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, sysclk); - if (err != 0) { + if (err) { dev_err(arizona->dev, "Failed to re-apply old SYSCLK settings: %d\n", err); @@ -332,16 +332,13 @@ err_sysclk: err_fll: err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, fll); - if (err != 0) { + if (err) { dev_err(arizona->dev, "Failed to re-apply old FLL settings: %d\n", err); } - if (ret != 0) - return ret; - else - return err; + return ret ?: err; } static int wm5102_clear_write_sequencer(struct arizona *arizona) @@ -407,8 +404,8 @@ static int arizona_runtime_resume(struct device *dev) goto err; } - ret = arizona_apply_hardware_patch(arizona); - if (ret != 0) { + ret = wm5102_apply_hardware_patch(arizona); + if (ret) { dev_err(arizona->dev, "Failed to apply hardware patch: %d\n", ret); @@ -924,8 +921,8 @@ int arizona_dev_init(struct arizona *arizona) switch (arizona->type) { case WM5102: - ret = arizona_apply_hardware_patch(arizona); - if (ret != 0) { + ret = wm5102_apply_hardware_patch(arizona); + if (ret) { dev_err(arizona->dev, "Failed to apply hardware patch: %d\n", ret); From 3850e3ee86c90e22767907fbd7505a717300c533 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:05 +0100 Subject: [PATCH 37/59] mfd: arizona: Factor out SYSCLK enable from wm5102 hardware patch wm5102 applies a custom hardware boot sequence, for this the SYSCLK needs to be enabled. This patch factors out the code that enables SYSCLK for this sequence such that it can be used for other boot time operations that require SYSCLK. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 77 ++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 326a4afdd832..b5a7e0efcf3e 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -264,19 +264,25 @@ static void arizona_disable_reset(struct arizona *arizona) } } -static int wm5102_apply_hardware_patch(struct arizona *arizona) +struct arizona_sysclk_state { + unsigned int fll; + unsigned int sysclk; +}; + +static int arizona_enable_freerun_sysclk(struct arizona *arizona, + struct arizona_sysclk_state *state) { - unsigned int fll, sysclk; int ret, err; /* Cache existing FLL and SYSCLK settings */ - ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &fll); + ret = regmap_read(arizona->regmap, ARIZONA_FLL1_CONTROL_1, &state->fll); if (ret) { dev_err(arizona->dev, "Failed to cache FLL settings: %d\n", ret); return ret; } - ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &sysclk); + ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, + &state->sysclk); if (ret) { dev_err(arizona->dev, "Failed to cache SYSCLK settings: %d\n", ret); @@ -306,14 +312,58 @@ static int wm5102_apply_hardware_patch(struct arizona *arizona) goto err_fll; } + return 0; + +err_fll: + err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, state->fll); + if (err) + dev_err(arizona->dev, + "Failed to re-apply old FLL settings: %d\n", err); + + return ret; +} + +static int arizona_disable_freerun_sysclk(struct arizona *arizona, + struct arizona_sysclk_state *state) +{ + int ret; + + ret = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, + state->sysclk); + if (ret) { + dev_err(arizona->dev, + "Failed to re-apply old SYSCLK settings: %d\n", ret); + return ret; + } + + ret = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, state->fll); + if (ret) { + dev_err(arizona->dev, + "Failed to re-apply old FLL settings: %d\n", ret); + return ret; + } + + return 0; +} + +static int wm5102_apply_hardware_patch(struct arizona *arizona) +{ + struct arizona_sysclk_state state; + int err, ret; + + ret = arizona_enable_freerun_sysclk(arizona, &state); + if (ret) + return ret; + /* Start the write sequencer and wait for it to finish */ ret = regmap_write(arizona->regmap, ARIZONA_WRITE_SEQUENCER_CTRL_0, ARIZONA_WSEQ_ENA | ARIZONA_WSEQ_START | 160); if (ret) { dev_err(arizona->dev, "Failed to start write sequencer: %d\n", ret); - goto err_sysclk; + goto err; } + ret = arizona_poll_reg(arizona, 5, ARIZONA_WRITE_SEQUENCER_CTRL_1, ARIZONA_WSEQ_BUSY, 0); if (ret) { @@ -322,21 +372,8 @@ static int wm5102_apply_hardware_patch(struct arizona *arizona) ret = -ETIMEDOUT; } -err_sysclk: - err = regmap_write(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, sysclk); - if (err) { - dev_err(arizona->dev, - "Failed to re-apply old SYSCLK settings: %d\n", - err); - } - -err_fll: - err = regmap_write(arizona->regmap, ARIZONA_FLL1_CONTROL_1, fll); - if (err) { - dev_err(arizona->dev, - "Failed to re-apply old FLL settings: %d\n", - err); - } +err: + err = arizona_disable_freerun_sysclk(arizona, &state); return ret ?: err; } From 882bc46813177640cfe01fb4f9b3e7af60201512 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:06 +0100 Subject: [PATCH 38/59] mfd: wm5110: Add register patch required for low power sleep Some register settings must be applied before the first time low power sleep mode is entered on the wm5110 to ensure optimium performance. These settings require SYSCLK to be enabled whilst they are being applied. This patch applies the settings using the recently factored out boot time SYSCLK functionality. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index b5a7e0efcf3e..c80ab7910615 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -378,6 +378,35 @@ err: return ret ?: err; } +/* + * Register patch to some of the CODECs internal write sequences + * to ensure a clean exit from the low power sleep state. + */ +static const struct reg_default wm5110_sleep_patch[] = { + { 0x337A, 0xC100 }, + { 0x337B, 0x0041 }, + { 0x3300, 0xA210 }, + { 0x3301, 0x050C }, +}; + +static int wm5110_apply_sleep_patch(struct arizona *arizona) +{ + struct arizona_sysclk_state state; + int err, ret; + + ret = arizona_enable_freerun_sysclk(arizona, &state); + if (ret) + return ret; + + ret = regmap_multi_reg_write_bypassed(arizona->regmap, + wm5110_sleep_patch, + ARRAY_SIZE(wm5110_sleep_patch)); + + err = arizona_disable_freerun_sysclk(arizona, &state); + + return ret ?: err; +} + static int wm5102_clear_write_sequencer(struct arizona *arizona) { int ret; @@ -966,6 +995,16 @@ int arizona_dev_init(struct arizona *arizona) goto err_reset; } break; + case WM5110: + case WM8280: + ret = wm5110_apply_sleep_patch(arizona); + if (ret) { + dev_err(arizona->dev, + "Failed to apply sleep patch: %d\n", + ret); + goto err_reset; + } + break; default: break; } From 96129a0eaa3f5232f433812972c7bf38004ebf6b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:08 +0100 Subject: [PATCH 39/59] mfd: wm5110: Set DCVDD voltage to 1.175V before entering sleep mode The low power sleep mode on wm5110 requires that the LDO1 regulator be set to 1.175V prior to entering sleep, then returned to 1.2V after exiting sleep mode. This patch apply these regulator settings. Signed-off-by: Charles Keepax Acked-by: Mark Brown Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index c80ab7910615..93024acc145e 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -478,6 +478,37 @@ static int arizona_runtime_resume(struct device *dev) goto err; } break; + case WM5110: + case WM8280: + ret = arizona_wait_for_boot(arizona); + if (ret) + goto err; + + if (arizona->external_dcvdd) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ISOLATION_CONTROL, + ARIZONA_ISOLATE_DCVDD1, 0); + if (ret) { + dev_err(arizona->dev, + "Failed to connect DCVDD: %d\n", ret); + goto err; + } + } else { + /* + * As this is only called for the internal regulator + * (where we know voltage ranges available) it is ok + * to request an exact range. + */ + ret = regulator_set_voltage(arizona->dcvdd, + 1200000, 1200000); + if (ret < 0) { + dev_err(arizona->dev, + "Failed to set resume voltage: %d\n", + ret); + goto err; + } + } + break; default: ret = arizona_wait_for_boot(arizona); if (ret != 0) { @@ -528,6 +559,27 @@ static int arizona_runtime_suspend(struct device *dev) ret); return ret; } + } else { + switch (arizona->type) { + case WM5110: + case WM8280: + /* + * As this is only called for the internal regulator + * (where we know voltage ranges available) it is ok + * to request an exact range. + */ + ret = regulator_set_voltage(arizona->dcvdd, + 1175000, 1175000); + if (ret < 0) { + dev_err(arizona->dev, + "Failed to set suspend voltage: %d\n", + ret); + return ret; + } + break; + default: + break; + } } regcache_cache_only(arizona->regmap, true); From e6cb73410a6db70eab266f15b7e25053a45b842d Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:09 +0100 Subject: [PATCH 40/59] mfd: arizona: Add better support for system suspend Allow the chip to completely power off if we enter runtime suspend and there is no jack detection active. This is helpful for systems where system suspend might remove the supplies to the CODEC, without informing us. Note the powering off is done in runtime suspend rather than system suspend, because we need to hold reset until the first time DCVDD is powered anyway (which would be in runtime resume), and we might as well save the extra power. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 93 ++++++++++++++++++++++++++------ include/linux/mfd/arizona/core.h | 1 + 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 93024acc145e..4f38ce654558 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -442,12 +442,33 @@ static int arizona_runtime_resume(struct device *dev) dev_dbg(arizona->dev, "Leaving AoD mode\n"); + if (arizona->has_fully_powered_off) { + dev_dbg(arizona->dev, "Re-enabling core supplies\n"); + + ret = regulator_bulk_enable(arizona->num_core_supplies, + arizona->core_supplies); + if (ret) { + dev_err(dev, "Failed to enable core supplies: %d\n", + ret); + return ret; + } + } + ret = regulator_enable(arizona->dcvdd); if (ret != 0) { dev_err(arizona->dev, "Failed to enable DCVDD: %d\n", ret); + if (arizona->has_fully_powered_off) + regulator_bulk_disable(arizona->num_core_supplies, + arizona->core_supplies); return ret; } + if (arizona->has_fully_powered_off) { + arizona_disable_reset(arizona); + enable_irq(arizona->irq); + arizona->has_fully_powered_off = false; + } + regcache_cache_only(arizona->regmap, false); switch (arizona->type) { @@ -508,6 +529,14 @@ static int arizona_runtime_resume(struct device *dev) goto err; } } + + ret = wm5110_apply_sleep_patch(arizona); + if (ret) { + dev_err(arizona->dev, + "Failed to re-apply sleep patch: %d\n", + ret); + goto err; + } break; default: ret = arizona_wait_for_boot(arizona); @@ -545,10 +574,17 @@ err: static int arizona_runtime_suspend(struct device *dev) { struct arizona *arizona = dev_get_drvdata(dev); + unsigned int val; int ret; dev_dbg(arizona->dev, "Entering AoD mode\n"); + ret = regmap_read(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, &val); + if (ret) { + dev_err(dev, "Failed to check jack det status: %d\n", ret); + return ret; + } + if (arizona->external_dcvdd) { ret = regmap_update_bits(arizona->regmap, ARIZONA_ISOLATION_CONTROL, @@ -559,33 +595,58 @@ static int arizona_runtime_suspend(struct device *dev) ret); return ret; } - } else { - switch (arizona->type) { - case WM5110: - case WM8280: - /* - * As this is only called for the internal regulator - * (where we know voltage ranges available) it is ok - * to request an exact range. - */ - ret = regulator_set_voltage(arizona->dcvdd, - 1175000, 1175000); - if (ret < 0) { + } + + switch (arizona->type) { + case WM5110: + case WM8280: + if (arizona->external_dcvdd) + break; + + /* + * As this is only called for the internal regulator + * (where we know voltage ranges available) it is ok + * to request an exact range. + */ + ret = regulator_set_voltage(arizona->dcvdd, 1175000, 1175000); + if (ret < 0) { + dev_err(arizona->dev, + "Failed to set suspend voltage: %d\n", ret); + return ret; + } + break; + case WM5102: + if (!(val & ARIZONA_JD1_ENA)) { + ret = regmap_write(arizona->regmap, + ARIZONA_WRITE_SEQUENCER_CTRL_3, 0x0); + if (ret) { dev_err(arizona->dev, - "Failed to set suspend voltage: %d\n", + "Failed to clear write sequencer: %d\n", ret); return ret; } - break; - default: - break; } + break; + default: + break; } regcache_cache_only(arizona->regmap, true); regcache_mark_dirty(arizona->regmap); regulator_disable(arizona->dcvdd); + /* Allow us to completely power down if no jack detection */ + if (!(val & ARIZONA_JD1_ENA)) { + dev_dbg(arizona->dev, "Fully powering off\n"); + + arizona->has_fully_powered_off = true; + + disable_irq(arizona->irq); + arizona_enable_reset(arizona); + regulator_bulk_disable(arizona->num_core_supplies, + arizona->core_supplies); + } + return 0; } #endif diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index 16a498f48169..847bdaf47f1d 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -117,6 +117,7 @@ struct arizona { int num_core_supplies; struct regulator_bulk_data core_supplies[ARIZONA_MAX_CORE_SUPPLIES]; struct regulator *dcvdd; + bool has_fully_powered_off; struct arizona_pdata pdata; From 121c075c1f845c2e75d61a4e7aac92d05d2def6e Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 11 May 2015 13:58:10 +0100 Subject: [PATCH 41/59] mfd: wm5110: Add delay before releasing reset line On the wm5110 it is important the reset line is held for slightly longer to ensure the device starts up well. This patch adds a 5mS delay for this. Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 4f38ce654558..cb8a1b2f07f1 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -259,6 +259,16 @@ static inline void arizona_enable_reset(struct arizona *arizona) static void arizona_disable_reset(struct arizona *arizona) { if (arizona->pdata.reset) { + switch (arizona->type) { + case WM5110: + case WM8280: + /* Meet requirements for minimum reset duration */ + msleep(5); + break; + default: + break; + } + gpio_set_value_cansleep(arizona->pdata.reset, 1); msleep(1); } From fc027d138b79537e5353f3d3bad2bcaac787cd17 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Fri, 1 May 2015 16:15:12 +0100 Subject: [PATCH 42/59] mfd: arizona: Split INx_MODE into two fields Later arizona silicon has the single/differential selector in a different register, and IN1_MODE only selects between analogue or digital. Prepare for this by splitting the INx_MODE definition into two fields. Signed-off-by: Richard Fitzgerald Signed-off-by: Lee Jones --- drivers/mfd/arizona-core.c | 8 ++++++-- include/linux/mfd/arizona/pdata.h | 5 ++++- include/linux/mfd/arizona/registers.h | 27 ++++++++++++++++++--------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index cb8a1b2f07f1..bebf58a06a6b 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -1206,12 +1206,16 @@ int arizona_dev_init(struct arizona *arizona) /* Default for both is 0 so noop with defaults */ val = arizona->pdata.dmic_ref[i] << ARIZONA_IN1_DMIC_SUP_SHIFT; - val |= arizona->pdata.inmode[i] << ARIZONA_IN1_MODE_SHIFT; + if (arizona->pdata.inmode[i] & ARIZONA_INMODE_DMIC) + val |= 1 << ARIZONA_IN1_MODE_SHIFT; + if (arizona->pdata.inmode[i] & ARIZONA_INMODE_SE) + val |= 1 << ARIZONA_IN1_SINGLE_ENDED_SHIFT; regmap_update_bits(arizona->regmap, ARIZONA_IN1L_CONTROL + (i * 8), ARIZONA_IN1_DMIC_SUP_MASK | - ARIZONA_IN1_MODE_MASK, val); + ARIZONA_IN1_MODE_MASK | + ARIZONA_IN1_SINGLE_ENDED_MASK, val); } for (i = 0; i < ARIZONA_MAX_OUTPUT; i++) { diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 1789cb0f4f17..f6722677e6d0 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -156,7 +156,10 @@ struct arizona_pdata { /** MICBIAS configurations */ struct arizona_micbias micbias[ARIZONA_MAX_MICBIAS]; - /** Mode of input structures */ + /** + * Mode of input structures + * One of the ARIZONA_INMODE_xxx values + */ int inmode[ARIZONA_MAX_INPUT]; /** Mode for outputs */ diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index aacc10d7789c..3499d36e6067 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -2515,9 +2515,12 @@ #define ARIZONA_IN1_DMIC_SUP_MASK 0x1800 /* IN1_DMIC_SUP - [12:11] */ #define ARIZONA_IN1_DMIC_SUP_SHIFT 11 /* IN1_DMIC_SUP - [12:11] */ #define ARIZONA_IN1_DMIC_SUP_WIDTH 2 /* IN1_DMIC_SUP - [12:11] */ -#define ARIZONA_IN1_MODE_MASK 0x0600 /* IN1_MODE - [10:9] */ -#define ARIZONA_IN1_MODE_SHIFT 9 /* IN1_MODE - [10:9] */ -#define ARIZONA_IN1_MODE_WIDTH 2 /* IN1_MODE - [10:9] */ +#define ARIZONA_IN1_MODE_MASK 0x0400 /* IN1_MODE - [10] */ +#define ARIZONA_IN1_MODE_SHIFT 10 /* IN1_MODE - [10] */ +#define ARIZONA_IN1_MODE_WIDTH 1 /* IN1_MODE - [10] */ +#define ARIZONA_IN1_SINGLE_ENDED_MASK 0x0200 /* IN1_MODE - [9] */ +#define ARIZONA_IN1_SINGLE_ENDED_SHIFT 9 /* IN1_MODE - [9] */ +#define ARIZONA_IN1_SINGLE_ENDED_WIDTH 1 /* IN1_MODE - [9] */ #define ARIZONA_IN1L_PGA_VOL_MASK 0x00FE /* IN1L_PGA_VOL - [7:1] */ #define ARIZONA_IN1L_PGA_VOL_SHIFT 1 /* IN1L_PGA_VOL - [7:1] */ #define ARIZONA_IN1L_PGA_VOL_WIDTH 7 /* IN1L_PGA_VOL - [7:1] */ @@ -2588,9 +2591,12 @@ #define ARIZONA_IN2_DMIC_SUP_MASK 0x1800 /* IN2_DMIC_SUP - [12:11] */ #define ARIZONA_IN2_DMIC_SUP_SHIFT 11 /* IN2_DMIC_SUP - [12:11] */ #define ARIZONA_IN2_DMIC_SUP_WIDTH 2 /* IN2_DMIC_SUP - [12:11] */ -#define ARIZONA_IN2_MODE_MASK 0x0600 /* IN2_MODE - [10:9] */ -#define ARIZONA_IN2_MODE_SHIFT 9 /* IN2_MODE - [10:9] */ -#define ARIZONA_IN2_MODE_WIDTH 2 /* IN2_MODE - [10:9] */ +#define ARIZONA_IN2_MODE_MASK 0x0400 /* IN2_MODE - [10] */ +#define ARIZONA_IN2_MODE_SHIFT 10 /* IN2_MODE - [10] */ +#define ARIZONA_IN2_MODE_WIDTH 1 /* IN2_MODE - [10] */ +#define ARIZONA_IN2_SINGLE_ENDED_MASK 0x0200 /* IN2_MODE - [9] */ +#define ARIZONA_IN2_SINGLE_ENDED_SHIFT 9 /* IN2_MODE - [9] */ +#define ARIZONA_IN2_SINGLE_ENDED_WIDTH 1 /* IN2_MODE - [9] */ #define ARIZONA_IN2L_PGA_VOL_MASK 0x00FE /* IN2L_PGA_VOL - [7:1] */ #define ARIZONA_IN2L_PGA_VOL_SHIFT 1 /* IN2L_PGA_VOL - [7:1] */ #define ARIZONA_IN2L_PGA_VOL_WIDTH 7 /* IN2L_PGA_VOL - [7:1] */ @@ -2661,9 +2667,12 @@ #define ARIZONA_IN3_DMIC_SUP_MASK 0x1800 /* IN3_DMIC_SUP - [12:11] */ #define ARIZONA_IN3_DMIC_SUP_SHIFT 11 /* IN3_DMIC_SUP - [12:11] */ #define ARIZONA_IN3_DMIC_SUP_WIDTH 2 /* IN3_DMIC_SUP - [12:11] */ -#define ARIZONA_IN3_MODE_MASK 0x0600 /* IN3_MODE - [10:9] */ -#define ARIZONA_IN3_MODE_SHIFT 9 /* IN3_MODE - [10:9] */ -#define ARIZONA_IN3_MODE_WIDTH 2 /* IN3_MODE - [10:9] */ +#define ARIZONA_IN3_MODE_MASK 0x0400 /* IN3_MODE - [10] */ +#define ARIZONA_IN3_MODE_SHIFT 10 /* IN3_MODE - [10] */ +#define ARIZONA_IN3_MODE_WIDTH 1 /* IN3_MODE - [10] */ +#define ARIZONA_IN3_SINGLE_ENDED_MASK 0x0200 /* IN3_MODE - [9] */ +#define ARIZONA_IN3_SINGLE_ENDED_SHIFT 9 /* IN3_MODE - [9] */ +#define ARIZONA_IN3_SINGLE_ENDED_WIDTH 1 /* IN3_MODE - [9] */ #define ARIZONA_IN3L_PGA_VOL_MASK 0x00FE /* IN3L_PGA_VOL - [7:1] */ #define ARIZONA_IN3L_PGA_VOL_SHIFT 1 /* IN3L_PGA_VOL - [7:1] */ #define ARIZONA_IN3L_PGA_VOL_WIDTH 7 /* IN3L_PGA_VOL - [7:1] */ From b64610dfbdf6a21e92073d50fef33704dbfe6028 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Fri, 1 May 2015 16:15:19 +0100 Subject: [PATCH 43/59] Documentation: Add WM8998/WM1814 device tree bindings Signed-off-by: Richard Fitzgerald Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/arizona.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/arizona.txt b/Documentation/devicetree/bindings/mfd/arizona.txt index 7665aa95979f..64fa3b2de6cd 100644 --- a/Documentation/devicetree/bindings/mfd/arizona.txt +++ b/Documentation/devicetree/bindings/mfd/arizona.txt @@ -10,6 +10,9 @@ Required properties: "wlf,wm5110" "wlf,wm8280" "wlf,wm8997" + "wlf,wm8998" + "wlf,wm1814" + - reg : I2C slave address when connected using I2C, chip select number when using SPI. @@ -31,10 +34,10 @@ Required properties: as covered in Documentation/devicetree/bindings/regulator/regulator.txt - DBVDD2-supply, DBVDD3-supply : Additional databus power supplies (wm5102, - wm5110, wm8280) + wm5110, wm8280, wm8998, wm1814) - SPKVDDL-supply, SPKVDDR-supply : Speaker driver power supplies (wm5102, - wm5110, wm8280) + wm5110, wm8280, wm8998, wm1814) - SPKVDD-supply : Speaker driver power supply (wm8997) @@ -53,8 +56,10 @@ Optional properties: of input signals. Valid values are 0 (Differential), 1 (Single-ended) and 2 (Digital Microphone). If absent, INn_MODE registers set to 0 by default. If present, values must be specified less than or equal to the number of - input singals. If values less than the number of input signals, elements - that has not been specifed are set to 0 by default. + input signals. If values less than the number of input signals, elements + that have not been specified are set to 0 by default. Entries are: + (wm5102, wm5110, wm8280, wm8997) + (wm8998, wm1814) - wlf,dmic-ref : DMIC reference voltage source for each input, can be selected from either MICVDD or one of the MICBIAS's, defines From ec40c606c7aebb8d61cb47af27e2f62c26e09e29 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 1 May 2015 10:41:10 +0100 Subject: [PATCH 44/59] mfd: Check ACPI device companion before checking resources Current code in mfd-core calls into ACPI to check resources even on a system that booted with a DT (on kernels with both DT and ACPI support compiled in). This triggers ACPI exceptions since we may end up calling the ACPI interpreter when it has not been initialized: "ACPI Exception: AE_BAD_PARAMETER, Thread 2064154624 could not acquire Mutex [0x1] (20150410/utmutex-285)" This patch fixes the issues by adding a check for an ACPI companion device before carrying out ACPI resources checks to avoid calling the ACPI interpreter if the fwnode representing the device is an OF one. Cc: Sudeep Holla Signed-off-by: Lorenzo Pieralisi Signed-off-by: Lee Jones --- drivers/mfd/mfd-core.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 1aed3b7b8d9b..14fd5cbcf0f2 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -207,9 +207,11 @@ static int mfd_add_device(struct device *parent, int id, } if (!cell->ignore_resource_conflicts) { - ret = acpi_check_resource_conflict(&res[r]); - if (ret) - goto fail_alias; + if (has_acpi_companion(&pdev->dev)) { + ret = acpi_check_resource_conflict(&res[r]); + if (ret) + goto fail_alias; + } } } From 7e2d67e94ab05f70d2a73d00283f46778386ca52 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 13 May 2015 16:52:25 +0100 Subject: [PATCH 45/59] mfd: arizona: Add stub for wm5102_patch() For the WM5102 there is a dependency in the core code on wm5102_patch() which only exists when CONFIG_MFD_WM5102 is defined. To avoid having to sprinkle #ifdefs around the code it is given an alternative empty stub version when CONFIG_MFD_WM5102 is deselected Signed-off-by: Richard Fitzgerald Signed-off-by: Lee Jones --- include/linux/mfd/arizona/core.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index 847bdaf47f1d..2f434f4f79a1 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -154,7 +154,15 @@ int arizona_request_irq(struct arizona *arizona, int irq, char *name, void arizona_free_irq(struct arizona *arizona, int irq, void *data); int arizona_set_irq_wake(struct arizona *arizona, int irq, int on); +#ifdef CONFIG_MFD_WM5102 int wm5102_patch(struct arizona *arizona); +#else +static inline int wm5102_patch(struct arizona *arizona) +{ + return 0; +} +#endif + int wm5110_patch(struct arizona *arizona); int wm8997_patch(struct arizona *arizona); From e2ae0fbc738fa207cfb712657d339a1863c6bd3e Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 13 May 2015 16:52:26 +0100 Subject: [PATCH 46/59] mfd: arizona: Fix incorrect Makefile conditionals The use of ifneq against 'n' to conditionally compile codec-specific parts is wrong and was resulting in all the codec tables being built even for deselected codecs. Signed-off-by: Richard Fitzgerald Signed-off-by: Lee Jones --- drivers/mfd/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 0e5cfeba107c..ea40e076cb61 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -39,13 +39,13 @@ obj-$(CONFIG_MFD_ARIZONA) += arizona-core.o obj-$(CONFIG_MFD_ARIZONA) += arizona-irq.o obj-$(CONFIG_MFD_ARIZONA_I2C) += arizona-i2c.o obj-$(CONFIG_MFD_ARIZONA_SPI) += arizona-spi.o -ifneq ($(CONFIG_MFD_WM5102),n) +ifeq ($(CONFIG_MFD_WM5102),y) obj-$(CONFIG_MFD_ARIZONA) += wm5102-tables.o endif -ifneq ($(CONFIG_MFD_WM5110),n) +ifeq ($(CONFIG_MFD_WM5110),y) obj-$(CONFIG_MFD_ARIZONA) += wm5110-tables.o endif -ifneq ($(CONFIG_MFD_WM8997),n) +ifeq ($(CONFIG_MFD_WM8997),y) obj-$(CONFIG_MFD_ARIZONA) += wm8997-tables.o endif obj-$(CONFIG_MFD_WM8400) += wm8400-core.o From 9011e4a8a6fe57f76511609930ed00d305389089 Mon Sep 17 00:00:00 2001 From: Steve Twiss Date: Tue, 19 May 2015 11:32:45 +0100 Subject: [PATCH 47/59] mfd: da9063: Add support for OnKey driver Add MFD support for the DA9063 OnKey driver The function da9063_clear_fault_log() is added to mitigate the case of a hardware power-cut after a long-long OnKey press. Although there is no software intervention in this case (by definition) such a shutdown would cause persistent information within the DA9063 FAULT_LOG that would be available during the next device restart. Clearance of this persistent register must be completed after such a hardware power-cut operation has happened so that the FAULT_LOG does not continue with previous values. The clearance function has been added here in the kernel driver because wiping the fault-log cannot be counted on outside the Linux kernel. Signed-off-by: Steve Twiss [Lee: Removed 'key_power' for Dmitry to take through the Input Tree] Signed-off-by: Lee Jones --- drivers/mfd/da9063-core.c | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/drivers/mfd/da9063-core.c b/drivers/mfd/da9063-core.c index facd3610ac77..af841c165787 100644 --- a/drivers/mfd/da9063-core.c +++ b/drivers/mfd/da9063-core.c @@ -60,6 +60,7 @@ static struct resource da9063_rtc_resources[] = { static struct resource da9063_onkey_resources[] = { { + .name = "ONKEY", .start = DA9063_IRQ_ONKEY, .end = DA9063_IRQ_ONKEY, .flags = IORESOURCE_IRQ, @@ -97,6 +98,7 @@ static const struct mfd_cell da9063_devs[] = { .name = DA9063_DRVNAME_ONKEY, .num_resources = ARRAY_SIZE(da9063_onkey_resources), .resources = da9063_onkey_resources, + .of_compatible = "dlg,da9063-onkey", }, { .name = DA9063_DRVNAME_RTC, @@ -109,12 +111,64 @@ static const struct mfd_cell da9063_devs[] = { }, }; +static int da9063_clear_fault_log(struct da9063 *da9063) +{ + int ret = 0; + int fault_log = 0; + + ret = regmap_read(da9063->regmap, DA9063_REG_FAULT_LOG, &fault_log); + if (ret < 0) { + dev_err(da9063->dev, "Cannot read FAULT_LOG.\n"); + return -EIO; + } + + if (fault_log) { + if (fault_log & DA9063_TWD_ERROR) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_TWD_ERROR\n"); + if (fault_log & DA9063_POR) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_POR\n"); + if (fault_log & DA9063_VDD_FAULT) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_VDD_FAULT\n"); + if (fault_log & DA9063_VDD_START) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_VDD_START\n"); + if (fault_log & DA9063_TEMP_CRIT) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_TEMP_CRIT\n"); + if (fault_log & DA9063_KEY_RESET) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_KEY_RESET\n"); + if (fault_log & DA9063_NSHUTDOWN) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_NSHUTDOWN\n"); + if (fault_log & DA9063_WAIT_SHUT) + dev_dbg(da9063->dev, + "Fault log entry detected: DA9063_WAIT_SHUT\n"); + } + + ret = regmap_write(da9063->regmap, + DA9063_REG_FAULT_LOG, + fault_log); + if (ret < 0) + dev_err(da9063->dev, + "Cannot reset FAULT_LOG values %d\n", ret); + + return ret; +} + int da9063_device_init(struct da9063 *da9063, unsigned int irq) { struct da9063_pdata *pdata = da9063->dev->platform_data; int model, variant_id, variant_code; int ret; + ret = da9063_clear_fault_log(da9063); + if (ret < 0) + dev_err(da9063->dev, "Cannot clear fault log\n"); + if (pdata) { da9063->flags = pdata->flags; da9063->irq_base = pdata->irq_base; From 557e86e5c9613b46f19b7aac834aa6aa7cd2cc28 Mon Sep 17 00:00:00 2001 From: Steve Twiss Date: Tue, 19 May 2015 11:32:45 +0100 Subject: [PATCH 48/59] mfd: dt: Add bindings for DA9063 OnKey Add device tree bindings for the DA9063 OnKey driver. Signed-off-by: Steve Twiss Acked-by: Rob Herring Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/da9063.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/da9063.txt b/Documentation/devicetree/bindings/mfd/da9063.txt index 42c6fa6f1c9a..05b21bcb8543 100644 --- a/Documentation/devicetree/bindings/mfd/da9063.txt +++ b/Documentation/devicetree/bindings/mfd/da9063.txt @@ -5,6 +5,7 @@ DA9093 consists of a large and varied group of sub-devices (I2C Only): Device Supply Names Description ------ ------------ ----------- da9063-regulator : : LDOs & BUCKs +da9063-onkey : : On Key da9063-rtc : : Real-Time Clock da9063-watchdog : : Watchdog @@ -51,6 +52,18 @@ Sub-nodes: the DA9063. There are currently no entries in this binding, however compatible = "dlg,da9063-rtc" should be added if a node is created. +- onkey : This node defines the OnKey settings for controlling the key + functionality of the device. The node should contain the compatible property + with the value "dlg,da9063-onkey". + + Optional onkey properties: + + - dlg,disable-key-power : Disable power-down using a long key-press. If this + entry exists the OnKey driver will remove support for the KEY_POWER key + press. If this entry does not exist then by default the key-press + triggered power down is enabled and the OnKey will support both KEY_POWER + and KEY_SLEEP. + - watchdog : This node defines settings for the Watchdog timer associated with the DA9063. There are currently no entries in this binding, however compatible = "dlg,da9063-watchdog" should be added if a node is created. @@ -73,6 +86,11 @@ Example: compatible = "dlg,da9063-watchdog"; }; + onkey { + compatible = "dlg,da9063-onkey"; + dlg,disable-key-power; + }; + regulators { DA9063_BCORE1: bcore1 { regulator-name = "BCORE1"; From 056c3e751e0b90c7e07eea06038214ff760ef71a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 25 May 2015 10:28:34 +0100 Subject: [PATCH 49/59] mfd: arizona: Add convience defines for micd_rate/micd_bias_starttime Signed-off-by: Charles Keepax Signed-off-by: Lee Jones --- include/dt-bindings/mfd/arizona.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/dt-bindings/mfd/arizona.h b/include/dt-bindings/mfd/arizona.h index c7af7c7ef793..555609910acb 100644 --- a/include/dt-bindings/mfd/arizona.h +++ b/include/dt-bindings/mfd/arizona.h @@ -90,4 +90,18 @@ #define ARIZONA_INMODE_SE 1 #define ARIZONA_INMODE_DMIC 2 +#define ARIZONA_MICD_TIME_CONTINUOUS 0 +#define ARIZONA_MICD_TIME_250US 1 +#define ARIZONA_MICD_TIME_500US 2 +#define ARIZONA_MICD_TIME_1MS 3 +#define ARIZONA_MICD_TIME_2MS 4 +#define ARIZONA_MICD_TIME_4MS 5 +#define ARIZONA_MICD_TIME_8MS 6 +#define ARIZONA_MICD_TIME_16MS 7 +#define ARIZONA_MICD_TIME_32MS 8 +#define ARIZONA_MICD_TIME_64MS 9 +#define ARIZONA_MICD_TIME_128MS 10 +#define ARIZONA_MICD_TIME_256MS 11 +#define ARIZONA_MICD_TIME_512MS 12 + #endif From cf55078b88c83bbf18e46dc4160d0e5b7e60799d Mon Sep 17 00:00:00 2001 From: Hongzhou Yang Date: Wed, 27 May 2015 02:10:35 -0700 Subject: [PATCH 50/59] mfd: mt6397-core: Add GPIO sub-module support Signed-off-by: Hongzhou Yang Acked-by: Linus Walleij Signed-off-by: Lee Jones --- drivers/mfd/mt6397-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index 32775ebbce18..38a0458f7834 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -34,6 +34,9 @@ static const struct mfd_cell mt6397_devs[] = { }, { .name = "mt6397-clk", .of_compatible = "mediatek,mt6397-clk", + }, { + .name = "mt6397-pinctrl", + .of_compatible = "mediatek,mt6397-pinctrl", }, }; From 7d5b1ed89bbb3614830c96420050088f3e0fb886 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:10 -0300 Subject: [PATCH 51/59] mfd: twl4030-irq: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/twl4030-irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c index 1b772ef761cb..a3fa7f4f1fb4 100644 --- a/drivers/mfd/twl4030-irq.c +++ b/drivers/mfd/twl4030-irq.c @@ -674,7 +674,7 @@ int twl4030_sih_setup(struct device *dev, int module, int irq_base) irq_set_handler_data(irq, agent); agent->irq_name = kasprintf(GFP_KERNEL, "twl4030_%s", sih->name); status = request_threaded_irq(irq, NULL, handle_twl4030_sih, - IRQF_EARLY_RESUME, + IRQF_EARLY_RESUME | IRQF_ONESHOT, agent->irq_name ?: sih->name, NULL); dev_info(dev, "%s (irq %d) chaining IRQs %d..%d\n", sih->name, From 0b182c287c5a219f7ae35d977de4de331215820e Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:11 -0300 Subject: [PATCH 52/59] mfd: mc13xxx-core: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/mc13xxx-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 25fd7116493a..3f9f4c874d2a 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -163,7 +163,7 @@ int mc13xxx_irq_request(struct mc13xxx *mc13xxx, int irq, int virq = regmap_irq_get_virq(mc13xxx->irq_data, irq); return devm_request_threaded_irq(mc13xxx->dev, virq, NULL, handler, - 0, name, dev); + IRQF_ONESHOT, name, dev); } EXPORT_SYMBOL(mc13xxx_irq_request); From 1a52598c4b1d358af7ac562defd98c395842dbdd Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:12 -0300 Subject: [PATCH 53/59] mfd: wm831x-auxadc: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/wm831x-auxadc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/wm831x-auxadc.c b/drivers/mfd/wm831x-auxadc.c index 6ee3018d8653..fd789d2eb0f5 100644 --- a/drivers/mfd/wm831x-auxadc.c +++ b/drivers/mfd/wm831x-auxadc.c @@ -285,7 +285,8 @@ void wm831x_auxadc_init(struct wm831x *wm831x) ret = request_threaded_irq(wm831x_irq(wm831x, WM831X_IRQ_AUXADC_DATA), - NULL, wm831x_auxadc_irq, 0, + NULL, wm831x_auxadc_irq, + IRQF_ONESHOT, "auxadc", wm831x); if (ret < 0) { dev_err(wm831x->dev, "AUXADC IRQ request failed: %d\n", From cea2cc735ae125187a28cd30a000ad62749f948e Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:13 -0300 Subject: [PATCH 54/59] mfd: htc-i2cpld: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/htc-i2cpld.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/htc-i2cpld.c b/drivers/mfd/htc-i2cpld.c index ebb9cf19e347..b54baad30164 100644 --- a/drivers/mfd/htc-i2cpld.c +++ b/drivers/mfd/htc-i2cpld.c @@ -564,7 +564,8 @@ static int htcpld_core_probe(struct platform_device *pdev) htcpld->chained_irq = res->start; /* Setup the chained interrupt handler */ - flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT; ret = request_threaded_irq(htcpld->chained_irq, NULL, htcpld_handler, flags, pdev->name, htcpld); From b3c8c82b48968fa816922114e259620921c3a29a Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:14 -0300 Subject: [PATCH 55/59] mfd: wm8350-core: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/wm8350-core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c index f5124a8acad8..8a07c5634aee 100644 --- a/drivers/mfd/wm8350-core.c +++ b/drivers/mfd/wm8350-core.c @@ -404,7 +404,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq, if (wm8350->irq_base) { ret = request_threaded_irq(wm8350->irq_base + WM8350_IRQ_AUXADC_DATARDY, - NULL, wm8350_auxadc_irq, 0, + NULL, wm8350_auxadc_irq, + IRQF_ONESHOT, "auxadc", wm8350); if (ret < 0) dev_warn(wm8350->dev, From abe5b47c3c158b682c8ed7becd59249d6d19938c Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:15 -0300 Subject: [PATCH 56/59] mfd: ab8500-debugfs: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/ab8500-debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index cdd6f3d63314..0236cd7cdce4 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -2885,7 +2885,7 @@ static ssize_t ab8500_subscribe_write(struct file *file, } err = request_threaded_irq(user_val, NULL, ab8500_debug_handler, - IRQF_SHARED | IRQF_NO_SUSPEND, + IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, "ab8500-debug", &dev->kobj); if (err < 0) { pr_info("request_threaded_irq failed %d, %lu\n", From e7d8ae3e7b01931ad2d75e4e2840313a3d05c85e Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:16 -0300 Subject: [PATCH 57/59] mfd: ab8500-gpadc: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/ab8500-gpadc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index dabbc93abdd7..c51c1b188d64 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -948,7 +948,8 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) if (gpadc->irq_sw >= 0) { ret = request_threaded_irq(gpadc->irq_sw, NULL, ab8500_bm_gpadcconvend_handler, - IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-sw", + IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, + "ab8500-gpadc-sw", gpadc); if (ret < 0) { dev_err(gpadc->dev, @@ -961,7 +962,8 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) if (gpadc->irq_hw >= 0) { ret = request_threaded_irq(gpadc->irq_hw, NULL, ab8500_bm_gpadcconvend_handler, - IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-hw", + IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, + "ab8500-gpadc-hw", gpadc); if (ret < 0) { dev_err(gpadc->dev, From ef76cc5bae0be6e382af0596fc9eb59d0546d50b Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 16 May 2015 15:42:17 -0300 Subject: [PATCH 58/59] mfd: si476x-i2c: Pass the IRQF_ONESHOT flag Since commit 1c6c69525b40eb76de8adf039409722015927dc3 ("genirq: Reject bogus threaded irq requests") threaded IRQs without a primary handler need to be requested with IRQF_ONESHOT, otherwise the request will fail. So pass the IRQF_ONESHOT flag in this case. The semantic patch that makes this change is available in scripts/coccinelle/misc/irqf_oneshot.cocci. Signed-off-by: Fabio Estevam Signed-off-by: Lee Jones --- drivers/mfd/si476x-i2c.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c index 7f87c62d91b3..e3deb466628b 100644 --- a/drivers/mfd/si476x-i2c.c +++ b/drivers/mfd/si476x-i2c.c @@ -777,7 +777,8 @@ static int si476x_core_probe(struct i2c_client *client, rval = devm_request_threaded_irq(&client->dev, client->irq, NULL, si476x_core_interrupt, - IRQF_TRIGGER_FALLING, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, client->name, core); if (rval < 0) { dev_err(&client->dev, "Could not request IRQ %d\n", From 1abf25a25b86dcfe28d243a5af71bd1c9d6de1ef Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 9 Jun 2015 12:17:07 +0300 Subject: [PATCH 59/59] mfd: lpc_ich: Assign subdevice ids automatically Using -1 as platform device id means that the platform driver core will not assign any id to the device (the device name will not have id at all). This results problems on systems that have multiple PCHs (Platform Controller HUBs) because all of them also include their own copy of LPC device. All the subsequent device creations will fail because there already exists platform device with the same name. Fix this by passing PLATFORM_DEVID_AUTO as platform device id. This makes the platform device core to allocate new ids automatically. Signed-off-by: Mika Westerberg Signed-off-by: Lee Jones --- drivers/mfd/lpc_ich.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c index 12d960a60ec4..8de34398abc0 100644 --- a/drivers/mfd/lpc_ich.c +++ b/drivers/mfd/lpc_ich.c @@ -934,8 +934,8 @@ gpe0_done: lpc_ich_enable_gpio_space(dev); lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_GPIO]); - ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_GPIO], - 1, NULL, 0, NULL); + ret = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, + &lpc_ich_cells[LPC_GPIO], 1, NULL, 0, NULL); gpio_done: if (acpi_conflict) @@ -1008,8 +1008,8 @@ static int lpc_ich_init_wdt(struct pci_dev *dev) } lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_WDT]); - ret = mfd_add_devices(&dev->dev, -1, &lpc_ich_cells[LPC_WDT], - 1, NULL, 0, NULL); + ret = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, + &lpc_ich_cells[LPC_WDT], 1, NULL, 0, NULL); wdt_done: return ret;