From 47910a49db876397150b9754bc66f0c179448854 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 27 Feb 2017 09:18:59 -0600 Subject: fpga: fix sparse warnings in fpga-mgr and fpga-bridge Fix up these sparse warnings: drivers/fpga/fpga-mgr.c:189:21: warning: symbol '__fpga_mgr_get' was not declared. Should it be static? drivers/fpga/fpga-bridge.c:30:12: warning: symbol 'bridge_list_lock' was not declared. Should it be static? Signed-off-by: Dinh Nguyen Acked-by: Moritz Fischer Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-bridge.c | 2 +- drivers/fpga/fpga-mgr.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index 33ee83e6373c..e04a899d1a90 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -27,7 +27,7 @@ static DEFINE_IDA(fpga_bridge_ida); static struct class *fpga_bridge_class; /* Lock for adding/removing bridges to linked lists*/ -spinlock_t bridge_list_lock; +static spinlock_t bridge_list_lock; static int fpga_bridge_of_node_match(struct device *dev, const void *data) { diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 86d2cb203533..188ffefa3cc3 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -361,7 +361,7 @@ static struct attribute *fpga_mgr_attrs[] = { }; ATTRIBUTE_GROUPS(fpga_mgr); -struct fpga_manager *__fpga_mgr_get(struct device *dev) +static struct fpga_manager *__fpga_mgr_get(struct device *dev) { struct fpga_manager *mgr; int ret = -ENODEV; -- cgit v1.2.3-70-g09d2 From 0f4f0c8ff1da9171bca0dc01ce5551e8b6d2f0f3 Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Mon, 27 Feb 2017 09:19:00 -0600 Subject: fpga: Add flag to indicate bitstream needs decrypting Add a flag that is passed to the write_init() callback, indicating that the bitstream is encrypted. The low-level driver will deal with the flag, or return an error, if encrypted bitstreams are not supported. Signed-off-by: Moritz Fischer Acked-by: Michal Simek Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- include/linux/fpga/fpga-mgr.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 57beb5d09bfc..e2ef94fd37af 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -70,6 +70,7 @@ enum fpga_mgr_states { */ #define FPGA_MGR_PARTIAL_RECONFIG BIT(0) #define FPGA_MGR_EXTERNAL_CONFIG BIT(1) +#define FPGA_MGR_ENCRYPTED_BITSTREAM BIT(2) /** * struct fpga_image_info - information specific to a FPGA image -- cgit v1.2.3-70-g09d2 From 7f33bbca14de25f24660441af5087440dd0d2fca Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Mon, 27 Feb 2017 09:19:01 -0600 Subject: fpga: zynq: Add support for encrypted bitstreams Add support for encrypted bitstreams. For this to work the system must be booted in secure mode. In order for on-the-fly decryption to work, the PCAP clock rate needs to be lowered via the PCAP_RATE_EN bit. Signed-off-by: Moritz Fischer Acked-by: Michal Simek Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/zynq-fpga.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index 34cb98139442..70b15b303471 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -72,6 +72,10 @@ #define CTRL_PCAP_PR_MASK BIT(27) /* Enable PCAP */ #define CTRL_PCAP_MODE_MASK BIT(26) +/* Lower rate to allow decrypt on the fly */ +#define CTRL_PCAP_RATE_EN_MASK BIT(25) +/* System booted in secure mode */ +#define CTRL_SEC_EN_MASK BIT(7) /* Miscellaneous Control Register bit definitions */ /* Internal PCAP loopback */ @@ -266,6 +270,17 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, if (err) return err; + /* check if bitstream is encrypted & and system's still secure */ + if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) { + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + if (!(ctrl & CTRL_SEC_EN_MASK)) { + dev_err(&mgr->dev, + "System not secure, can't use crypted bitstreams\n"); + err = -EINVAL; + goto out_err; + } + } + /* don't globally reset PL if we're doing partial reconfig */ if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { if (!zynq_fpga_has_sync(buf, count)) { @@ -337,12 +352,19 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, /* set configuration register with following options: * - enable PCAP interface - * - set throughput for maximum speed + * - set throughput for maximum speed (if bistream not crypted) * - set CPU in user mode */ ctrl = zynq_fpga_read(priv, CTRL_OFFSET); - zynq_fpga_write(priv, CTRL_OFFSET, - (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl)); + if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) + zynq_fpga_write(priv, CTRL_OFFSET, + (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK + | CTRL_PCAP_RATE_EN_MASK | ctrl)); + else + zynq_fpga_write(priv, CTRL_OFFSET, + (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK + | ctrl)); + /* We expect that the command queue is empty right now. */ status = zynq_fpga_read(priv, STATUS_OFFSET); -- cgit v1.2.3-70-g09d2 From 161db575ef6c3b0a6d96dc263abb39cd0dc0f0c2 Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Mon, 27 Feb 2017 09:19:02 -0600 Subject: fpga: region: Add fpga-region property 'encrypted-fpga-config' Add fpga-region property to allow passing the fact that the bitstream is encrypted to the fpga-region and ultimately to the low-level driver. Signed-off-by: Moritz Fischer Acked-by: Michal Simek Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/fpga/fpga-region.txt | 1 + drivers/fpga/fpga-region.c | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/fpga/fpga-region.txt b/Documentation/devicetree/bindings/fpga/fpga-region.txt index 3b32ba15a717..81bf3adba24b 100644 --- a/Documentation/devicetree/bindings/fpga/fpga-region.txt +++ b/Documentation/devicetree/bindings/fpga/fpga-region.txt @@ -186,6 +186,7 @@ Optional properties: otherwise full reconfiguration is done. - external-fpga-config : boolean, set if the FPGA has already been configured prior to OS boot up. +- encrypted-fpga-config : boolean, set if the bitstream is encrypted - region-unfreeze-timeout-us : The maximum time in microseconds to wait for bridges to successfully become enabled after the region has been programmed. diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 3222fdbad75a..2fe2a52c66ca 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -337,8 +337,9 @@ static int child_regions_with_firmware(struct device_node *overlay) * The overlay must add either firmware-name or external-fpga-config property * to the FPGA Region. * - * firmware-name : program the FPGA - * external-fpga-config : FPGA is already programmed + * firmware-name : program the FPGA + * external-fpga-config : FPGA is already programmed + * encrypted-fpga-config : FPGA bitstream is encrypted * * The overlay can add other FPGA regions, but child FPGA regions cannot have a * firmware-name property since those regions don't exist yet. @@ -373,6 +374,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, if (of_property_read_bool(nd->overlay, "external-fpga-config")) info->flags |= FPGA_MGR_EXTERNAL_CONFIG; + if (of_property_read_bool(nd->overlay, "encrypted-fpga-config")) + info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM; + of_property_read_string(nd->overlay, "firmware-name", &firmware_name); of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us", -- cgit v1.2.3-70-g09d2 From 4348f7e2ae250d9b986b08c8e8ea8a402790f369 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 27 Feb 2017 16:14:22 -0600 Subject: FPGA: Add TS-7300 FPGA manager Add support for loading bitstreams on the Altera Cyclone II FPGA populated on the TS-7300 board. This is done through the configuration and data registers offered through a memory interface between the EP93xx SoC and the FPGA via an intermediate CPLD device. The EP93xx SoC on the TS-7300 does not have direct means of configuring the on-board FPGA other than by using the special memory mapped interface to the CPLD. No other entity on the system can control the FPGA bitstream. Signed-off-by: Florian Fainelli Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 7 ++ drivers/fpga/Makefile | 1 + drivers/fpga/ts73xx-fpga.c | 156 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 drivers/fpga/ts73xx-fpga.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index ce861a2853a4..d9cbef60db80 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -33,6 +33,13 @@ config FPGA_MGR_SOCFPGA_A10 help FPGA manager driver support for Altera Arria10 SoCFPGA. +config FPGA_MGR_TS73XX + tristate "Technologic Systems TS-73xx SBC FPGA Manager" + depends on ARCH_EP93XX && MACH_TS72XX + help + FPGA manager driver support for the Altera Cyclone II FPGA + present on the TS-73xx SBC boards. + config FPGA_MGR_ZYNQ_FPGA tristate "Xilinx Zynq FPGA" depends on ARCH_ZYNQ || COMPILE_TEST diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 8df07bcf42a6..a1160169e6d9 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o +obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o # FPGA Bridge Drivers diff --git a/drivers/fpga/ts73xx-fpga.c b/drivers/fpga/ts73xx-fpga.c new file mode 100644 index 000000000000..f6a96b42e2ca --- /dev/null +++ b/drivers/fpga/ts73xx-fpga.c @@ -0,0 +1,156 @@ +/* + * Technologic Systems TS-73xx SBC FPGA loader + * + * Copyright (C) 2016 Florian Fainelli + * + * FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on + * TS-7300, heavily based on load_fpga.c in their vendor tree. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include +#include + +#define TS73XX_FPGA_DATA_REG 0 +#define TS73XX_FPGA_CONFIG_REG 1 + +#define TS73XX_FPGA_WRITE_DONE 0x1 +#define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */ +#define TS73XX_FPGA_RESET 0x2 +#define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */ +#define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */ +#define TS73XX_FPGA_LOAD_OK 0x4 +#define TS73XX_FPGA_CONFIG_LOAD 0x8 + +struct ts73xx_fpga_priv { + void __iomem *io_base; + struct device *dev; +}; + +static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static int ts73xx_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + + /* Reset the FPGA */ + writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG); + udelay(TS73XX_FPGA_RESET_LOW_DELAY); + writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG); + udelay(TS73XX_FPGA_RESET_HIGH_DELAY); + + return 0; +} + +static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + size_t i = 0; + int ret; + u8 reg; + + while (count--) { + ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG, + reg, !(reg & TS73XX_FPGA_WRITE_DONE), + 1, TS73XX_FPGA_WRITE_DONE_TIMEOUT); + if (ret < 0) + return ret; + + writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG); + i++; + } + + return 0; +} + +static int ts73xx_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + u8 reg; + + usleep_range(1000, 2000); + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + reg |= TS73XX_FPGA_CONFIG_LOAD; + writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); + + usleep_range(1000, 2000); + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + reg &= ~TS73XX_FPGA_CONFIG_LOAD; + writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); + + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK) + return -ETIMEDOUT; + + return 0; +} + +static const struct fpga_manager_ops ts73xx_fpga_ops = { + .state = ts73xx_fpga_state, + .write_init = ts73xx_fpga_write_init, + .write = ts73xx_fpga_write, + .write_complete = ts73xx_fpga_write_complete, +}; + +static int ts73xx_fpga_probe(struct platform_device *pdev) +{ + struct device *kdev = &pdev->dev; + struct ts73xx_fpga_priv *priv; + struct resource *res; + + priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = kdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(kdev, res); + if (IS_ERR(priv->io_base)) { + dev_err(kdev, "unable to remap registers\n"); + return PTR_ERR(priv->io_base); + } + + return fpga_mgr_register(kdev, "TS-73xx FPGA Manager", + &ts73xx_fpga_ops, priv); +} + +static int ts73xx_fpga_remove(struct platform_device *pdev) +{ + fpga_mgr_unregister(&pdev->dev); + + return 0; +} + +static struct platform_driver ts73xx_fpga_driver = { + .driver = { + .name = "ts73xx-fpga-mgr", + }, + .probe = ts73xx_fpga_probe, + .remove = ts73xx_fpga_remove, +}; +module_platform_driver(ts73xx_fpga_driver); + +MODULE_AUTHOR("Florian Fainelli "); +MODULE_DESCRIPTION("TS-73xx FPGA Manager driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From f677e0f3ed45a4fcd6270d43b25ff9abb92da608 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 27 Feb 2017 16:14:23 -0600 Subject: ARM: ep93xx: Register ts73xx-fpga manager driver for TS-7300 Register the TS-7300 FPGA manager device drivers which allows us to load bitstreams into the on-board Altera Cyclone II FPGA. Acked-by: H Hartley Sweeten Signed-off-by: Florian Fainelli Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- arch/arm/mach-ep93xx/ts72xx.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/arm/mach-ep93xx/ts72xx.c b/arch/arm/mach-ep93xx/ts72xx.c index 8a5b6f059498..55b186ef863a 100644 --- a/arch/arm/mach-ep93xx/ts72xx.c +++ b/arch/arm/mach-ep93xx/ts72xx.c @@ -210,6 +210,28 @@ static struct ep93xx_eth_data __initdata ts72xx_eth_data = { .phy_id = 1, }; +#if IS_ENABLED(CONFIG_FPGA_MGR_TS73XX) + +/* Relative to EP93XX_CS1_PHYS_BASE */ +#define TS73XX_FPGA_LOADER_BASE 0x03c00000 + +static struct resource ts73xx_fpga_resources[] = { + { + .start = EP93XX_CS1_PHYS_BASE + TS73XX_FPGA_LOADER_BASE, + .end = EP93XX_CS1_PHYS_BASE + TS73XX_FPGA_LOADER_BASE + 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device ts73xx_fpga_device = { + .name = "ts73xx-fpga-mgr", + .id = -1, + .resource = ts73xx_fpga_resources, + .num_resources = ARRAY_SIZE(ts73xx_fpga_resources), +}; + +#endif + static void __init ts72xx_init_machine(void) { ep93xx_init_devices(); @@ -218,6 +240,10 @@ static void __init ts72xx_init_machine(void) platform_device_register(&ts72xx_wdt_device); ep93xx_register_eth(&ts72xx_eth_data, 1); +#if IS_ENABLED(CONFIG_FPGA_MGR_TS73XX) + if (board_is_ts7300()) + platform_device_register(&ts73xx_fpga_device); +#endif } MACHINE_START(TS72XX, "Technologic Systems TS-72xx SBC") -- cgit v1.2.3-70-g09d2 From e10c11d2739fd3657d8d1d5b1fa06f9185b4f012 Mon Sep 17 00:00:00 2001 From: Joel Holdsworth Date: Mon, 27 Feb 2017 16:14:25 -0600 Subject: Documentation: Add binding document for Lattice iCE40 FPGA manager This adds documentation of the device tree bindings of the Lattice iCE40 FPGA driver for the FPGA manager framework. Signed-off-by: Joel Holdsworth Acked-by: Rob Herring Acked-by: Alan Tull Acked-by: Moritz Fischer Acked-by: Marek Vasut Signed-off-by: Greg Kroah-Hartman --- .../bindings/fpga/lattice-ice40-fpga-mgr.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/devicetree/bindings/fpga/lattice-ice40-fpga-mgr.txt diff --git a/Documentation/devicetree/bindings/fpga/lattice-ice40-fpga-mgr.txt b/Documentation/devicetree/bindings/fpga/lattice-ice40-fpga-mgr.txt new file mode 100644 index 000000000000..4dc412437b08 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/lattice-ice40-fpga-mgr.txt @@ -0,0 +1,21 @@ +Lattice iCE40 FPGA Manager + +Required properties: +- compatible: Should contain "lattice,ice40-fpga-mgr" +- reg: SPI chip select +- spi-max-frequency: Maximum SPI frequency (>=1000000, <=25000000) +- cdone-gpios: GPIO input connected to CDONE pin +- reset-gpios: Active-low GPIO output connected to CRESET_B pin. Note + that unless the GPIO is held low during startup, the + FPGA will enter Master SPI mode and drive SCK with a + clock signal potentially jamming other devices on the + bus until the firmware is loaded. + +Example: + fpga: fpga@0 { + compatible = "lattice,ice40-fpga-mgr"; + reg = <0>; + spi-max-frequency = <1000000>; + cdone-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio 22 GPIO_ACTIVE_LOW>; + }; -- cgit v1.2.3-70-g09d2 From 21f8ba2ef378d906374a26f7abc05fb92219e9c6 Mon Sep 17 00:00:00 2001 From: Joel Holdsworth Date: Mon, 27 Feb 2017 16:14:26 -0600 Subject: fpga: Add support for Lattice iCE40 FPGAs This patch adds support to the FPGA manager for configuring the SRAM of iCE40LM, iCE40LP, iCE40HX, iCE40 Ultra, iCE40 UltraLite and iCE40 UltraPlus devices, through slave SPI. Signed-off-by: Joel Holdsworth Reviewed-by: Marek Vasut Reviewed-by: Moritz Fischer Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 6 ++ drivers/fpga/Makefile | 1 + drivers/fpga/ice40-spi.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 drivers/fpga/ice40-spi.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index d9cbef60db80..dee470f9113f 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -20,6 +20,12 @@ config FPGA_REGION FPGA Regions allow loading FPGA images under control of the Device Tree. +config FPGA_MGR_ICE40_SPI + tristate "Lattice iCE40 SPI" + depends on OF && SPI + help + FPGA manager driver support for Lattice iCE40 FPGAs over SPI. + config FPGA_MGR_SOCFPGA tristate "Altera SOCFPGA FPGA Manager" depends on ARCH_SOCFPGA || COMPILE_TEST diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index a1160169e6d9..a5ee3ffe8b18 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers +obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o diff --git a/drivers/fpga/ice40-spi.c b/drivers/fpga/ice40-spi.c new file mode 100644 index 000000000000..7fca82023062 --- /dev/null +++ b/drivers/fpga/ice40-spi.c @@ -0,0 +1,207 @@ +/* + * FPGA Manager Driver for Lattice iCE40. + * + * Copyright (c) 2016 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This driver adds support to the FPGA manager for configuring the SRAM of + * Lattice iCE40 FPGAs through slave SPI. + */ + +#include +#include +#include +#include +#include +#include + +#define ICE40_SPI_MAX_SPEED 25000000 /* Hz */ +#define ICE40_SPI_MIN_SPEED 1000000 /* Hz */ + +#define ICE40_SPI_RESET_DELAY 1 /* us (>200ns) */ +#define ICE40_SPI_HOUSEKEEPING_DELAY 1200 /* us */ + +#define ICE40_SPI_NUM_ACTIVATION_BYTES DIV_ROUND_UP(49, 8) + +struct ice40_fpga_priv { + struct spi_device *dev; + struct gpio_desc *reset; + struct gpio_desc *cdone; +}; + +static enum fpga_mgr_states ice40_fpga_ops_state(struct fpga_manager *mgr) +{ + struct ice40_fpga_priv *priv = mgr->priv; + + return gpiod_get_value(priv->cdone) ? FPGA_MGR_STATE_OPERATING : + FPGA_MGR_STATE_UNKNOWN; +} + +static int ice40_fpga_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct ice40_fpga_priv *priv = mgr->priv; + struct spi_device *dev = priv->dev; + struct spi_message message; + struct spi_transfer assert_cs_then_reset_delay = { + .cs_change = 1, + .delay_usecs = ICE40_SPI_RESET_DELAY + }; + struct spi_transfer housekeeping_delay_then_release_cs = { + .delay_usecs = ICE40_SPI_HOUSEKEEPING_DELAY + }; + int ret; + + if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&dev->dev, + "Partial reconfiguration is not supported\n"); + return -ENOTSUPP; + } + + /* Lock the bus, assert CRESET_B and SS_B and delay >200ns */ + spi_bus_lock(dev->master); + + gpiod_set_value(priv->reset, 1); + + spi_message_init(&message); + spi_message_add_tail(&assert_cs_then_reset_delay, &message); + ret = spi_sync_locked(dev, &message); + + /* Come out of reset */ + gpiod_set_value(priv->reset, 0); + + /* Abort if the chip-select failed */ + if (ret) + goto fail; + + /* Check CDONE is de-asserted i.e. the FPGA is reset */ + if (gpiod_get_value(priv->cdone)) { + dev_err(&dev->dev, "Device reset failed, CDONE is asserted\n"); + ret = -EIO; + goto fail; + } + + /* Wait for the housekeeping to complete, and release SS_B */ + spi_message_init(&message); + spi_message_add_tail(&housekeeping_delay_then_release_cs, &message); + ret = spi_sync_locked(dev, &message); + +fail: + spi_bus_unlock(dev->master); + + return ret; +} + +static int ice40_fpga_ops_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct ice40_fpga_priv *priv = mgr->priv; + + return spi_write(priv->dev, buf, count); +} + +static int ice40_fpga_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct ice40_fpga_priv *priv = mgr->priv; + struct spi_device *dev = priv->dev; + const u8 padding[ICE40_SPI_NUM_ACTIVATION_BYTES] = {0}; + + /* Check CDONE is asserted */ + if (!gpiod_get_value(priv->cdone)) { + dev_err(&dev->dev, + "CDONE was not asserted after firmware transfer\n"); + return -EIO; + } + + /* Send of zero-padding to activate the firmware */ + return spi_write(dev, padding, sizeof(padding)); +} + +static const struct fpga_manager_ops ice40_fpga_ops = { + .state = ice40_fpga_ops_state, + .write_init = ice40_fpga_ops_write_init, + .write = ice40_fpga_ops_write, + .write_complete = ice40_fpga_ops_write_complete, +}; + +static int ice40_fpga_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ice40_fpga_priv *priv; + int ret; + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = spi; + + /* Check board setup data. */ + if (spi->max_speed_hz > ICE40_SPI_MAX_SPEED) { + dev_err(dev, "SPI speed is too high, maximum speed is " + __stringify(ICE40_SPI_MAX_SPEED) "\n"); + return -EINVAL; + } + + if (spi->max_speed_hz < ICE40_SPI_MIN_SPEED) { + dev_err(dev, "SPI speed is too low, minimum speed is " + __stringify(ICE40_SPI_MIN_SPEED) "\n"); + return -EINVAL; + } + + if (spi->mode & SPI_CPHA) { + dev_err(dev, "Bad SPI mode, CPHA not supported\n"); + return -EINVAL; + } + + /* Set up the GPIOs */ + priv->cdone = devm_gpiod_get(dev, "cdone", GPIOD_IN); + if (IS_ERR(priv->cdone)) { + ret = PTR_ERR(priv->cdone); + dev_err(dev, "Failed to get CDONE GPIO: %d\n", ret); + return ret; + } + + priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + dev_err(dev, "Failed to get CRESET_B GPIO: %d\n", ret); + return ret; + } + + /* Register with the FPGA manager */ + return fpga_mgr_register(dev, "Lattice iCE40 FPGA Manager", + &ice40_fpga_ops, priv); +} + +static int ice40_fpga_remove(struct spi_device *spi) +{ + fpga_mgr_unregister(&spi->dev); + return 0; +} + +static const struct of_device_id ice40_fpga_of_match[] = { + { .compatible = "lattice,ice40-fpga-mgr", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ice40_fpga_of_match); + +static struct spi_driver ice40_fpga_driver = { + .probe = ice40_fpga_probe, + .remove = ice40_fpga_remove, + .driver = { + .name = "ice40spi", + .of_match_table = of_match_ptr(ice40_fpga_of_match), + }, +}; + +module_spi_driver(ice40_fpga_driver); + +MODULE_AUTHOR("Joel Holdsworth "); +MODULE_DESCRIPTION("Lattice iCE40 FPGA Manager"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From c37235cce31d5e03b73692f31e994964f18e2dc2 Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Fri, 10 Mar 2017 12:47:11 -0800 Subject: fpga: bridge: Replace open-coded list_for_each + list_entry Replaces open-coded list_for_each() + list_entry() with macro list_for_each_entry() Signed-off-by: Moritz Fischer Cc: linux-fpga@vger.kernel.org Cc: linux-kernel@vger.kernel.org Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-bridge.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index e04a899d1a90..9651aa56244a 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -146,11 +146,9 @@ EXPORT_SYMBOL_GPL(fpga_bridge_put); int fpga_bridges_enable(struct list_head *bridge_list) { struct fpga_bridge *bridge; - struct list_head *node; int ret; - list_for_each(node, bridge_list) { - bridge = list_entry(node, struct fpga_bridge, node); + list_for_each_entry(bridge, bridge_list, node) { ret = fpga_bridge_enable(bridge); if (ret) return ret; @@ -172,11 +170,9 @@ EXPORT_SYMBOL_GPL(fpga_bridges_enable); int fpga_bridges_disable(struct list_head *bridge_list) { struct fpga_bridge *bridge; - struct list_head *node; int ret; - list_for_each(node, bridge_list) { - bridge = list_entry(node, struct fpga_bridge, node); + list_for_each_entry(bridge, bridge_list, node) { ret = fpga_bridge_disable(bridge); if (ret) return ret; @@ -196,13 +192,10 @@ EXPORT_SYMBOL_GPL(fpga_bridges_disable); */ void fpga_bridges_put(struct list_head *bridge_list) { - struct fpga_bridge *bridge; - struct list_head *node, *next; + struct fpga_bridge *bridge, *next; unsigned long flags; - list_for_each_safe(node, next, bridge_list) { - bridge = list_entry(node, struct fpga_bridge, node); - + list_for_each_entry_safe(bridge, next, bridge_list, node) { fpga_bridge_put(bridge); spin_lock_irqsave(&bridge_list_lock, flags); -- cgit v1.2.3-70-g09d2 From ada6eb11137be0baf846e00fca40fc096ba85a28 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:10 -0700 Subject: vmbus: only reschedule tasklet if time limit exceeded The change to reschedule tasklet if more data arrives in ring buffer can cause performance regression if host timing is such that the next response happens in small window. Go back to a modified version of the original looping behavior. If the race occurs in a small time, then loop. But if the tasklet has been running for a long interval due to flood, then reschedule the tasklet to allow migration to ksoftirqd. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 65 ++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index a8366fec1458..fce27fb141cc 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -296,44 +296,47 @@ struct vmbus_channel *relid2channel(u32 relid) /* * vmbus_on_event - Process a channel event notification + * + * For batched channels (default) optimize host to guest signaling + * by ensuring: + * 1. While reading the channel, we disable interrupts from host. + * 2. Ensure that we process all posted messages from the host + * before returning from this callback. + * 3. Once we return, enable signaling from the host. Once this + * state is set we check to see if additional packets are + * available to read. In this case we repeat the process. + * If this tasklet has been running for a long time + * then reschedule ourselves. */ void vmbus_on_event(unsigned long data) { struct vmbus_channel *channel = (void *) data; - void (*callback_fn)(void *); + unsigned long time_limit = jiffies + 2; - /* - * A channel once created is persistent even when there - * is no driver handling the device. An unloading driver - * sets the onchannel_callback to NULL on the same CPU - * as where this interrupt is handled (in an interrupt context). - * Thus, checking and invoking the driver specific callback takes - * care of orderly unloading of the driver. - */ - callback_fn = READ_ONCE(channel->onchannel_callback); - if (unlikely(callback_fn == NULL)) - return; - - (*callback_fn)(channel->channel_callback_context); - - if (channel->callback_mode == HV_CALL_BATCHED) { - /* - * This callback reads the messages sent by the host. - * We can optimize host to guest signaling by ensuring: - * 1. While reading the channel, we disable interrupts from - * host. - * 2. Ensure that we process all posted messages from the host - * before returning from this callback. - * 3. Once we return, enable signaling from the host. Once this - * state is set we check to see if additional packets are - * available to read. In this case we repeat the process. + do { + void (*callback_fn)(void *); + + /* A channel once created is persistent even when + * there is no driver handling the device. An + * unloading driver sets the onchannel_callback to NULL. */ - if (hv_end_read(&channel->inbound) != 0) { - hv_begin_read(&channel->inbound); + callback_fn = READ_ONCE(channel->onchannel_callback); + if (unlikely(callback_fn == NULL)) + return; - tasklet_schedule(&channel->callback_event); - } - } + (*callback_fn)(channel->channel_callback_context); + + if (channel->callback_mode != HV_CALL_BATCHED) + return; + + if (likely(hv_end_read(&channel->inbound) == 0)) + return; + + hv_begin_read(&channel->inbound); + } while (likely(time_before(jiffies, time_limit))); + + /* The time limit (2 jiffies) has been reached */ + tasklet_schedule(&channel->callback_event); } /* -- cgit v1.2.3-70-g09d2 From 670718161852c6acdb8d6bca6a73510f7b9f4320 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:11 -0700 Subject: hyperv: fix warning about missing prototype Compiling with warnings enabled finds missing prototype for hv_do_hypercall. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/hyperv/hv_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c index 8bef70e7f3cc..ecaa3f3b1923 100644 --- a/arch/x86/hyperv/hv_init.c +++ b/arch/x86/hyperv/hv_init.c @@ -25,7 +25,7 @@ #include #include #include - +#include #ifdef CONFIG_X86_64 -- cgit v1.2.3-70-g09d2 From 8b1f91fb4c1a8a860b8edc0c383821b2ff8a1ece Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:12 -0700 Subject: vmbus: remove useless return's No need for empty return at end of void function Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 2 -- drivers/hv/hv_fcopy.c | 2 -- drivers/hv/hv_kvp.c | 2 -- drivers/hv/hv_snapshot.c | 2 -- drivers/hv/ring_buffer.c | 2 -- drivers/hv/vmbus_drv.c | 2 -- include/linux/hyperv.h | 2 -- 7 files changed, 14 deletions(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 5fd03e59cee5..f5728deff893 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -722,8 +722,6 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, 5*HZ); post_status(&dm_device); } - - return; } static void hv_online_page(struct page *pg) diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c index 9aee6014339d..3ce7559d7b41 100644 --- a/drivers/hv/hv_fcopy.c +++ b/drivers/hv/hv_fcopy.c @@ -187,8 +187,6 @@ static void fcopy_send_data(struct work_struct *dummy) } } kfree(smsg_out); - - return; } /* diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index de263712e247..a65b7f88d7aa 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -484,8 +484,6 @@ kvp_send_key(struct work_struct *dummy) } kfree(message); - - return; } /* diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c index bcc03f0748d6..216d02277759 100644 --- a/drivers/hv/hv_snapshot.c +++ b/drivers/hv/hv_snapshot.c @@ -213,8 +213,6 @@ static void vss_send_op(void) } kfree(vss_msg); - - return; } static void vss_handle_request(struct work_struct *dummy) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 87799e81af97..d0ff5b41161a 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -73,8 +73,6 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel) */ if (old_write == READ_ONCE(rbi->ring_buffer->read_index)) vmbus_setevent(channel); - - return; } /* Get the next write location for the specified ring buffer. */ diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index da6b59ba5940..5ca5004861c6 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -787,8 +787,6 @@ static void vmbus_shutdown(struct device *child_device) if (drv->shutdown) drv->shutdown(dev); - - return; } diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 62bbf3c1aa4a..2b1ed66824be 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1500,8 +1500,6 @@ static inline void hv_signal_on_read(struct vmbus_channel *channel) cached_write_sz = hv_get_cached_bytes_to_write(rbi); if (cached_write_sz < pending_sz) vmbus_setevent(channel); - - return; } static inline void -- cgit v1.2.3-70-g09d2 From 2c616a8b6bd3b51b560d994f3412da998fe7ee48 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:13 -0700 Subject: vmbus: remove unnecessary initialization Don't initialize variables that are then set a few lines later. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/ring_buffer.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index d0ff5b41161a..52d0556a5a25 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -265,14 +265,13 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info) int hv_ringbuffer_write(struct vmbus_channel *channel, const struct kvec *kv_list, u32 kv_count) { - int i = 0; + int i; u32 bytes_avail_towrite; - u32 totalbytes_towrite = 0; - + u32 totalbytes_towrite = sizeof(u64); u32 next_write_location; u32 old_write; - u64 prev_indices = 0; - unsigned long flags = 0; + u64 prev_indices; + unsigned long flags; struct hv_ring_buffer_info *outring_info = &channel->outbound; if (channel->rescind) @@ -281,8 +280,6 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, for (i = 0; i < kv_count; i++) totalbytes_towrite += kv_list[i].iov_len; - totalbytes_towrite += sizeof(u64); - spin_lock_irqsave(&outring_info->ring_lock, flags); bytes_avail_towrite = hv_get_bytes_to_write(outring_info); @@ -339,7 +336,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, u64 *requestid, bool raw) { u32 bytes_avail_toread; - u32 next_read_location = 0; + u32 next_read_location; u64 prev_indices = 0; struct vmpacket_descriptor desc; u32 offset; -- cgit v1.2.3-70-g09d2 From bdc1dd47dbcd39f266f0a6c8727a2b1995b36ef2 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:14 -0700 Subject: vmbus: fix spelling errors Several spelling errors in comments Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 10 +++++----- drivers/hv/hv_kvp.c | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index bd0d1988feb2..5c1aa1c09064 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -333,7 +333,7 @@ static int create_gpadl_header(void *kbuffer, u32 size, * Gpadl is u32 and we are using a pointer which could * be 64-bit * This is governed by the guest/host protocol and - * so the hypervisor gurantees that this is ok. + * so the hypervisor guarantees that this is ok. */ for (i = 0; i < pfncurr; i++) gpadl_body->pfn[i] = slow_virt_to_phys( @@ -380,7 +380,7 @@ nomem: } /* - * vmbus_establish_gpadl - Estabish a GPADL for the specified buffer + * vmbus_establish_gpadl - Establish a GPADL for the specified buffer * * @channel: a channel * @kbuffer: from kmalloc or vmalloc @@ -732,7 +732,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, /* Setup the descriptor */ desc.type = VM_PKT_DATA_USING_GPA_DIRECT; desc.flags = flags; - desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */ + desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */ desc.length8 = (u16)(packetlen_aligned >> 3); desc.transactionid = requestid; desc.rangecount = pagecount; @@ -793,7 +793,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, /* Setup the descriptor */ desc->type = VM_PKT_DATA_USING_GPA_DIRECT; desc->flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; - desc->dataoffset8 = desc_size >> 3; /* in 8-bytes grandularity */ + desc->dataoffset8 = desc_size >> 3; /* in 8-bytes granularity */ desc->length8 = (u16)(packetlen_aligned >> 3); desc->transactionid = requestid; desc->rangecount = 1; @@ -843,7 +843,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, /* Setup the descriptor */ desc.type = VM_PKT_DATA_USING_GPA_DIRECT; desc.flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; - desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */ + desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */ desc.length8 = (u16)(packetlen_aligned >> 3); desc.transactionid = requestid; desc.rangecount = 1; diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index a65b7f88d7aa..b0a36e8a7268 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -69,7 +69,7 @@ static const int fw_versions[] = { * * While the request/response protocol is guaranteed by the host, we further * ensure this by serializing packet processing in this driver - we do not - * read additional packets from the VMBUs until the current packet is fully + * read additional packets from the VMBUS until the current packet is fully * handled. */ @@ -398,7 +398,7 @@ kvp_send_key(struct work_struct *dummy) * the max lengths specified. We will however, reserve room * for the string terminating character - in the utf16s_utf8s() * function we limit the size of the buffer where the converted - * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to gaurantee + * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to guarantee * that the strings can be properly terminated! */ @@ -532,7 +532,7 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error) */ if (error) { /* - * Something failed or we have timedout; + * Something failed or we have timed out; * terminate the current host-side iteration. */ goto response_done; @@ -606,8 +606,8 @@ response_done: * This callback is invoked when we get a KVP message from the host. * The host ensures that only one KVP transaction can be active at a time. * KVP implementation in Linux needs to forward the key to a user-mde - * component to retrive the corresponding value. Consequently, we cannot - * respond to the host in the conext of this callback. Since the host + * component to retrieve the corresponding value. Consequently, we cannot + * respond to the host in the context of this callback. Since the host * guarantees that at most only one transaction can be active at a time, * we stash away the transaction state in a set of global variables. */ -- cgit v1.2.3-70-g09d2 From 42dd2715428f24d4ac990ed9207ce28c60a4b7f6 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:15 -0700 Subject: hyperv: remove unnecessary return variable hv_ringbuffer_read cleanup. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/ring_buffer.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 52d0556a5a25..8a249740b4b4 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -341,13 +341,11 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, struct vmpacket_descriptor desc; u32 offset; u32 packetlen; - int ret = 0; struct hv_ring_buffer_info *inring_info = &channel->inbound; if (buflen <= 0) return -EINVAL; - *buffer_actual_len = 0; *requestid = 0; @@ -358,7 +356,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, * No error is set when there is even no header, drivers are * supposed to analyze buffer_actual_len. */ - return ret; + return 0; } init_cached_read_index(channel); @@ -403,5 +401,5 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, hv_signal_on_read(channel); - return ret; + return 0; } -- cgit v1.2.3-70-g09d2 From e6242fa0fb50570acb6364446dec369d7b9b5b2c Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:16 -0700 Subject: vmbus: make channel_message table constant This table is immutable and should be const. Cleanup indentation and whitespace for this as well. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 48 +++++++++++++++++++++++------------------------ drivers/hv/hyperv_vmbus.h | 2 +- drivers/hv/vmbus_drv.c | 2 +- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index f33465d78a02..c1ba440a4987 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -1097,30 +1097,30 @@ static void vmbus_onversion_response( } /* Channel message dispatch table */ -struct vmbus_channel_message_table_entry - channel_message_table[CHANNELMSG_COUNT] = { - {CHANNELMSG_INVALID, 0, NULL}, - {CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer}, - {CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind}, - {CHANNELMSG_REQUESTOFFERS, 0, NULL}, - {CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered}, - {CHANNELMSG_OPENCHANNEL, 0, NULL}, - {CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result}, - {CHANNELMSG_CLOSECHANNEL, 0, NULL}, - {CHANNELMSG_GPADL_HEADER, 0, NULL}, - {CHANNELMSG_GPADL_BODY, 0, NULL}, - {CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created}, - {CHANNELMSG_GPADL_TEARDOWN, 0, NULL}, - {CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown}, - {CHANNELMSG_RELID_RELEASED, 0, NULL}, - {CHANNELMSG_INITIATE_CONTACT, 0, NULL}, - {CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response}, - {CHANNELMSG_UNLOAD, 0, NULL}, - {CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response}, - {CHANNELMSG_18, 0, NULL}, - {CHANNELMSG_19, 0, NULL}, - {CHANNELMSG_20, 0, NULL}, - {CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL}, +const struct vmbus_channel_message_table_entry +channel_message_table[CHANNELMSG_COUNT] = { + { CHANNELMSG_INVALID, 0, NULL }, + { CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer }, + { CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind }, + { CHANNELMSG_REQUESTOFFERS, 0, NULL }, + { CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered }, + { CHANNELMSG_OPENCHANNEL, 0, NULL }, + { CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result }, + { CHANNELMSG_CLOSECHANNEL, 0, NULL }, + { CHANNELMSG_GPADL_HEADER, 0, NULL }, + { CHANNELMSG_GPADL_BODY, 0, NULL }, + { CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created }, + { CHANNELMSG_GPADL_TEARDOWN, 0, NULL }, + { CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown }, + { CHANNELMSG_RELID_RELEASED, 0, NULL }, + { CHANNELMSG_INITIATE_CONTACT, 0, NULL }, + { CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response }, + { CHANNELMSG_UNLOAD, 0, NULL }, + { CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response }, + { CHANNELMSG_18, 0, NULL }, + { CHANNELMSG_19, 0, NULL }, + { CHANNELMSG_20, 0, NULL }, + { CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL }, }; /* diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 884f83bba1ab..b552c3a4dd3c 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -376,7 +376,7 @@ struct vmbus_channel_message_table_entry { void (*message_handler)(struct vmbus_channel_message_header *msg); }; -extern struct vmbus_channel_message_table_entry +extern const struct vmbus_channel_message_table_entry channel_message_table[CHANNELMSG_COUNT]; diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 5ca5004861c6..8b272d389ce5 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -853,7 +853,7 @@ void vmbus_on_msg_dpc(unsigned long data) struct hv_message *msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; struct vmbus_channel_message_header *hdr; - struct vmbus_channel_message_table_entry *entry; + const struct vmbus_channel_message_table_entry *entry; struct onmessage_work_context *ctx; u32 message_type = msg->header.message_type; -- cgit v1.2.3-70-g09d2 From 2a9d7de2038e87bb2a1085ac73c4246c260263f0 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:17 -0700 Subject: vmbus: cleanup header file style Minor changes to align hyper-v vmbus include files with current linux kernel style. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hyperv_vmbus.h | 16 ++++++++-------- include/linux/hyperv.h | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index b552c3a4dd3c..a69b52de8d56 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -218,8 +218,8 @@ struct hv_per_cpu_context { struct hv_context { /* We only support running on top of Hyper-V - * So at this point this really can only contain the Hyper-V ID - */ + * So at this point this really can only contain the Hyper-V ID + */ u64 guestid; void *tsc_page; @@ -403,17 +403,17 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep); void vmbus_on_event(unsigned long data); void vmbus_on_msg_dpc(unsigned long data); -int hv_kvp_init(struct hv_util_service *); +int hv_kvp_init(struct hv_util_service *srv); void hv_kvp_deinit(void); -void hv_kvp_onchannelcallback(void *); +void hv_kvp_onchannelcallback(void *context); -int hv_vss_init(struct hv_util_service *); +int hv_vss_init(struct hv_util_service *srv); void hv_vss_deinit(void); -void hv_vss_onchannelcallback(void *); +void hv_vss_onchannelcallback(void *context); -int hv_fcopy_init(struct hv_util_service *); +int hv_fcopy_init(struct hv_util_service *srv); void hv_fcopy_deinit(void); -void hv_fcopy_onchannelcallback(void *); +void hv_fcopy_onchannelcallback(void *context); void vmbus_initiate_unload(bool crash); static inline void hv_poll_channel(struct vmbus_channel *channel, diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2b1ed66824be..de9b80ff6698 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -524,10 +524,10 @@ struct vmbus_channel_open_channel { u32 target_vp; /* - * The upstream ring buffer begins at offset zero in the memory - * described by RingBufferGpadlHandle. The downstream ring buffer - * follows it at this offset (in pages). - */ + * The upstream ring buffer begins at offset zero in the memory + * described by RingBufferGpadlHandle. The downstream ring buffer + * follows it at this offset (in pages). + */ u32 downstream_ringbuffer_pageoffset; /* User-specific data to be passed along to the server endpoint. */ @@ -1006,7 +1006,7 @@ extern int vmbus_open(struct vmbus_channel *channel, u32 recv_ringbuffersize, void *userdata, u32 userdatalen, - void(*onchannel_callback)(void *context), + void (*onchannel_callback)(void *context), void *context); extern void vmbus_close(struct vmbus_channel *channel); @@ -1421,7 +1421,7 @@ struct hyperv_service_callback { char *log_msg; uuid_le data; struct vmbus_channel *channel; - void (*callback) (void *context); + void (*callback)(void *context); }; #define MAX_SRV_VER 0x7ffffff -- cgit v1.2.3-70-g09d2 From 4827ee1dca5691c9fc568883170a568db94f9b38 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 4 Mar 2017 18:27:18 -0700 Subject: vmbus: expose debug info for drivers Allow driver to get debug information about state of the ring. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hyperv_vmbus.h | 11 ----------- drivers/hv/ring_buffer.c | 1 + include/linux/hyperv.h | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index a69b52de8d56..6113e915c50e 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -248,14 +248,6 @@ struct hv_context { extern struct hv_context hv_context; -struct hv_ring_buffer_debug_info { - u32 current_interrupt_mask; - u32 current_read_index; - u32 current_write_index; - u32 bytes_avail_toread; - u32 bytes_avail_towrite; -}; - /* Hv Interface */ extern int hv_init(void); @@ -289,9 +281,6 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, u64 *requestid, bool raw); -void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info, - struct hv_ring_buffer_debug_info *debug_info); - /* * Maximum channels is determined by the size of the interrupt page * which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 8a249740b4b4..cfacca566e3f 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -206,6 +206,7 @@ void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info, ring_info->ring_buffer->interrupt_mask; } } +EXPORT_SYMBOL_GPL(hv_ringbuffer_get_debuginfo); /* Initialize the ring buffer. */ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info, diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index de9b80ff6698..1fa727fe5f93 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -491,6 +491,12 @@ struct vmbus_channel_rescind_offer { u32 child_relid; } __packed; +static inline u32 +hv_ringbuffer_pending_size(const struct hv_ring_buffer_info *rbi) +{ + return rbi->ring_buffer->pending_send_sz; +} + /* * Request Offer -- no parameters, SynIC message contains the partition ID * Set Snoop -- no parameters, SynIC message contains the partition ID @@ -1148,6 +1154,17 @@ static inline void *hv_get_drvdata(struct hv_device *dev) return dev_get_drvdata(&dev->device); } +struct hv_ring_buffer_debug_info { + u32 current_interrupt_mask; + u32 current_read_index; + u32 current_write_index; + u32 bytes_avail_toread; + u32 bytes_avail_towrite; +}; + +void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info, + struct hv_ring_buffer_debug_info *debug_info); + /* Vmbus interface */ #define vmbus_driver_register(driver) \ __vmbus_driver_register(driver, THIS_MODULE, KBUILD_MODNAME) -- cgit v1.2.3-70-g09d2 From 4539673a6ac027e3ba95901379b5f36c5eb3c9dc Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Tue, 14 Mar 2017 18:01:38 -0700 Subject: Drivers: hv: Fix a typo Fix a typo in the macro. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/uapi/asm/hyperv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/uapi/asm/hyperv.h b/arch/x86/include/uapi/asm/hyperv.h index 3a20ccf787b8..32ea7a3ad5b4 100644 --- a/arch/x86/include/uapi/asm/hyperv.h +++ b/arch/x86/include/uapi/asm/hyperv.h @@ -124,7 +124,7 @@ * Recommend using hypercall for address space switches rather * than MOV to CR3 instruction */ -#define HV_X64_MWAIT_RECOMMENDED (1 << 0) +#define HV_X64_AS_SWITCH_RECOMMENDED (1 << 0) /* Recommend using hypercall for local TLB flushes rather * than INVLPG or MOV to CR3 instructions */ #define HV_X64_LOCAL_TLB_FLUSH_RECOMMENDED (1 << 1) -- cgit v1.2.3-70-g09d2 From 6c248aad81c8903981db9f12fd74189fa01b3789 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Tue, 14 Mar 2017 18:01:39 -0700 Subject: Drivers: hv: Base autoeoi enablement based on hypervisor hints Don't enable auto-eoi if the hypervisor recommends otherwise. This will enable vAPIC functionality if available. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/uapi/asm/hyperv.h | 5 +++++ drivers/hv/hv.c | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/uapi/asm/hyperv.h b/arch/x86/include/uapi/asm/hyperv.h index 32ea7a3ad5b4..432df4b1baec 100644 --- a/arch/x86/include/uapi/asm/hyperv.h +++ b/arch/x86/include/uapi/asm/hyperv.h @@ -147,6 +147,11 @@ */ #define HV_X64_RELAXED_TIMING_RECOMMENDED (1 << 5) +/* + * Virtual APIC support + */ +#define HV_X64_DEPRECATING_AEOI_RECOMMENDED (1 << 9) + /* * Crash notification flag. */ diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 665a64f1611e..12e7baecb84e 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -254,7 +254,10 @@ int hv_synic_init(unsigned int cpu) shared_sint.as_uint64 = 0; shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR; shared_sint.masked = false; - shared_sint.auto_eoi = true; + if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED) + shared_sint.auto_eoi = false; + else + shared_sint.auto_eoi = true; hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64); -- cgit v1.2.3-70-g09d2 From dde04eb116fa1b0b0d443532af8c167520402b70 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Wed, 15 Feb 2017 09:35:15 -0600 Subject: binder: Add 'hwbinder' to the default devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As AOSP master is now starting to require a hwbinder device, add it to the the default Kconfig. Having the hwbinder device when not needed shouldn't hurt anything either. Cc: Greg Kroah-Hartman Cc: John Stultz Cc: Martijn Coenen Cc: Arve Hjønnevåg Cc: Amit Pundir Cc: Serban Constantinescu Cc: Dmitry Shmidt Cc: Rom Lemarchand Cc: Android Kernel Team Signed-off-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- drivers/android/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index a82fc022d34b..832e885349b1 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -22,7 +22,7 @@ config ANDROID_BINDER_IPC config ANDROID_BINDER_DEVICES string "Android Binder devices" depends on ANDROID_BINDER_IPC - default "binder" + default "binder,hwbinder" ---help--- Default value for the binder.devices parameter. -- cgit v1.2.3-70-g09d2 From 70359c4a6802caa9e5a9233b863c6175e011abeb Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 15 Feb 2017 11:50:55 -0800 Subject: rapidio: use is_visible() to hide switch-specific attributes Instead of creating switch-specific attributes by hand, implement is_visible() method of attribute group and hide them when dealing with non-switch devices. This will ensure that all attributes are created together, before userspace gets notified of new device. Also, remove rio-sysfs.c from list of files that are scanned when compiling RapiodIO documentations as it no longer has any structured comments, and leaving it in leads to warning when building docs. Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- Documentation/DocBook/rapidio.tmpl | 3 -- drivers/rapidio/rio-sysfs.c | 76 +++++++++++++++++--------------------- drivers/rapidio/rio.c | 3 -- drivers/rapidio/rio.h | 2 - 4 files changed, 33 insertions(+), 51 deletions(-) diff --git a/Documentation/DocBook/rapidio.tmpl b/Documentation/DocBook/rapidio.tmpl index 50479360d845..ac3cca3399a1 100644 --- a/Documentation/DocBook/rapidio.tmpl +++ b/Documentation/DocBook/rapidio.tmpl @@ -128,9 +128,6 @@ Device model support !Idrivers/rapidio/rio-driver.c - - Sysfs support -!Idrivers/rapidio/rio-sysfs.c PPC32 support !Iarch/powerpc/sysdev/fsl_rio.c diff --git a/drivers/rapidio/rio-sysfs.c b/drivers/rapidio/rio-sysfs.c index eda41563d06d..73e4b407f162 100644 --- a/drivers/rapidio/rio-sysfs.c +++ b/drivers/rapidio/rio-sysfs.c @@ -108,15 +108,11 @@ static struct attribute *rio_dev_attrs[] = { &dev_attr_lprev.attr, &dev_attr_destid.attr, &dev_attr_modalias.attr, - NULL, -}; -static const struct attribute_group rio_dev_group = { - .attrs = rio_dev_attrs, -}; - -const struct attribute_group *rio_dev_groups[] = { - &rio_dev_group, + /* Switch-only attributes */ + &dev_attr_routes.attr, + &dev_attr_lnext.attr, + &dev_attr_hopcount.attr, NULL, }; @@ -259,46 +255,40 @@ static struct bin_attribute rio_config_attr = { .write = rio_write_config, }; -/** - * rio_create_sysfs_dev_files - create RIO specific sysfs files - * @rdev: device whose entries should be created - * - * Create files when @rdev is added to sysfs. - */ -int rio_create_sysfs_dev_files(struct rio_dev *rdev) -{ - int err = 0; - - err = device_create_bin_file(&rdev->dev, &rio_config_attr); +static struct bin_attribute *rio_dev_bin_attrs[] = { + &rio_config_attr, + NULL, +}; - if (!err && (rdev->pef & RIO_PEF_SWITCH)) { - err |= device_create_file(&rdev->dev, &dev_attr_routes); - err |= device_create_file(&rdev->dev, &dev_attr_lnext); - err |= device_create_file(&rdev->dev, &dev_attr_hopcount); +static umode_t rio_dev_is_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct rio_dev *rdev = to_rio_dev(kobj_to_dev(kobj)); + umode_t mode = attr->mode; + + if (!(rdev->pef & RIO_PEF_SWITCH) && + (attr == &dev_attr_routes.attr || + attr == &dev_attr_lnext.attr || + attr == &dev_attr_hopcount.attr)) { + /* + * Hide switch-specific attributes for a non-switch device. + */ + mode = 0; } - if (err) - pr_warning("RIO: Failed to create attribute file(s) for %s\n", - rio_name(rdev)); - - return err; + return mode; } -/** - * rio_remove_sysfs_dev_files - cleanup RIO specific sysfs files - * @rdev: device whose entries we should free - * - * Cleanup when @rdev is removed from sysfs. - */ -void rio_remove_sysfs_dev_files(struct rio_dev *rdev) -{ - device_remove_bin_file(&rdev->dev, &rio_config_attr); - if (rdev->pef & RIO_PEF_SWITCH) { - device_remove_file(&rdev->dev, &dev_attr_routes); - device_remove_file(&rdev->dev, &dev_attr_lnext); - device_remove_file(&rdev->dev, &dev_attr_hopcount); - } -} +static const struct attribute_group rio_dev_group = { + .attrs = rio_dev_attrs, + .is_visible = rio_dev_is_attr_visible, + .bin_attrs = rio_dev_bin_attrs, +}; + +const struct attribute_group *rio_dev_groups[] = { + &rio_dev_group, + NULL, +}; static ssize_t bus_scan_store(struct bus_type *bus, const char *buf, size_t count) diff --git a/drivers/rapidio/rio.c b/drivers/rapidio/rio.c index 37042858c2db..38d949405618 100644 --- a/drivers/rapidio/rio.c +++ b/drivers/rapidio/rio.c @@ -192,8 +192,6 @@ int rio_add_device(struct rio_dev *rdev) } spin_unlock(&rio_global_list_lock); - rio_create_sysfs_dev_files(rdev); - return 0; } EXPORT_SYMBOL_GPL(rio_add_device); @@ -220,7 +218,6 @@ void rio_del_device(struct rio_dev *rdev, enum rio_device_state state) } } spin_unlock(&rio_global_list_lock); - rio_remove_sysfs_dev_files(rdev); device_unregister(&rdev->dev); } EXPORT_SYMBOL_GPL(rio_del_device); diff --git a/drivers/rapidio/rio.h b/drivers/rapidio/rio.h index 9796b3fee70d..b2abf8576397 100644 --- a/drivers/rapidio/rio.h +++ b/drivers/rapidio/rio.h @@ -27,8 +27,6 @@ extern u32 rio_mport_get_efb(struct rio_mport *port, int local, u16 destid, u8 hopcount, u32 from); extern int rio_mport_chk_dev_access(struct rio_mport *mport, u16 destid, u8 hopcount); -extern int rio_create_sysfs_dev_files(struct rio_dev *rdev); -extern void rio_remove_sysfs_dev_files(struct rio_dev *rdev); extern int rio_lock_device(struct rio_mport *port, u16 destid, u8 hopcount, int wait_ms); extern int rio_unlock_device(struct rio_mport *port, u16 destid, u8 hopcount); -- cgit v1.2.3-70-g09d2 From a553910fbaafbd7c18823c0f1e5dcc2d12c9fd2a Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Thu, 23 Feb 2017 09:29:26 +0800 Subject: MAINTAINERS: update firmware loader entry This email of 'ming.lei@canonical.com' isn't valid any more, please remove the entry. Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index c776906f67a9..2b44648d1541 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5110,7 +5110,6 @@ F: include/uapi/linux/firewire*.h F: tools/firewire/ FIRMWARE LOADER (request_firmware) -M: Ming Lei M: Luis R. Rodriguez L: linux-kernel@vger.kernel.org S: Maintained -- cgit v1.2.3-70-g09d2 From 87e715deb8e24bf1a6a9a1babaf465c28c93889c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 14 Feb 2017 12:24:09 -0800 Subject: zorro: stop creating attributes by hand Instead of creating attributes one by one, define attribute_group array and attach it to bus->dev_groups, so that all needed attributes are created automatically when a new device is registered on the bus. Also switch to using standard DEVICE_ATTR_RO() macros. Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/zorro/zorro-driver.c | 15 +++++---- drivers/zorro/zorro-sysfs.c | 76 +++++++++++++++++++++++--------------------- drivers/zorro/zorro.c | 3 -- drivers/zorro/zorro.h | 3 +- 4 files changed, 50 insertions(+), 47 deletions(-) diff --git a/drivers/zorro/zorro-driver.c b/drivers/zorro/zorro-driver.c index eacae1434b73..fa23b7366b98 100644 --- a/drivers/zorro/zorro-driver.c +++ b/drivers/zorro/zorro-driver.c @@ -14,6 +14,8 @@ #include #include +#include "zorro.h" + /** * zorro_match_device - Tell if a Zorro device structure has a matching @@ -161,12 +163,13 @@ static int zorro_uevent(struct device *dev, struct kobj_uevent_env *env) } struct bus_type zorro_bus_type = { - .name = "zorro", - .dev_name = "zorro", - .match = zorro_bus_match, - .uevent = zorro_uevent, - .probe = zorro_device_probe, - .remove = zorro_device_remove, + .name = "zorro", + .dev_name = "zorro", + .dev_groups = zorro_device_attribute_groups, + .match = zorro_bus_match, + .uevent = zorro_uevent, + .probe = zorro_device_probe, + .remove = zorro_device_remove, }; EXPORT_SYMBOL(zorro_bus_type); diff --git a/drivers/zorro/zorro-sysfs.c b/drivers/zorro/zorro-sysfs.c index 9282dbf5abdb..3d34dba9bb2d 100644 --- a/drivers/zorro/zorro-sysfs.c +++ b/drivers/zorro/zorro-sysfs.c @@ -23,33 +23,33 @@ /* show configuration fields */ #define zorro_config_attr(name, field, format_string) \ -static ssize_t \ -show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ { \ struct zorro_dev *z; \ \ z = to_zorro_dev(dev); \ return sprintf(buf, format_string, z->field); \ } \ -static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); +static DEVICE_ATTR_RO(name); zorro_config_attr(id, id, "0x%08x\n"); zorro_config_attr(type, rom.er_Type, "0x%02x\n"); zorro_config_attr(slotaddr, slotaddr, "0x%04x\n"); zorro_config_attr(slotsize, slotsize, "0x%04x\n"); -static ssize_t -show_serial(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t serial_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct zorro_dev *z; z = to_zorro_dev(dev); return sprintf(buf, "0x%08x\n", be32_to_cpu(z->rom.er_SerialNumber)); } +static DEVICE_ATTR_RO(serial); -static DEVICE_ATTR(serial, S_IRUGO, show_serial, NULL); - -static ssize_t zorro_show_resource(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t resource_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct zorro_dev *z = to_zorro_dev(dev); @@ -58,8 +58,27 @@ static ssize_t zorro_show_resource(struct device *dev, struct device_attribute * (unsigned long)zorro_resource_end(z), zorro_resource_flags(z)); } +static DEVICE_ATTR_RO(resource); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct zorro_dev *z = to_zorro_dev(dev); -static DEVICE_ATTR(resource, S_IRUGO, zorro_show_resource, NULL); + return sprintf(buf, ZORRO_DEVICE_MODALIAS_FMT "\n", z->id); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *zorro_device_attrs[] = { + &dev_attr_id.attr, + &dev_attr_type.attr, + &dev_attr_serial.attr, + &dev_attr_slotaddr.attr, + &dev_attr_slotsize.attr, + &dev_attr_resource.attr, + &dev_attr_modalias.attr, + NULL +}; static ssize_t zorro_read_config(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, @@ -88,32 +107,17 @@ static struct bin_attribute zorro_config_attr = { .read = zorro_read_config, }; -static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct zorro_dev *z = to_zorro_dev(dev); - - return sprintf(buf, ZORRO_DEVICE_MODALIAS_FMT "\n", z->id); -} - -static DEVICE_ATTR(modalias, S_IRUGO, modalias_show, NULL); +static struct bin_attribute *zorro_device_bin_attrs[] = { + &zorro_config_attr, + NULL +}; -int zorro_create_sysfs_dev_files(struct zorro_dev *z) -{ - struct device *dev = &z->dev; - int error; - - /* current configuration's attributes */ - if ((error = device_create_file(dev, &dev_attr_id)) || - (error = device_create_file(dev, &dev_attr_type)) || - (error = device_create_file(dev, &dev_attr_serial)) || - (error = device_create_file(dev, &dev_attr_slotaddr)) || - (error = device_create_file(dev, &dev_attr_slotsize)) || - (error = device_create_file(dev, &dev_attr_resource)) || - (error = device_create_file(dev, &dev_attr_modalias)) || - (error = sysfs_create_bin_file(&dev->kobj, &zorro_config_attr))) - return error; - - return 0; -} +static const struct attribute_group zorro_device_attr_group = { + .attrs = zorro_device_attrs, + .bin_attrs = zorro_device_bin_attrs, +}; +const struct attribute_group *zorro_device_attribute_groups[] = { + &zorro_device_attr_group, + NULL +}; diff --git a/drivers/zorro/zorro.c b/drivers/zorro/zorro.c index d295d9878dff..cc1b1ac57d61 100644 --- a/drivers/zorro/zorro.c +++ b/drivers/zorro/zorro.c @@ -197,9 +197,6 @@ static int __init amiga_zorro_probe(struct platform_device *pdev) put_device(&z->dev); continue; } - error = zorro_create_sysfs_dev_files(z); - if (error) - dev_err(&z->dev, "Error creating sysfs files\n"); } /* Mark all available Zorro II memory */ diff --git a/drivers/zorro/zorro.h b/drivers/zorro/zorro.h index 34119fb4e560..4f805c01cfbc 100644 --- a/drivers/zorro/zorro.h +++ b/drivers/zorro/zorro.h @@ -5,5 +5,4 @@ extern void zorro_name_device(struct zorro_dev *z); static inline void zorro_name_device(struct zorro_dev *dev) { } #endif -extern int zorro_create_sysfs_dev_files(struct zorro_dev *z); - +extern const struct attribute_group *zorro_device_attribute_groups[]; -- cgit v1.2.3-70-g09d2 From 6c4e976785011dfbe461821d0bfc58cfd60eac56 Mon Sep 17 00:00:00 2001 From: Cyril Bur Date: Fri, 17 Feb 2017 14:28:49 +1100 Subject: drivers/misc: Add Aspeed LPC control driver In order to manage server systems, there is typically another processor known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash. The Aspeed BMC family which is what is used on OpenPOWER machines and a number of x86 as well is typically connected to the host via an LPC (Low Pin Count) bus (among others). The LPC bus is an ISA bus on steroids. It's generally used by the BMC chip to provide the host with access to the system flash (via MEM/FW cycles) that contains the BIOS or other host firmware along with a number of SuperIO-style IOs (via IO space) such as UARTs, IPMI controllers. On the BMC chip side, this is all configured via a bunch of registers whose content is related to a given policy of what devices are exposed at a per system level, which is system/vendor specific, so we don't want to bolt that into the BMC kernel. This started with a need to provide something nicer than /dev/mem for user space to configure these things. One important aspect of the configuration is how the MEM/FW space is exposed to the host (ie, the x86 or POWER). Some registers in that bridge can define a window remapping all or portion of the LPC MEM/FW space to a portion of the BMC internal bus, with no specific limits imposed in HW. I think it makes sense to ensure that this window is configured by a kernel driver that can apply some serious sanity checks on what it is configured to map. In practice, user space wants to control this by flipping the mapping between essentially two types of portions of the BMC address space: - The flash space. This is a region of the BMC MMIO space that more/less directly maps the system flash (at least for reads, writes are somewhat more complicated). - One (or more) reserved area(s) of the BMC physical memory. The latter is needed for a number of things, such as avoiding letting the host manipulate the innards of the BMC flash controller via some evil backdoor, we want to do flash updates by routing the window to a portion of memory (under control of a mailbox protocol via some separate set of registers) which the host can use to write new data in bulk and then request the BMC to flash it. There are other uses, such as allowing the host to boot from an in-memory flash image rather than the one in flash (very handy for continuous integration and test, the BMC can just download new images). It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated. Signed-off-by: Cyril Bur Reviewed-by: Joel Stanley Reviewed-by: Benjamin Herrenschmidt Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 8 ++ drivers/misc/Makefile | 1 + drivers/misc/aspeed-lpc-ctrl.c | 267 +++++++++++++++++++++++++++++++++++ include/uapi/linux/aspeed-lpc-ctrl.h | 60 ++++++++ 4 files changed, 336 insertions(+) create mode 100644 drivers/misc/aspeed-lpc-ctrl.c create mode 100644 include/uapi/linux/aspeed-lpc-ctrl.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c290990d73ed..77b001e7cf85 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -771,6 +771,14 @@ config PANEL_BOOT_MESSAGE endif # PANEL +config ASPEED_LPC_CTRL + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON + tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control" + ---help--- + Control Aspeed ast2400/2500 HOST LPC to BMC mappings through + ioctl()s, the driver also provides a read/write interface to a BMC ram + region where the host LPC read/write region can be buffered. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7a3ea89339b4..4925ea8e1952 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o +obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c new file mode 100644 index 000000000000..f6acbe1d9378 --- /dev/null +++ b/drivers/misc/aspeed-lpc-ctrl.c @@ -0,0 +1,267 @@ +/* + * Copyright 2017 IBM Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DEVICE_NAME "aspeed-lpc-ctrl" + +#define HICR7 0x8 +#define HICR8 0xc + +struct aspeed_lpc_ctrl { + struct miscdevice miscdev; + struct regmap *regmap; + phys_addr_t mem_base; + resource_size_t mem_size; + u32 pnor_size; + u32 pnor_base; +}; + +static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file) +{ + return container_of(file->private_data, struct aspeed_lpc_ctrl, + miscdev); +} + +static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file); + unsigned long vsize = vma->vm_end - vma->vm_start; + pgprot_t prot = vma->vm_page_prot; + + if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size) + return -EINVAL; + + /* ast2400/2500 AHB accesses are not cache coherent */ + prot = pgprot_dmacoherent(prot); + + if (remap_pfn_range(vma, vma->vm_start, + (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff, + vsize, prot)) + return -EAGAIN; + + return 0; +} + +static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file); + void __user *p = (void __user *)param; + struct aspeed_lpc_ctrl_mapping map; + u32 addr; + u32 size; + long rc; + + if (copy_from_user(&map, p, sizeof(map))) + return -EFAULT; + + if (map.flags != 0) + return -EINVAL; + + switch (cmd) { + case ASPEED_LPC_CTRL_IOCTL_GET_SIZE: + /* The flash windows don't report their size */ + if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY) + return -EINVAL; + + /* Support more than one window id in the future */ + if (map.window_id != 0) + return -EINVAL; + + map.size = lpc_ctrl->mem_size; + + return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0; + case ASPEED_LPC_CTRL_IOCTL_MAP: + + /* + * The top half of HICR7 is the MSB of the BMC address of the + * mapping. + * The bottom half of HICR7 is the MSB of the HOST LPC + * firmware space address of the mapping. + * + * The 1 bits in the top of half of HICR8 represent the bits + * (in the requested address) that should be ignored and + * replaced with those from the top half of HICR7. + * The 1 bits in the bottom half of HICR8 represent the bits + * (in the requested address) that should be kept and pass + * into the BMC address space. + */ + + /* + * It doesn't make sense to talk about a size or offset with + * low 16 bits set. Both HICR7 and HICR8 talk about the top 16 + * bits of addresses and sizes. + */ + + if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff)) + return -EINVAL; + + /* + * Because of the way the masks work in HICR8 offset has to + * be a multiple of size. + */ + if (map.offset & (map.size - 1)) + return -EINVAL; + + if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) { + addr = lpc_ctrl->pnor_base; + size = lpc_ctrl->pnor_size; + } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) { + addr = lpc_ctrl->mem_base; + size = lpc_ctrl->mem_size; + } else { + return -EINVAL; + } + + /* Check overflow first! */ + if (map.offset + map.size < map.offset || + map.offset + map.size > size) + return -EINVAL; + + if (map.size == 0 || map.size > size) + return -EINVAL; + + addr += map.offset; + + /* + * addr (host lpc address) is safe regardless of values. This + * simply changes the address the host has to request on its + * side of the LPC bus. This cannot impact the hosts own + * memory space by surprise as LPC specific accessors are + * required. The only strange thing that could be done is + * setting the lower 16 bits but the shift takes care of that. + */ + + rc = regmap_write(lpc_ctrl->regmap, HICR7, + (addr | (map.addr >> 16))); + if (rc) + return rc; + + return regmap_write(lpc_ctrl->regmap, HICR8, + (~(map.size - 1)) | ((map.size >> 16) - 1)); + } + + return -EINVAL; +} + +static const struct file_operations aspeed_lpc_ctrl_fops = { + .owner = THIS_MODULE, + .mmap = aspeed_lpc_ctrl_mmap, + .unlocked_ioctl = aspeed_lpc_ctrl_ioctl, +}; + +static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) +{ + struct aspeed_lpc_ctrl *lpc_ctrl; + struct device_node *node; + struct resource resm; + struct device *dev; + int rc; + + dev = &pdev->dev; + + lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL); + if (!lpc_ctrl) + return -ENOMEM; + + node = of_parse_phandle(dev->of_node, "flash", 0); + if (!node) { + dev_err(dev, "Didn't find host pnor flash node\n"); + return -ENODEV; + } + + rc = of_address_to_resource(node, 1, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for flash\n"); + return rc; + } + + lpc_ctrl->pnor_size = resource_size(&resm); + lpc_ctrl->pnor_base = resm.start; + + dev_set_drvdata(&pdev->dev, lpc_ctrl); + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "Didn't find reserved memory\n"); + return -EINVAL; + } + + rc = of_address_to_resource(node, 0, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for reserved memory\n"); + return -ENOMEM; + } + + lpc_ctrl->mem_size = resource_size(&resm); + lpc_ctrl->mem_base = resm.start; + + lpc_ctrl->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(lpc_ctrl->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_ctrl->miscdev.name = DEVICE_NAME; + lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops; + lpc_ctrl->miscdev.parent = dev; + rc = misc_register(&lpc_ctrl->miscdev); + if (rc) + dev_err(dev, "Unable to register device\n"); + else + dev_info(dev, "Loaded at 0x%08x (0x%08x)\n", + lpc_ctrl->mem_base, lpc_ctrl->mem_size); + + return rc; +} + +static int aspeed_lpc_ctrl_remove(struct platform_device *pdev) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev); + + misc_deregister(&lpc_ctrl->miscdev); + + return 0; +} + +static const struct of_device_id aspeed_lpc_ctrl_match[] = { + { .compatible = "aspeed,ast2400-lpc-ctrl" }, + { .compatible = "aspeed,ast2500-lpc-ctrl" }, + { }, +}; + +static struct platform_driver aspeed_lpc_ctrl_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_lpc_ctrl_match, + }, + .probe = aspeed_lpc_ctrl_probe, + .remove = aspeed_lpc_ctrl_remove, +}; + +module_platform_driver(aspeed_lpc_ctrl_driver); + +MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cyril Bur "); +MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings"); diff --git a/include/uapi/linux/aspeed-lpc-ctrl.h b/include/uapi/linux/aspeed-lpc-ctrl.h new file mode 100644 index 000000000000..f96fa995a3f0 --- /dev/null +++ b/include/uapi/linux/aspeed-lpc-ctrl.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _UAPI_LINUX_ASPEED_LPC_CTRL_H +#define _UAPI_LINUX_ASPEED_LPC_CTRL_H + +#include + +/* Window types */ +#define ASPEED_LPC_CTRL_WINDOW_FLASH 1 +#define ASPEED_LPC_CTRL_WINDOW_MEMORY 2 + +/* + * This driver provides a window for the host to access a BMC resource + * across the BMC <-> Host LPC bus. + * + * window_type: The BMC resource that the host will access through the + * window. BMC flash and BMC RAM. + * + * window_id: For each window type there may be multiple windows, + * these are referenced by ID. + * + * flags: Reserved for future use, this field is expected to be + * zeroed. + * + * addr: Address on the host LPC bus that the specified window should + * be mapped. This address must be power of two aligned. + * + * offset: Offset into the BMC window that should be mapped to the + * host (at addr). This must be a multiple of size. + * + * size: The size of the mapping. The smallest possible size is 64K. + * This must be power of two aligned. + * + */ + +struct aspeed_lpc_ctrl_mapping { + __u8 window_type; + __u8 window_id; + __u16 flags; + __u32 addr; + __u32 offset; + __u32 size; +}; + +#define __ASPEED_LPC_CTRL_IOCTL_MAGIC 0xb2 + +#define ASPEED_LPC_CTRL_IOCTL_GET_SIZE _IOWR(__ASPEED_LPC_CTRL_IOCTL_MAGIC, \ + 0x00, struct aspeed_lpc_ctrl_mapping) + +#define ASPEED_LPC_CTRL_IOCTL_MAP _IOW(__ASPEED_LPC_CTRL_IOCTL_MAGIC, \ + 0x01, struct aspeed_lpc_ctrl_mapping) + +#endif /* _UAPI_LINUX_ASPEED_LPC_CTRL_H */ -- cgit v1.2.3-70-g09d2 From 5cd5e6ad0ede862432e1e766bfe02a9ad447533e Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Mon, 13 Mar 2017 11:57:25 -0700 Subject: hpet: Make cmd parameter of hpet_ioctl_common() unsigned The value passed by the two callers of the function is unsigned anyway. Making the parameter unsigned fixes the following warning when building with clang: drivers/char/hpet.c:588:7: error: overflow converting case value to switch condition type (2149083139 to 18446744071563667459) [-Werror,-Wswitch] case HPET_INFO: ^ include/uapi/linux/hpet.h:18:19: note: expanded from macro 'HPET_INFO' ^ include/uapi/asm-generic/ioctl.h:77:28: note: expanded from macro '_IOR' ^ include/uapi/asm-generic/ioctl.h:66:2: note: expanded from macro '_IOC' (((dir) << _IOC_DIRSHIFT) | \ Signed-off-by: Matthias Kaehlcke Acked-by: Clemens Ladisch Signed-off-by: Greg Kroah-Hartman --- drivers/char/hpet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c index 8bdc38d81adf..b941e6d59fd6 100644 --- a/drivers/char/hpet.c +++ b/drivers/char/hpet.c @@ -575,7 +575,7 @@ static inline unsigned long hpet_time_div(struct hpets *hpets, } static int -hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg, +hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg, struct hpet_info *info) { struct hpet_timer __iomem *timer; -- cgit v1.2.3-70-g09d2 From b5bc980a4929bb2a449fef3e0b7131466815d0b1 Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Sat, 4 Mar 2017 00:34:29 +0000 Subject: docs: Add kernel-doc comments to VME driver API Add kernel-doc comments to the VME driver API and structures. This documentation will be integrated into the RST documentation in a later patch. Signed-off-by: Martyn Welch Signed-off-by: Greg Kroah-Hartman --- drivers/vme/vme.c | 469 ++++++++++++++++++++++++++++++++++++++++++++++++---- include/linux/vme.h | 12 +- 2 files changed, 449 insertions(+), 32 deletions(-) diff --git a/drivers/vme/vme.c b/drivers/vme/vme.c index 0035cf79760a..6a3ead42aba8 100644 --- a/drivers/vme/vme.c +++ b/drivers/vme/vme.c @@ -76,9 +76,16 @@ static struct vme_bridge *find_bridge(struct vme_resource *resource) } } -/* +/** + * vme_free_consistent - Allocate contiguous memory. + * @resource: Pointer to VME resource. + * @size: Size of allocation required. + * @dma: Pointer to variable to store physical address of allocation. + * * Allocate a contiguous block of memory for use by the driver. This is used to * create the buffers for the slave windows. + * + * Return: Virtual address of allocation on success, NULL on failure. */ void *vme_alloc_consistent(struct vme_resource *resource, size_t size, dma_addr_t *dma) @@ -111,8 +118,14 @@ void *vme_alloc_consistent(struct vme_resource *resource, size_t size, } EXPORT_SYMBOL(vme_alloc_consistent); -/* - * Free previously allocated contiguous block of memory. +/** + * vme_free_consistent - Free previously allocated memory. + * @resource: Pointer to VME resource. + * @size: Size of allocation to free. + * @vaddr: Virtual address of allocation. + * @dma: Physical address of allocation. + * + * Free previously allocated block of contiguous memory. */ void vme_free_consistent(struct vme_resource *resource, size_t size, void *vaddr, dma_addr_t dma) @@ -145,6 +158,16 @@ void vme_free_consistent(struct vme_resource *resource, size_t size, } EXPORT_SYMBOL(vme_free_consistent); +/** + * vme_get_size - Helper function returning size of a VME window + * @resource: Pointer to VME slave or master resource. + * + * Determine the size of the VME window provided. This is a helper + * function, wrappering the call to vme_master_get or vme_slave_get + * depending on the type of window resource handed to it. + * + * Return: Size of the window on success, zero on failure. + */ size_t vme_get_size(struct vme_resource *resource) { int enabled, retval; @@ -259,9 +282,16 @@ static u32 vme_get_aspace(int am) return 0; } -/* - * Request a slave image with specific attributes, return some unique - * identifier. +/** + * vme_slave_request - Request a VME slave window resource. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @address: Required VME address space. + * @cycle: Required VME data transfer cycle type. + * + * Request use of a VME window resource capable of being set for the requested + * address space and data transfer cycle. + * + * Return: Pointer to VME resource on success, NULL on failure. */ struct vme_resource *vme_slave_request(struct vme_dev *vdev, u32 address, u32 cycle) @@ -327,6 +357,23 @@ err_bus: } EXPORT_SYMBOL(vme_slave_request); +/** + * vme_slave_set - Set VME slave window configuration. + * @resource: Pointer to VME slave resource. + * @enabled: State to which the window should be configured. + * @vme_base: Base address for the window. + * @size: Size of the VME window. + * @buf_base: Based address of buffer used to provide VME slave window storage. + * @aspace: VME address space for the VME window. + * @cycle: VME data transfer cycle type for the VME window. + * + * Set configuration for provided VME slave window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device, if an invalid resource has been provided or invalid + * attributes are provided. Hardware specific errors may also be + * returned. + */ int vme_slave_set(struct vme_resource *resource, int enabled, unsigned long long vme_base, unsigned long long size, dma_addr_t buf_base, u32 aspace, u32 cycle) @@ -362,6 +409,21 @@ int vme_slave_set(struct vme_resource *resource, int enabled, } EXPORT_SYMBOL(vme_slave_set); +/** + * vme_slave_get - Retrieve VME slave window configuration. + * @resource: Pointer to VME slave resource. + * @enabled: Pointer to variable for storing state. + * @vme_base: Pointer to variable for storing window base address. + * @size: Pointer to variable for storing window size. + * @buf_base: Pointer to variable for storing slave buffer base address. + * @aspace: Pointer to variable for storing VME address space. + * @cycle: Pointer to variable for storing VME data transfer cycle type. + * + * Return configuration for provided VME slave window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device or if an invalid resource has been provided. + */ int vme_slave_get(struct vme_resource *resource, int *enabled, unsigned long long *vme_base, unsigned long long *size, dma_addr_t *buf_base, u32 *aspace, u32 *cycle) @@ -386,6 +448,12 @@ int vme_slave_get(struct vme_resource *resource, int *enabled, } EXPORT_SYMBOL(vme_slave_get); +/** + * vme_slave_free - Free VME slave window + * @resource: Pointer to VME slave resource. + * + * Free the provided slave resource so that it may be reallocated. + */ void vme_slave_free(struct vme_resource *resource) { struct vme_slave_resource *slave_image; @@ -415,9 +483,17 @@ void vme_slave_free(struct vme_resource *resource) } EXPORT_SYMBOL(vme_slave_free); -/* - * Request a master image with specific attributes, return some unique - * identifier. +/** + * vme_master_request - Request a VME master window resource. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @address: Required VME address space. + * @cycle: Required VME data transfer cycle type. + * @dwidth: Required VME data transfer width. + * + * Request use of a VME window resource capable of being set for the requested + * address space, data transfer cycle and width. + * + * Return: Pointer to VME resource on success, NULL on failure. */ struct vme_resource *vme_master_request(struct vme_dev *vdev, u32 address, u32 cycle, u32 dwidth) @@ -486,6 +562,23 @@ err_bus: } EXPORT_SYMBOL(vme_master_request); +/** + * vme_master_set - Set VME master window configuration. + * @resource: Pointer to VME master resource. + * @enabled: State to which the window should be configured. + * @vme_base: Base address for the window. + * @size: Size of the VME window. + * @aspace: VME address space for the VME window. + * @cycle: VME data transfer cycle type for the VME window. + * @dwidth: VME data transfer width for the VME window. + * + * Set configuration for provided VME master window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device, if an invalid resource has been provided or invalid + * attributes are provided. Hardware specific errors may also be + * returned. + */ int vme_master_set(struct vme_resource *resource, int enabled, unsigned long long vme_base, unsigned long long size, u32 aspace, u32 cycle, u32 dwidth) @@ -522,6 +615,21 @@ int vme_master_set(struct vme_resource *resource, int enabled, } EXPORT_SYMBOL(vme_master_set); +/** + * vme_master_get - Retrieve VME master window configuration. + * @resource: Pointer to VME master resource. + * @enabled: Pointer to variable for storing state. + * @vme_base: Pointer to variable for storing window base address. + * @size: Pointer to variable for storing window size. + * @aspace: Pointer to variable for storing VME address space. + * @cycle: Pointer to variable for storing VME data transfer cycle type. + * @dwidth: Pointer to variable for storing VME data transfer width. + * + * Return configuration for provided VME master window. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device or if an invalid resource has been provided. + */ int vme_master_get(struct vme_resource *resource, int *enabled, unsigned long long *vme_base, unsigned long long *size, u32 *aspace, u32 *cycle, u32 *dwidth) @@ -546,8 +654,20 @@ int vme_master_get(struct vme_resource *resource, int *enabled, } EXPORT_SYMBOL(vme_master_get); -/* - * Read data out of VME space into a buffer. +/** + * vme_master_write - Read data from VME space into a buffer. + * @resource: Pointer to VME master resource. + * @buf: Pointer to buffer where data should be transferred. + * @count: Number of bytes to transfer. + * @offset: Offset into VME master window at which to start transfer. + * + * Perform read of count bytes of data from location on VME bus which maps into + * the VME master window at offset to buf. + * + * Return: Number of bytes read, -EINVAL if resource is not a VME master + * resource or read operation is not supported. -EFAULT returned if + * invalid offset is provided. Hardware specific errors may also be + * returned. */ ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count, loff_t offset) @@ -583,8 +703,20 @@ ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count, } EXPORT_SYMBOL(vme_master_read); -/* - * Write data out to VME space from a buffer. +/** + * vme_master_write - Write data out to VME space from a buffer. + * @resource: Pointer to VME master resource. + * @buf: Pointer to buffer holding data to transfer. + * @count: Number of bytes to transfer. + * @offset: Offset into VME master window at which to start transfer. + * + * Perform write of count bytes of data from buf to location on VME bus which + * maps into the VME master window at offset. + * + * Return: Number of bytes written, -EINVAL if resource is not a VME master + * resource or write operation is not supported. -EFAULT returned if + * invalid offset is provided. Hardware specific errors may also be + * returned. */ ssize_t vme_master_write(struct vme_resource *resource, void *buf, size_t count, loff_t offset) @@ -619,8 +751,24 @@ ssize_t vme_master_write(struct vme_resource *resource, void *buf, } EXPORT_SYMBOL(vme_master_write); -/* - * Perform RMW cycle to provided location. +/** + * vme_master_rmw - Perform read-modify-write cycle. + * @resource: Pointer to VME master resource. + * @mask: Bits to be compared and swapped in operation. + * @compare: Bits to be compared with data read from offset. + * @swap: Bits to be swapped in data read from offset. + * @offset: Offset into VME master window at which to perform operation. + * + * Perform read-modify-write cycle on provided location: + * - Location on VME bus is read. + * - Bits selected by mask are compared with compare. + * - Where a selected bit matches that in compare and are selected in swap, + * the bit is swapped. + * - Result written back to location on VME bus. + * + * Return: Bytes written on success, -EINVAL if resource is not a VME master + * resource or RMW operation is not supported. Hardware specific + * errors may also be returned. */ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask, unsigned int compare, unsigned int swap, loff_t offset) @@ -644,6 +792,17 @@ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask, } EXPORT_SYMBOL(vme_master_rmw); +/** + * vme_master_mmap - Mmap region of VME master window. + * @resource: Pointer to VME master resource. + * @vma: Pointer to definition of user mapping. + * + * Memory map a region of the VME master window into user space. + * + * Return: Zero on success, -EINVAL if resource is not a VME master + * resource or -EFAULT if map exceeds window size. Other generic mmap + * errors may also be returned. + */ int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma) { struct vme_master_resource *image; @@ -670,6 +829,12 @@ int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma) } EXPORT_SYMBOL(vme_master_mmap); +/** + * vme_master_free - Free VME master window + * @resource: Pointer to VME master resource. + * + * Free the provided master resource so that it may be reallocated. + */ void vme_master_free(struct vme_resource *resource) { struct vme_master_resource *master_image; @@ -699,9 +864,15 @@ void vme_master_free(struct vme_resource *resource) } EXPORT_SYMBOL(vme_master_free); -/* - * Request a DMA controller with specific attributes, return some unique - * identifier. +/** + * vme_dma_request - Request a DMA controller. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @route: Required src/destination combination. + * + * Request a VME DMA controller with capability to perform transfers bewteen + * requested source/destination combination. + * + * Return: Pointer to VME DMA resource on success, NULL on failure. */ struct vme_resource *vme_dma_request(struct vme_dev *vdev, u32 route) { @@ -768,8 +939,15 @@ err_bus: } EXPORT_SYMBOL(vme_dma_request); -/* - * Start new list +/** + * vme_new_dma_list - Create new VME DMA list. + * @resource: Pointer to VME DMA resource. + * + * Create a new VME DMA list. It is the responsibility of the user to free + * the list once it is no longer required with vme_dma_list_free(). + * + * Return: Pointer to new VME DMA list, NULL on allocation failure or invalid + * VME DMA resource. */ struct vme_dma_list *vme_new_dma_list(struct vme_resource *resource) { @@ -796,8 +974,16 @@ struct vme_dma_list *vme_new_dma_list(struct vme_resource *resource) } EXPORT_SYMBOL(vme_new_dma_list); -/* - * Create "Pattern" type attributes +/** + * vme_dma_pattern_attribute - Create "Pattern" type VME DMA list attribute. + * @pattern: Value to use used as pattern + * @type: Type of pattern to be written. + * + * Create VME DMA list attribute for pattern generation. It is the + * responsibility of the user to free used attributes using + * vme_dma_free_attribute(). + * + * Return: Pointer to VME DMA attribute, NULL on failure. */ struct vme_dma_attr *vme_dma_pattern_attribute(u32 pattern, u32 type) { @@ -831,8 +1017,15 @@ err_attr: } EXPORT_SYMBOL(vme_dma_pattern_attribute); -/* - * Create "PCI" type attributes +/** + * vme_dma_pci_attribute - Create "PCI" type VME DMA list attribute. + * @address: PCI base address for DMA transfer. + * + * Create VME DMA list attribute pointing to a location on PCI for DMA + * transfers. It is the responsibility of the user to free used attributes + * using vme_dma_free_attribute(). + * + * Return: Pointer to VME DMA attribute, NULL on failure. */ struct vme_dma_attr *vme_dma_pci_attribute(dma_addr_t address) { @@ -869,8 +1062,18 @@ err_attr: } EXPORT_SYMBOL(vme_dma_pci_attribute); -/* - * Create "VME" type attributes +/** + * vme_dma_vme_attribute - Create "VME" type VME DMA list attribute. + * @address: VME base address for DMA transfer. + * @aspace: VME address space to use for DMA transfer. + * @cycle: VME bus cycle to use for DMA transfer. + * @dwidth: VME data width to use for DMA transfer. + * + * Create VME DMA list attribute pointing to a location on the VME bus for DMA + * transfers. It is the responsibility of the user to free used attributes + * using vme_dma_free_attribute(). + * + * Return: Pointer to VME DMA attribute, NULL on failure. */ struct vme_dma_attr *vme_dma_vme_attribute(unsigned long long address, u32 aspace, u32 cycle, u32 dwidth) @@ -908,8 +1111,12 @@ err_attr: } EXPORT_SYMBOL(vme_dma_vme_attribute); -/* - * Free attribute +/** + * vme_dma_free_attribute - Free DMA list attribute. + * @attributes: Pointer to DMA list attribute. + * + * Free VME DMA list attribute. VME DMA list attributes can be safely freed + * once vme_dma_list_add() has returned. */ void vme_dma_free_attribute(struct vme_dma_attr *attributes) { @@ -918,6 +1125,23 @@ void vme_dma_free_attribute(struct vme_dma_attr *attributes) } EXPORT_SYMBOL(vme_dma_free_attribute); +/** + * vme_dma_list_add - Add enty to a VME DMA list. + * @list: Pointer to VME list. + * @src: Pointer to DMA list attribute to use as source. + * @dest: Pointer to DMA list attribute to use as destination. + * @count: Number of bytes to transfer. + * + * Add an entry to the provided VME DMA list. Entry requires pointers to source + * and destination DMA attributes and a count. + * + * Please note, the attributes supported as source and destinations for + * transfers are hardware dependent. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device or if the link list has already been submitted for execution. + * Hardware specific errors also possible. + */ int vme_dma_list_add(struct vme_dma_list *list, struct vme_dma_attr *src, struct vme_dma_attr *dest, size_t count) { @@ -942,6 +1166,16 @@ int vme_dma_list_add(struct vme_dma_list *list, struct vme_dma_attr *src, } EXPORT_SYMBOL(vme_dma_list_add); +/** + * vme_dma_list_exec - Queue a VME DMA list for execution. + * @list: Pointer to VME list. + * + * Queue the provided VME DMA list for execution. The call will return once the + * list has been executed. + * + * Return: Zero on success, -EINVAL if operation is not supported on this + * device. Hardware specific errors also possible. + */ int vme_dma_list_exec(struct vme_dma_list *list) { struct vme_bridge *bridge = list->parent->parent; @@ -962,6 +1196,15 @@ int vme_dma_list_exec(struct vme_dma_list *list) } EXPORT_SYMBOL(vme_dma_list_exec); +/** + * vme_dma_list_free - Free a VME DMA list. + * @list: Pointer to VME list. + * + * Free the provided DMA list and all its entries. + * + * Return: Zero on success, -EINVAL on invalid VME resource, -EBUSY if resource + * is still in use. Hardware specific errors also possible. + */ int vme_dma_list_free(struct vme_dma_list *list) { struct vme_bridge *bridge = list->parent->parent; @@ -994,6 +1237,15 @@ int vme_dma_list_free(struct vme_dma_list *list) } EXPORT_SYMBOL(vme_dma_list_free); +/** + * vme_dma_free - Free a VME DMA resource. + * @resource: Pointer to VME DMA resource. + * + * Free the provided DMA resource so that it may be reallocated. + * + * Return: Zero on success, -EINVAL on invalid VME resource, -EBUSY if resource + * is still active. + */ int vme_dma_free(struct vme_resource *resource) { struct vme_dma_resource *ctrlr; @@ -1099,6 +1351,22 @@ void vme_irq_handler(struct vme_bridge *bridge, int level, int statid) } EXPORT_SYMBOL(vme_irq_handler); +/** + * vme_irq_request - Request a specific VME interrupt. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @level: Interrupt priority being requested. + * @statid: Interrupt vector being requested. + * @callback: Pointer to callback function called when VME interrupt/vector + * received. + * @priv_data: Generic pointer that will be passed to the callback function. + * + * Request callback to be attached as a handler for VME interrupts with provided + * level and statid. + * + * Return: Zero on success, -EINVAL on invalid vme device, level or if the + * function is not supported, -EBUSY if the level/statid combination is + * already in use. Hardware specific errors also possible. + */ int vme_irq_request(struct vme_dev *vdev, int level, int statid, void (*callback)(int, int, void *), void *priv_data) @@ -1142,6 +1410,14 @@ int vme_irq_request(struct vme_dev *vdev, int level, int statid, } EXPORT_SYMBOL(vme_irq_request); +/** + * vme_irq_free - Free a VME interrupt. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @level: Interrupt priority of interrupt being freed. + * @statid: Interrupt vector of interrupt being freed. + * + * Remove previously attached callback from VME interrupt priority/vector. + */ void vme_irq_free(struct vme_dev *vdev, int level, int statid) { struct vme_bridge *bridge; @@ -1177,6 +1453,18 @@ void vme_irq_free(struct vme_dev *vdev, int level, int statid) } EXPORT_SYMBOL(vme_irq_free); +/** + * vme_irq_generate - Generate VME interrupt. + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * @level: Interrupt priority at which to assert the interrupt. + * @statid: Interrupt vector to associate with the interrupt. + * + * Generate a VME interrupt of the provided level and with the provided + * statid. + * + * Return: Zero on success, -EINVAL on invalid vme device, level or if the + * function is not supported. Hardware specific errors also possible. + */ int vme_irq_generate(struct vme_dev *vdev, int level, int statid) { struct vme_bridge *bridge; @@ -1201,8 +1489,15 @@ int vme_irq_generate(struct vme_dev *vdev, int level, int statid) } EXPORT_SYMBOL(vme_irq_generate); -/* - * Request the location monitor, return resource or NULL +/** + * vme_lm_request - Request a VME location monitor + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * + * Allocate a location monitor resource to the driver. A location monitor + * allows the driver to monitor accesses to a contiguous number of + * addresses on the VME bus. + * + * Return: Pointer to a VME resource on success or NULL on failure. */ struct vme_resource *vme_lm_request(struct vme_dev *vdev) { @@ -1218,7 +1513,7 @@ struct vme_resource *vme_lm_request(struct vme_dev *vdev) goto err_bus; } - /* Loop through DMA resources */ + /* Loop through LM resources */ list_for_each(lm_pos, &bridge->lm_resources) { lm = list_entry(lm_pos, struct vme_lm_resource, list); @@ -1264,6 +1559,17 @@ err_bus: } EXPORT_SYMBOL(vme_lm_request); +/** + * vme_lm_count - Determine number of VME Addresses monitored + * @resource: Pointer to VME location monitor resource. + * + * The number of contiguous addresses monitored is hardware dependent. + * Return the number of contiguous addresses monitored by the + * location monitor. + * + * Return: Count of addresses monitored or -EINVAL when provided with an + * invalid location monitor resource. + */ int vme_lm_count(struct vme_resource *resource) { struct vme_lm_resource *lm; @@ -1279,6 +1585,20 @@ int vme_lm_count(struct vme_resource *resource) } EXPORT_SYMBOL(vme_lm_count); +/** + * vme_lm_set - Configure location monitor + * @resource: Pointer to VME location monitor resource. + * @lm_base: Base address to monitor. + * @aspace: VME address space to monitor. + * @cycle: VME bus cycle type to monitor. + * + * Set the base address, address space and cycle type of accesses to be + * monitored by the location monitor. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_set(struct vme_resource *resource, unsigned long long lm_base, u32 aspace, u32 cycle) { @@ -1301,6 +1621,20 @@ int vme_lm_set(struct vme_resource *resource, unsigned long long lm_base, } EXPORT_SYMBOL(vme_lm_set); +/** + * vme_lm_get - Retrieve location monitor settings + * @resource: Pointer to VME location monitor resource. + * @lm_base: Pointer used to output the base address monitored. + * @aspace: Pointer used to output the address space monitored. + * @cycle: Pointer used to output the VME bus cycle type monitored. + * + * Retrieve the base address, address space and cycle type of accesses to + * be monitored by the location monitor. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_get(struct vme_resource *resource, unsigned long long *lm_base, u32 *aspace, u32 *cycle) { @@ -1323,6 +1657,21 @@ int vme_lm_get(struct vme_resource *resource, unsigned long long *lm_base, } EXPORT_SYMBOL(vme_lm_get); +/** + * vme_lm_attach - Provide callback for location monitor address + * @resource: Pointer to VME location monitor resource. + * @monitor: Offset to which callback should be attached. + * @callback: Pointer to callback function called when triggered. + * @data: Generic pointer that will be passed to the callback function. + * + * Attach a callback to the specificed offset into the location monitors + * monitored addresses. A generic pointer is provided to allow data to be + * passed to the callback when called. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_attach(struct vme_resource *resource, int monitor, void (*callback)(void *), void *data) { @@ -1345,6 +1694,18 @@ int vme_lm_attach(struct vme_resource *resource, int monitor, } EXPORT_SYMBOL(vme_lm_attach); +/** + * vme_lm_detach - Remove callback for location monitor address + * @resource: Pointer to VME location monitor resource. + * @monitor: Offset to which callback should be removed. + * + * Remove the callback associated with the specificed offset into the + * location monitors monitored addresses. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource or function is not supported. Hardware specific + * errors may also be returned. + */ int vme_lm_detach(struct vme_resource *resource, int monitor) { struct vme_bridge *bridge = find_bridge(resource); @@ -1366,6 +1727,18 @@ int vme_lm_detach(struct vme_resource *resource, int monitor) } EXPORT_SYMBOL(vme_lm_detach); +/** + * vme_lm_free - Free allocated VME location monitor + * @resource: Pointer to VME location monitor resource. + * + * Free allocation of a VME location monitor. + * + * WARNING: This function currently expects that any callbacks that have + * been attached to the location monitor have been removed. + * + * Return: Zero on success, -EINVAL when provided with an invalid location + * monitor resource. + */ void vme_lm_free(struct vme_resource *resource) { struct vme_lm_resource *lm; @@ -1392,6 +1765,16 @@ void vme_lm_free(struct vme_resource *resource) } EXPORT_SYMBOL(vme_lm_free); +/** + * vme_slot_num - Retrieve slot ID + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * + * Retrieve the slot ID associated with the provided VME device. + * + * Return: The slot ID on success, -EINVAL if VME bridge cannot be determined + * or the function is not supported. Hardware specific errors may also + * be returned. + */ int vme_slot_num(struct vme_dev *vdev) { struct vme_bridge *bridge; @@ -1411,6 +1794,15 @@ int vme_slot_num(struct vme_dev *vdev) } EXPORT_SYMBOL(vme_slot_num); +/** + * vme_bus_num - Retrieve bus number + * @vdev: Pointer to VME device struct vme_dev assigned to driver instance. + * + * Retrieve the bus enumeration associated with the provided VME device. + * + * Return: The bus number on success, -EINVAL if VME bridge cannot be + * determined. + */ int vme_bus_num(struct vme_dev *vdev) { struct vme_bridge *bridge; @@ -1556,6 +1948,15 @@ static int __vme_register_driver(struct vme_driver *drv, unsigned int ndevs) return err; } +/** + * vme_register_driver - Register a VME driver + * @drv: Pointer to VME driver structure to register. + * @ndevs: Maximum number of devices to allow to be enumerated. + * + * Register a VME device driver with the VME subsystem. + * + * Return: Zero on success, error value on registration failure. + */ int vme_register_driver(struct vme_driver *drv, unsigned int ndevs) { int err; @@ -1576,6 +1977,12 @@ int vme_register_driver(struct vme_driver *drv, unsigned int ndevs) } EXPORT_SYMBOL(vme_register_driver); +/** + * vme_unregister_driver - Unregister a VME driver + * @drv: Pointer to VME driver structure to unregister. + * + * Unregister a VME device driver from the VME subsystem. + */ void vme_unregister_driver(struct vme_driver *drv) { struct vme_dev *dev, *dev_tmp; diff --git a/include/linux/vme.h b/include/linux/vme.h index ec5e8bf6118e..25874da3f2e1 100644 --- a/include/linux/vme.h +++ b/include/linux/vme.h @@ -92,7 +92,7 @@ extern struct bus_type vme_bus_type; #define VME_SLOT_ALL -2 /** - * Structure representing a VME device + * struct vme_dev - Structure representing a VME device * @num: The device number * @bridge: Pointer to the bridge device this device is on * @dev: Internal device structure @@ -107,6 +107,16 @@ struct vme_dev { struct list_head bridge_list; }; +/** + * struct vme_driver - Structure representing a VME driver + * @name: Driver name, should be unique among VME drivers and usually the same + * as the module name. + * @match: Callback used to determine whether probe should be run. + * @probe: Callback for device binding, called when new device is detected. + * @remove: Callback, called on device removal. + * @driver: Underlying generic device driver structure. + * @devices: List of VME devices (struct vme_dev) associated with this driver. + */ struct vme_driver { const char *name; int (*match)(struct vme_dev *); -- cgit v1.2.3-70-g09d2 From 48a5e6bdc2dd82281d7d6623a7395be7d6c3a95b Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Sat, 4 Mar 2017 00:34:30 +0000 Subject: docs: Update VME documentation to include kerneldoc comments Update VME documentation given that kerneldoc comments are now provided. Add "VME API" section to VME RST to pull in kerneldoc comments. Signed-off-by: Martyn Welch Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/vme.rst | 363 ++++++++++----------------------------- 1 file changed, 93 insertions(+), 270 deletions(-) diff --git a/Documentation/driver-api/vme.rst b/Documentation/driver-api/vme.rst index 89776fb3c8bd..def139c13410 100644 --- a/Documentation/driver-api/vme.rst +++ b/Documentation/driver-api/vme.rst @@ -6,36 +6,15 @@ Driver registration As with other subsystems within the Linux kernel, VME device drivers register with the VME subsystem, typically called from the devices init routine. This is -achieved via a call to the following function: +achieved via a call to :c:func:`vme_register_driver`. -.. code-block:: c - - int vme_register_driver (struct vme_driver *driver, unsigned int ndevs); +A pointer to a structure of type :c:type:`struct vme_driver ` must +be provided to the registration function. Along with the maximum number of +devices your driver is able to support. -If driver registration is successful this function returns zero, if an error -occurred a negative error code will be returned. - -A pointer to a structure of type 'vme_driver' must be provided to the -registration function. Along with ndevs, which is the number of devices your -driver is able to support. The structure is as follows: - -.. code-block:: c - - struct vme_driver { - struct list_head node; - const char *name; - int (*match)(struct vme_dev *); - int (*probe)(struct vme_dev *); - int (*remove)(struct vme_dev *); - void (*shutdown)(void); - struct device_driver driver; - struct list_head devices; - unsigned int ndev; - }; - -At the minimum, the '.name', '.match' and '.probe' elements of this structure -should be correctly set. The '.name' element is a pointer to a string holding -the device driver's name. +At the minimum, the '.name', '.match' and '.probe' elements of +:c:type:`struct vme_driver ` should be correctly set. The '.name' +element is a pointer to a string holding the device driver's name. The '.match' function allows control over which VME devices should be registered with the driver. The match function should return 1 if a device should be @@ -54,29 +33,16 @@ the number of devices probed to one: } The '.probe' element should contain a pointer to the probe routine. The -probe routine is passed a 'struct vme_dev' pointer as an argument. The -'struct vme_dev' structure looks like the following: - -.. code-block:: c - - struct vme_dev { - int num; - struct vme_bridge *bridge; - struct device dev; - struct list_head drv_list; - struct list_head bridge_list; - }; +probe routine is passed a :c:type:`struct vme_dev ` pointer as an +argument. Here, the 'num' field refers to the sequential device ID for this specific driver. The bridge number (or bus number) can be accessed using dev->bridge->num. -A function is also provided to unregister the driver from the VME core and is -usually called from the device driver's exit routine: - -.. code-block:: c - - void vme_unregister_driver (struct vme_driver *driver); +A function is also provided to unregister the driver from the VME core called +:c:func:`vme_unregister_driver` and should usually be called from the device +driver's exit routine. Resource management @@ -90,47 +56,29 @@ driver is called. The probe routine is passed a pointer to the devices device structure. This pointer should be saved, it will be required for requesting VME resources. -The driver can request ownership of one or more master windows, slave windows -and/or dma channels. Rather than allowing the device driver to request a -specific window or DMA channel (which may be used by a different driver) this -driver allows a resource to be assigned based on the required attributes of the -driver in question: - -.. code-block:: c - - struct vme_resource * vme_master_request(struct vme_dev *dev, - u32 aspace, u32 cycle, u32 width); - - struct vme_resource * vme_slave_request(struct vme_dev *dev, u32 aspace, - u32 cycle); - - struct vme_resource *vme_dma_request(struct vme_dev *dev, u32 route); - -For slave windows these attributes are split into the VME address spaces that -need to be accessed in 'aspace' and VME bus cycle types required in 'cycle'. -Master windows add a further set of attributes in 'width' specifying the -required data transfer widths. These attributes are defined as bitmasks and as -such any combination of the attributes can be requested for a single window, -the core will assign a window that meets the requirements, returning a pointer -of type vme_resource that should be used to identify the allocated resource -when it is used. For DMA controllers, the request function requires the -potential direction of any transfers to be provided in the route attributes. -This is typically VME-to-MEM and/or MEM-to-VME, though some hardware can -support VME-to-VME and MEM-to-MEM transfers as well as test pattern generation. -If an unallocated window fitting the requirements can not be found a NULL -pointer will be returned. +The driver can request ownership of one or more master windows +(:c:func:`vme_master_request`), slave windows (:c:func:`vme_slave_request`) +and/or dma channels (:c:func:`vme_dma_request`). Rather than allowing the device +driver to request a specific window or DMA channel (which may be used by a +different driver) the API allows a resource to be assigned based on the required +attributes of the driver in question. For slave windows these attributes are +split into the VME address spaces that need to be accessed in 'aspace' and VME +bus cycle types required in 'cycle'. Master windows add a further set of +attributes in 'width' specifying the required data transfer widths. These +attributes are defined as bitmasks and as such any combination of the +attributes can be requested for a single window, the core will assign a window +that meets the requirements, returning a pointer of type vme_resource that +should be used to identify the allocated resource when it is used. For DMA +controllers, the request function requires the potential direction of any +transfers to be provided in the route attributes. This is typically VME-to-MEM +and/or MEM-to-VME, though some hardware can support VME-to-VME and MEM-to-MEM +transfers as well as test pattern generation. If an unallocated window fitting +the requirements can not be found a NULL pointer will be returned. Functions are also provided to free window allocations once they are no longer -required. These functions should be passed the pointer to the resource provided -during resource allocation: - -.. code-block:: c - - void vme_master_free(struct vme_resource *res); - - void vme_slave_free(struct vme_resource *res); - - void vme_dma_free(struct vme_resource *res); +required. These functions (:c:func:`vme_master_free`, :c:func:`vme_slave_free` +and :c:func:`vme_dma_free`) should be passed the pointer to the resource +provided during resource allocation. Master windows @@ -144,61 +92,22 @@ the underlying chipset. A window must be configured before it can be used. Master window configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Once a master window has been assigned the following functions can be used to -configure it and retrieve the current settings: - -.. code-block:: c - - int vme_master_set (struct vme_resource *res, int enabled, - unsigned long long base, unsigned long long size, u32 aspace, - u32 cycle, u32 width); - - int vme_master_get (struct vme_resource *res, int *enabled, - unsigned long long *base, unsigned long long *size, u32 *aspace, - u32 *cycle, u32 *width); - -The address spaces, transfer widths and cycle types are the same as described +Once a master window has been assigned :c:func:`vme_master_set` can be used to +configure it and :c:func:`vme_master_get` to retrieve the current settings. The +address spaces, transfer widths and cycle types are the same as described under resource management, however some of the options are mutually exclusive. For example, only one address space may be specified. -These functions return 0 on success or an error code should the call fail. - Master window access ~~~~~~~~~~~~~~~~~~~~ -The following functions can be used to read from and write to configured master -windows. These functions return the number of bytes copied: - -.. code-block:: c - - ssize_t vme_master_read(struct vme_resource *res, void *buf, - size_t count, loff_t offset); - - ssize_t vme_master_write(struct vme_resource *res, void *buf, - size_t count, loff_t offset); - -In addition to simple reads and writes, a function is provided to do a -read-modify-write transaction. This function returns the original value of the -VME bus location : - -.. code-block:: c - - unsigned int vme_master_rmw (struct vme_resource *res, - unsigned int mask, unsigned int compare, unsigned int swap, - loff_t offset); - -This functions by reading the offset, applying the mask. If the bits selected in -the mask match with the values of the corresponding bits in the compare field, -the value of swap is written the specified offset. - -Parts of a VME window can be mapped into user space memory using the following -function: +The function :c:func:`vme_master_read` can be used to read from and +:c:func:`vme_master_write` used to write to configured master windows. -.. code-block:: c - - int vme_master_mmap(struct vme_resource *resource, - struct vm_area_struct *vma) +In addition to simple reads and writes, :c:func:`vme_master_rmw` is provided to +do a read-modify-write transaction. Parts of a VME window can also be mapped +into user space memory using :c:func:`vme_master_mmap`. Slave windows @@ -213,41 +122,23 @@ it can be used. Slave window configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Once a slave window has been assigned the following functions can be used to -configure it and retrieve the current settings: - -.. code-block:: c - - int vme_slave_set (struct vme_resource *res, int enabled, - unsigned long long base, unsigned long long size, - dma_addr_t mem, u32 aspace, u32 cycle); - - int vme_slave_get (struct vme_resource *res, int *enabled, - unsigned long long *base, unsigned long long *size, - dma_addr_t *mem, u32 *aspace, u32 *cycle); +Once a slave window has been assigned :c:func:`vme_slave_set` can be used to +configure it and :c:func:`vme_slave_get` to retrieve the current settings. The address spaces, transfer widths and cycle types are the same as described under resource management, however some of the options are mutually exclusive. For example, only one address space may be specified. -These functions return 0 on success or an error code should the call fail. - Slave window buffer allocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Functions are provided to allow the user to allocate and free a contiguous -buffers which will be accessible by the VME bridge. These functions do not have -to be used, other methods can be used to allocate a buffer, though care must be -taken to ensure that they are contiguous and accessible by the VME bridge: - -.. code-block:: c - - void * vme_alloc_consistent(struct vme_resource *res, size_t size, - dma_addr_t *mem); - - void vme_free_consistent(struct vme_resource *res, size_t size, - void *virt, dma_addr_t mem); +Functions are provided to allow the user to allocate +(:c:func:`vme_alloc_consistent`) and free (:c:func:`vme_free_consistent`) +contiguous buffers which will be accessible by the VME bridge. These functions +do not have to be used, other methods can be used to allocate a buffer, though +care must be taken to ensure that they are contiguous and accessible by the VME +bridge. Slave window access @@ -269,29 +160,18 @@ executed, reused and destroyed. List Management ~~~~~~~~~~~~~~~ -The following functions are provided to create and destroy DMA lists. Execution -of a list will not automatically destroy the list, thus enabling a list to be -reused for repetitive tasks: - -.. code-block:: c - - struct vme_dma_list *vme_new_dma_list(struct vme_resource *res); - - int vme_dma_list_free(struct vme_dma_list *list); +The function :c:func:`vme_new_dma_list` is provided to create and +:c:func:`vme_dma_list_free` to destroy DMA lists. Execution of a list will not +automatically destroy the list, thus enabling a list to be reused for repetitive +tasks. List Population ~~~~~~~~~~~~~~~ -An item can be added to a list using the following function ( the source and +An item can be added to a list using :c:func:`vme_dma_list_add` (the source and destination attributes need to be created before calling this function, this is -covered under "Transfer Attributes"): - -.. code-block:: c - - int vme_dma_list_add(struct vme_dma_list *list, - struct vme_dma_attr *src, struct vme_dma_attr *dest, - size_t count); +covered under "Transfer Attributes"). .. note:: @@ -310,41 +190,19 @@ an item to a list. This is due to the diverse attributes required for each type of source and destination. There are functions to create attributes for PCI, VME and pattern sources and destinations (where appropriate): -Pattern source: - -.. code-block:: c - - struct vme_dma_attr *vme_dma_pattern_attribute(u32 pattern, u32 type); - -PCI source or destination: - -.. code-block:: c - - struct vme_dma_attr *vme_dma_pci_attribute(dma_addr_t mem); - -VME source or destination: + - PCI source or destination: :c:func:`vme_dma_pci_attribute` + - VME source or destination: :c:func:`vme_dma_vme_attribute` + - Pattern source: :c:func:`vme_dma_pattern_attribute` -.. code-block:: c - - struct vme_dma_attr *vme_dma_vme_attribute(unsigned long long base, - u32 aspace, u32 cycle, u32 width); - -The following function should be used to free an attribute: - -.. code-block:: c - - void vme_dma_free_attribute(struct vme_dma_attr *attr); +The function :c:func:`vme_dma_free_attribute` should be used to free an +attribute. List Execution ~~~~~~~~~~~~~~ -The following function queues a list for execution. The function will return -once the list has been executed: - -.. code-block:: c - - int vme_dma_list_exec(struct vme_dma_list *list); +The function :c:func:`vme_dma_list_exec` queues a list for execution and will +return once the list has been executed. Interrupts @@ -358,20 +216,13 @@ specific VME level and status IDs. Attaching Interrupt Handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following functions can be used to attach and free a specific VME level and -status ID combination. Any given combination can only be assigned a single -callback function. A void pointer parameter is provided, the value of which is -passed to the callback function, the use of this pointer is user undefined: - -.. code-block:: c - - int vme_irq_request(struct vme_dev *dev, int level, int statid, - void (*callback)(int, int, void *), void *priv); - - void vme_irq_free(struct vme_dev *dev, int level, int statid); - -The callback parameters are as follows. Care must be taken in writing a callback -function, callback functions run in interrupt context: +The function :c:func:`vme_irq_request` can be used to attach and +:c:func:`vme_irq_free` to free a specific VME level and status ID combination. +Any given combination can only be assigned a single callback function. A void +pointer parameter is provided, the value of which is passed to the callback +function, the use of this pointer is user undefined. The callback parameters are +as follows. Care must be taken in writing a callback function, callback +functions run in interrupt context: .. code-block:: c @@ -381,12 +232,8 @@ function, callback functions run in interrupt context: Interrupt Generation ~~~~~~~~~~~~~~~~~~~~ -The following function can be used to generate a VME interrupt at a given VME -level and VME status ID: - -.. code-block:: c - - int vme_irq_generate(struct vme_dev *dev, int level, int statid); +The function :c:func:`vme_irq_generate` can be used to generate a VME interrupt +at a given VME level and VME status ID. Location monitors @@ -399,54 +246,29 @@ monitor. Location Monitor Management ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following functions are provided to request the use of a block of location -monitors and to free them after they are no longer required: - -.. code-block:: c - - struct vme_resource * vme_lm_request(struct vme_dev *dev); - - void vme_lm_free(struct vme_resource * res); - -Each block may provide a number of location monitors, monitoring adjacent -locations. The following function can be used to determine how many locations -are provided: - -.. code-block:: c - - int vme_lm_count(struct vme_resource * res); +The function :c:func:`vme_lm_request` is provided to request the use of a block +of location monitors and :c:func:`vme_lm_free` to free them after they are no +longer required. Each block may provide a number of location monitors, +monitoring adjacent locations. The function :c:func:`vme_lm_count` can be used +to determine how many locations are provided. Location Monitor Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Once a bank of location monitors has been allocated, the following functions -are provided to configure the location and mode of the location monitor: - -.. code-block:: c - - int vme_lm_set(struct vme_resource *res, unsigned long long base, - u32 aspace, u32 cycle); - - int vme_lm_get(struct vme_resource *res, unsigned long long *base, - u32 *aspace, u32 *cycle); +Once a bank of location monitors has been allocated, the function +:c:func:`vme_lm_set` is provided to configure the location and mode of the +location monitor. The function :c:func:`vme_lm_get` can be used to retrieve +existing settings. Location Monitor Use ~~~~~~~~~~~~~~~~~~~~ -The following functions allow a callback to be attached and detached from each -location monitor location. Each location monitor can monitor a number of -adjacent locations: - -.. code-block:: c - - int vme_lm_attach(struct vme_resource *res, int num, - void (*callback)(void *)); - - int vme_lm_detach(struct vme_resource *res, int num); - -The callback function is declared as follows. +The function :c:func:`vme_lm_attach` enables a callback to be attached and +:c:func:`vme_lm_detach` allows on to be detached from each location monitor +location. Each location monitor can monitor a number of adjacent locations. The +callback function is declared as follows. .. code-block:: c @@ -456,19 +278,20 @@ The callback function is declared as follows. Slot Detection -------------- -This function returns the slot ID of the provided bridge. - -.. code-block:: c - - int vme_slot_num(struct vme_dev *dev); +The function :c:func:`vme_slot_num` returns the slot ID of the provided bridge. Bus Detection ------------- -This function returns the bus ID of the provided bridge. +The function :c:func:`vme_bus_num` returns the bus ID of the provided bridge. -.. code-block:: c - int vme_bus_num(struct vme_dev *dev); +VME API +------- + +.. kernel-doc:: include/linux/vme.h + :internal: +.. kernel-doc:: drivers/vme/vme.c + :export: -- cgit v1.2.3-70-g09d2 From 2ac6665701afd1946e1c93bd1308e97fd2b0ce10 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Fri, 10 Mar 2017 15:19:44 -0800 Subject: pps: add ioctl_compat function to correct ioctl definitions ioctl definitions use the pointer size of the architecture which is fine when userspace and kernel are the same bitsize. This patchset workarounds an issue with mixed bitsize kernel + userspace by rewriting the cmd to the kernelspace architecture pointer size. Acked-by: Rodolfo Giometti Signed-off-by: Matt Ranostay Signed-off-by: Greg Kroah-Hartman --- drivers/pps/pps.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 2f07cd615665..452ead5a5e52 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -242,6 +242,18 @@ static long pps_cdev_ioctl(struct file *file, return 0; } +#ifdef CONFIG_COMPAT +static long pps_cdev_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *)); + + return pps_cdev_ioctl(file, cmd, arg); +} +#else +#define pps_cdev_compat_ioctl NULL +#endif + static int pps_cdev_open(struct inode *inode, struct file *file) { struct pps_device *pps = container_of(inode->i_cdev, @@ -268,6 +280,7 @@ static const struct file_operations pps_cdev_fops = { .llseek = no_llseek, .poll = pps_cdev_poll, .fasync = pps_cdev_fasync, + .compat_ioctl = pps_cdev_compat_ioctl, .unlocked_ioctl = pps_cdev_ioctl, .open = pps_cdev_open, .release = pps_cdev_release, -- cgit v1.2.3-70-g09d2 From c2a49fe8eeef301b40d0c8065d817c5425d31b11 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Fri, 10 Mar 2017 15:19:45 -0800 Subject: pps: fix padding issue with PPS_FETCH for ioctl_compat Issue is that x86 32-bit aligns to 4-bytes instead of 8-bytes so this patchset works around the issue and corrects the data returned in pps_fdata_compat. Acked-by: Rodolfo Giometti Signed-off-by: Matt Ranostay Signed-off-by: Greg Kroah-Hartman --- drivers/pps/pps.c | 110 ++++++++++++++++++++++++++++++++++------------- include/uapi/linux/pps.h | 19 ++++++++ 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 452ead5a5e52..6eb0db37dd88 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -64,6 +64,43 @@ static int pps_cdev_fasync(int fd, struct file *file, int on) return fasync_helper(fd, file, on, &pps->async_queue); } +static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata) +{ + unsigned int ev = pps->last_ev; + int err = 0; + + /* Manage the timeout */ + if (fdata->timeout.flags & PPS_TIME_INVALID) + err = wait_event_interruptible(pps->queue, + ev != pps->last_ev); + else { + unsigned long ticks; + + dev_dbg(pps->dev, "timeout %lld.%09d\n", + (long long) fdata->timeout.sec, + fdata->timeout.nsec); + ticks = fdata->timeout.sec * HZ; + ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ); + + if (ticks != 0) { + err = wait_event_interruptible_timeout( + pps->queue, + ev != pps->last_ev, + ticks); + if (err == 0) + return -ETIMEDOUT; + } + } + + /* Check for pending signals */ + if (err == -ERESTARTSYS) { + dev_dbg(pps->dev, "pending signal caught\n"); + return -EINTR; + } + + return 0; +} + static long pps_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -144,7 +181,6 @@ static long pps_cdev_ioctl(struct file *file, case PPS_FETCH: { struct pps_fdata fdata; - unsigned int ev; dev_dbg(pps->dev, "PPS_FETCH\n"); @@ -152,36 +188,9 @@ static long pps_cdev_ioctl(struct file *file, if (err) return -EFAULT; - ev = pps->last_ev; - - /* Manage the timeout */ - if (fdata.timeout.flags & PPS_TIME_INVALID) - err = wait_event_interruptible(pps->queue, - ev != pps->last_ev); - else { - unsigned long ticks; - - dev_dbg(pps->dev, "timeout %lld.%09d\n", - (long long) fdata.timeout.sec, - fdata.timeout.nsec); - ticks = fdata.timeout.sec * HZ; - ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ); - - if (ticks != 0) { - err = wait_event_interruptible_timeout( - pps->queue, - ev != pps->last_ev, - ticks); - if (err == 0) - return -ETIMEDOUT; - } - } - - /* Check for pending signals */ - if (err == -ERESTARTSYS) { - dev_dbg(pps->dev, "pending signal caught\n"); - return -EINTR; - } + err = pps_cdev_pps_fetch(pps, &fdata); + if (err) + return err; /* Return the fetched timestamp */ spin_lock_irq(&pps->lock); @@ -246,8 +255,47 @@ static long pps_cdev_ioctl(struct file *file, static long pps_cdev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct pps_device *pps = file->private_data; + void __user *uarg = (void __user *) arg; + cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *)); + if (cmd == PPS_FETCH) { + struct pps_fdata_compat compat; + struct pps_fdata fdata; + int err; + + dev_dbg(pps->dev, "PPS_FETCH\n"); + + err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat)); + if (err) + return -EFAULT; + + memcpy(&fdata.timeout, &compat.timeout, + sizeof(struct pps_ktime_compat)); + + err = pps_cdev_pps_fetch(pps, &fdata); + if (err) + return err; + + /* Return the fetched timestamp */ + spin_lock_irq(&pps->lock); + + compat.info.assert_sequence = pps->assert_sequence; + compat.info.clear_sequence = pps->clear_sequence; + compat.info.current_mode = pps->current_mode; + + memcpy(&compat.info.assert_tu, &pps->assert_tu, + sizeof(struct pps_ktime_compat)); + memcpy(&compat.info.clear_tu, &pps->clear_tu, + sizeof(struct pps_ktime_compat)); + + spin_unlock_irq(&pps->lock); + + return copy_to_user(uarg, &compat, + sizeof(struct pps_fdata_compat)) ? -EFAULT : 0; + } + return pps_cdev_ioctl(file, cmd, arg); } #else diff --git a/include/uapi/linux/pps.h b/include/uapi/linux/pps.h index a9bb1d93451a..c1cb3825a8bc 100644 --- a/include/uapi/linux/pps.h +++ b/include/uapi/linux/pps.h @@ -55,6 +55,12 @@ struct pps_ktime { __s32 nsec; __u32 flags; }; + +struct pps_ktime_compat { + __s64 sec; + __s32 nsec; + __u32 flags; +} __attribute__((packed, aligned(4))); #define PPS_TIME_INVALID (1<<0) /* used to specify timeout==NULL */ struct pps_kinfo { @@ -65,6 +71,14 @@ struct pps_kinfo { int current_mode; /* current mode bits */ }; +struct pps_kinfo_compat { + __u32 assert_sequence; /* seq. num. of assert event */ + __u32 clear_sequence; /* seq. num. of clear event */ + struct pps_ktime_compat assert_tu; /* time of assert event */ + struct pps_ktime_compat clear_tu; /* time of clear event */ + int current_mode; /* current mode bits */ +}; + struct pps_kparams { int api_version; /* API version # */ int mode; /* mode bits */ @@ -114,6 +128,11 @@ struct pps_fdata { struct pps_ktime timeout; }; +struct pps_fdata_compat { + struct pps_kinfo_compat info; + struct pps_ktime_compat timeout; +}; + struct pps_bind_args { int tsformat; /* format of time stamps */ int edge; /* selected event type */ -- cgit v1.2.3-70-g09d2 From a9ee205883a36e3d51dff5b8f3a7c2c1c4fbab57 Mon Sep 17 00:00:00 2001 From: Mariusz Bialonczyk Date: Thu, 23 Feb 2017 07:38:42 +0100 Subject: w1: add missing DS2413 documentation Signed-off-by: Mariusz Bialonczyk Signed-off-by: Greg Kroah-Hartman --- Documentation/w1/slaves/00-INDEX | 2 ++ Documentation/w1/slaves/w1_ds2413 | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 Documentation/w1/slaves/w1_ds2413 diff --git a/Documentation/w1/slaves/00-INDEX b/Documentation/w1/slaves/00-INDEX index 6e18c70c3474..cbcca1d3a680 100644 --- a/Documentation/w1/slaves/00-INDEX +++ b/Documentation/w1/slaves/00-INDEX @@ -2,6 +2,8 @@ - This file w1_therm - The Maxim/Dallas Semiconductor ds18*20 temperature sensor. +w1_ds2413 + - The Maxim/Dallas Semiconductor ds2413 dual channel addressable switch. w1_ds2423 - The Maxim/Dallas Semiconductor ds2423 counter device. w1_ds28e04 diff --git a/Documentation/w1/slaves/w1_ds2413 b/Documentation/w1/slaves/w1_ds2413 new file mode 100644 index 000000000000..936263a8ccb4 --- /dev/null +++ b/Documentation/w1/slaves/w1_ds2413 @@ -0,0 +1,50 @@ +Kernel driver w1_ds2413 +======================= + +Supported chips: + * Maxim DS2413 1-Wire Dual Channel Addressable Switch + +supported family codes: + W1_FAMILY_DS2413 0x3A + +Author: Mariusz Bialonczyk + +Description +----------- + +The DS2413 chip has two open-drain outputs (PIO A and PIO B). +Support is provided through the sysfs files "output" and "state". + +Reading state +------------- +The "state" file provides one-byte value which is in the same format as for +the chip PIO_ACCESS_READ command (refer the datasheet for details): + +Bit 0: PIOA Pin State +Bit 1: PIOA Output Latch State +Bit 2: PIOB Pin State +Bit 3: PIOB Output Latch State +Bit 4-7: Complement of Bit 3 to Bit 0 (verified by the kernel module) + +This file is readonly. + +Writing output +-------------- +You can set the PIO pins using the "output" file. +It is writable, you can write one-byte value to this sysfs file. +Similarly the byte format is the same as for the PIO_ACCESS_WRITE command: + +Bit 0: PIOA +Bit 1: PIOB +Bit 2-7: No matter (driver will set it to "1"s) + + +The chip has some kind of basic protection against transmission errors. +When reading the state, there is a four complement bits. +The driver is checking this complement, and when it is wrong then it is +returning I/O error. + +When writing output, the master must repeat the PIO Output Data byte in +its inverted form and it is waiting for a confirmation. +If the write is unsuccessful for three times, the write also returns +I/O error. -- cgit v1.2.3-70-g09d2 From e3af95e60738e2f0ecedca96f8f3baa2fa4d1b7d Mon Sep 17 00:00:00 2001 From: Mariusz Bialonczyk Date: Thu, 23 Feb 2017 07:38:43 +0100 Subject: w1: add support for DS2438 Smart Battery Monitor Detailed information about support and provided sysfs files in my next commit which creates a documentation file: Documentation/w1/slaves/w1_ds2438 Signed-off-by: Mariusz Bialonczyk Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/Kconfig | 6 + drivers/w1/slaves/Makefile | 1 + drivers/w1/slaves/w1_ds2438.c | 390 ++++++++++++++++++++++++++++++++++++++++++ drivers/w1/w1_family.h | 1 + 4 files changed, 398 insertions(+) create mode 100644 drivers/w1/slaves/w1_ds2438.c diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 0ef9f2663dbd..fb68465908f2 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -86,6 +86,12 @@ config W1_SLAVE_DS2433_CRC Each block has 30 bytes of data and a two byte CRC16. Full block writes are only allowed if the CRC is valid. +config W1_SLAVE_DS2438 + tristate "DS2438 Smart Battery Monitor 0x26 family support" + help + Say Y here if you want to use a 1-wire + DS2438 Smart Battery Monitor device support + config W1_SLAVE_DS2760 tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)" help diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index b4a358955ef9..54c63e420302 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o +obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c new file mode 100644 index 000000000000..5ededb4965e1 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2438.c @@ -0,0 +1,390 @@ +/* + * 1-Wire implementation for the ds2438 chip + * + * Copyright (c) 2017 Mariusz Bialonczyk + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include +#include +#include +#include +#include + +#include "../w1.h" +#include "../w1_family.h" + +#define W1_DS2438_RETRIES 3 + +/* Memory commands */ +#define W1_DS2438_READ_SCRATCH 0xBE +#define W1_DS2438_WRITE_SCRATCH 0x4E +#define W1_DS2438_COPY_SCRATCH 0x48 +#define W1_DS2438_RECALL_MEMORY 0xB8 +/* Register commands */ +#define W1_DS2438_CONVERT_TEMP 0x44 +#define W1_DS2438_CONVERT_VOLTAGE 0xB4 + +#define DS2438_PAGE_SIZE 8 +#define DS2438_ADC_INPUT_VAD 0 +#define DS2438_ADC_INPUT_VDD 1 +#define DS2438_MAX_CONVERSION_TIME 10 /* ms */ + +/* Page #0 definitions */ +#define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */ +#define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */ +#define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */ +#define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */ +#define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */ +#define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */ +#define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */ +#define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */ + +#define DS2438_TEMP_LSB 0x01 +#define DS2438_TEMP_MSB 0x02 +#define DS2438_VOLTAGE_LSB 0x03 +#define DS2438_VOLTAGE_MSB 0x04 +#define DS2438_CURRENT_LSB 0x05 +#define DS2438_CURRENT_MSB 0x06 +#define DS2438_THRESHOLD 0x07 + +int w1_ds2438_get_page(struct w1_slave *sl, int pageno, u8 *buf) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[2]; + u8 crc; + size_t count; + + while (retries--) { + crc = 0; + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + count = w1_read_block(sl->master, buf, DS2438_PAGE_SIZE + 1); + if (count == DS2438_PAGE_SIZE + 1) { + crc = w1_calc_crc8(buf, DS2438_PAGE_SIZE); + + /* check for correct CRC */ + if ((u8)buf[DS2438_PAGE_SIZE] == crc) + return 0; + } + } + return -1; +} + +int w1_ds2438_get_temperature(struct w1_slave *sl, int16_t *temperature) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_TEMP); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *temperature = (((int16_t) w1_buf[DS2438_TEMP_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_TEMP_LSB]); + ret = 0; + } else + ret = -1; + + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +int w1_ds2438_change_config_bit(struct w1_slave *sl, u8 mask, u8 value) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[3]; + u8 status; + int perform_write = 0; + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + /* reading one byte of result */ + status = w1_read_8(sl->master); + + /* if bit0=1, set a value to a mask for easy compare */ + if (value) + value = mask; + + if ((status & mask) == value) + return 0; /* already set as requested */ + else { + /* changing bit */ + status ^= mask; + perform_write = 1; + } + break; + } + + if (perform_write) { + retries = W1_DS2438_RETRIES; + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_WRITE_SCRATCH; + w1_buf[1] = 0x00; + w1_buf[2] = status; + w1_write_block(sl->master, w1_buf, 3); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_COPY_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + return 0; + } + } + return -1; +} + +uint16_t w1_ds2438_get_voltage(struct w1_slave *sl, int adc_input, uint16_t *voltage) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_AD, adc_input)) { + ret = -1; + goto pre_unlock; + } + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_VOLTAGE); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *voltage = (((uint16_t) w1_buf[DS2438_VOLTAGE_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_VOLTAGE_LSB]); + ret = 0; + } else + ret = -1; + +pre_unlock: + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +static ssize_t iad_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_IAD, *buf & 0x01) == 0) + ret = 1; + else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t page0_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + memcpy(buf, &w1_buf, DS2438_PAGE_SIZE); + ret = DS2438_PAGE_SIZE; + } else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t temperature_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + ssize_t c = PAGE_SIZE; + int16_t temp; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_temperature(sl, &temp) == 0) { + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", temp); + ret = PAGE_SIZE - c; + } else + ret = -EIO; + + return ret; +} + +static ssize_t vad_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + ssize_t c = PAGE_SIZE; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VAD, &voltage) == 0) { + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", voltage); + ret = PAGE_SIZE - c; + } else + ret = -EIO; + + return ret; +} + +static ssize_t vdd_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + ssize_t c = PAGE_SIZE; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VDD, &voltage) == 0) { + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", voltage); + ret = PAGE_SIZE - c; + } else + ret = -EIO; + + return ret; +} + +static BIN_ATTR(iad, S_IRUGO | S_IWUSR | S_IWGRP, NULL, iad_write, 1); +static BIN_ATTR_RO(page0, DS2438_PAGE_SIZE); +static BIN_ATTR_RO(temperature, 0/* real length varies */); +static BIN_ATTR_RO(vad, 0/* real length varies */); +static BIN_ATTR_RO(vdd, 0/* real length varies */); + +static struct bin_attribute *w1_ds2438_bin_attrs[] = { + &bin_attr_iad, + &bin_attr_page0, + &bin_attr_temperature, + &bin_attr_vad, + &bin_attr_vdd, + NULL, +}; + +static const struct attribute_group w1_ds2438_group = { + .bin_attrs = w1_ds2438_bin_attrs, +}; + +static const struct attribute_group *w1_ds2438_groups[] = { + &w1_ds2438_group, + NULL, +}; + +static struct w1_family_ops w1_ds2438_fops = { + .groups = w1_ds2438_groups, +}; + +static struct w1_family w1_ds2438_family = { + .fid = W1_FAMILY_DS2438, + .fops = &w1_ds2438_fops, +}; +module_w1_family(w1_ds2438_family); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mariusz Bialonczyk "); +MODULE_DESCRIPTION("1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2438)); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index c4a6b257a367..869a3ff87d29 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -29,6 +29,7 @@ #define W1_COUNTER_DS2423 0x1D #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 +#define W1_FAMILY_DS2438 0x26 #define W1_THERM_DS18B20 0x28 #define W1_FAMILY_DS2408 0x29 #define W1_EEPROM_DS2431 0x2D -- cgit v1.2.3-70-g09d2 From 6d691550ea0d67a86be351bb78fcd6d28bb60fff Mon Sep 17 00:00:00 2001 From: Mariusz Bialonczyk Date: Thu, 23 Feb 2017 07:38:44 +0100 Subject: w1: add documentation for w1_ds2438 Signed-off-by: Mariusz Bialonczyk Signed-off-by: Greg Kroah-Hartman --- Documentation/w1/slaves/00-INDEX | 2 ++ Documentation/w1/slaves/w1_ds2438 | 63 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Documentation/w1/slaves/w1_ds2438 diff --git a/Documentation/w1/slaves/00-INDEX b/Documentation/w1/slaves/00-INDEX index cbcca1d3a680..8d76718e1ea2 100644 --- a/Documentation/w1/slaves/00-INDEX +++ b/Documentation/w1/slaves/00-INDEX @@ -6,5 +6,7 @@ w1_ds2413 - The Maxim/Dallas Semiconductor ds2413 dual channel addressable switch. w1_ds2423 - The Maxim/Dallas Semiconductor ds2423 counter device. +w1_ds2438 + - The Maxim/Dallas Semiconductor ds2438 smart battery monitor. w1_ds28e04 - The Maxim/Dallas Semiconductor ds28e04 eeprom. diff --git a/Documentation/w1/slaves/w1_ds2438 b/Documentation/w1/slaves/w1_ds2438 new file mode 100644 index 000000000000..b99f3674c5b4 --- /dev/null +++ b/Documentation/w1/slaves/w1_ds2438 @@ -0,0 +1,63 @@ +Kernel driver w1_ds2438 +======================= + +Supported chips: + * Maxim DS2438 Smart Battery Monitor + +supported family codes: + W1_FAMILY_DS2438 0x26 + +Author: Mariusz Bialonczyk + +Description +----------- + +The DS2438 chip provides several functions that are desirable to carry in +a battery pack. It also has a 40 bytes of nonvolatile EEPROM. +Because the ability of temperature, current and voltage measurement, the chip +is also often used in weather stations and applications such as: rain gauge, +wind speed/direction measuring, humidity sensing, etc. + +Current support is provided through the following sysfs files (all files +except "iad" are readonly): + +"iad" +----- +This file controls the 'Current A/D Control Bit' (IAD) in the +Status/Configuration Register. +Writing a zero value will clear the IAD bit and disables the current +measurements. +Writing value "1" is setting the IAD bit (enables the measurements). +The IAD bit is enabled by default in the DS2438. + +When writing to sysfs file bits 2-7 are ignored, so it's safe to write ASCII. +An I/O error is returned when there is a problem setting the new value. + +"page0" +------- +This file provides full 8 bytes of the chip Page 0 (00h). +This page contains the most frequently accessed information of the DS2438. +Internally when this file is read, the additional CRC byte is also obtained +from the slave device. If it is correct, the 8 bytes page data are passed +to userspace, otherwise an I/O error is returned. + +"temperature" +------------- +Opening and reading this file initiates the CONVERT_T (temperature conversion) +command of the chip, afterwards the temperature is read from the device +registers and provided as an ASCII decimal value. + +Important: The returned value has to be divided by 256 to get a real +temperature in degrees Celsius. + +"vad", "vdd" +------------ +Opening and reading this file initiates the CONVERT_V (voltage conversion) +command of the chip. + +Depending on a sysfs filename a different input for the A/D will be selected: +vad: general purpose A/D input (VAD) +vdd: battery input (VDD) + +After the voltage conversion the value is returned as decimal ASCII. +Note: The value is in mV, so to get a volts the value has to be divided by 10. -- cgit v1.2.3-70-g09d2 From e447d4c06868d85342f6e35db499c463519a27d0 Mon Sep 17 00:00:00 2001 From: Mariusz Bialonczyk Date: Thu, 23 Feb 2017 07:38:45 +0100 Subject: w1: w1_ds2760.h: fix defines indentation Signed-off-by: Mariusz Bialonczyk Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/w1_ds2760.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/w1/slaves/w1_ds2760.h b/drivers/w1/slaves/w1_ds2760.h index 58e774141568..24168c94eeae 100644 --- a/drivers/w1/slaves/w1_ds2760.h +++ b/drivers/w1/slaves/w1_ds2760.h @@ -24,11 +24,13 @@ #define DS2760_DATA_SIZE 0x40 #define DS2760_PROTECTION_REG 0x00 + #define DS2760_STATUS_REG 0x01 - #define DS2760_STATUS_IE (1 << 2) - #define DS2760_STATUS_SWEN (1 << 3) - #define DS2760_STATUS_RNAOP (1 << 4) - #define DS2760_STATUS_PMOD (1 << 5) +#define DS2760_STATUS_IE (1 << 2) +#define DS2760_STATUS_SWEN (1 << 3) +#define DS2760_STATUS_RNAOP (1 << 4) +#define DS2760_STATUS_PMOD (1 << 5) + #define DS2760_EEPROM_REG 0x07 #define DS2760_SPECIAL_FEATURE_REG 0x08 #define DS2760_VOLTAGE_MSB 0x0c -- cgit v1.2.3-70-g09d2 From 2eec10807a2fbf0f240c55070b71b8cd5c09ab23 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Thu, 16 Feb 2017 23:11:26 -0800 Subject: drivers/char: Convert remaining use of pr_warning to pr_warn To enable eventual removal of pr_warning This makes pr_warn use consistent for drivers/char Prior to this patch, there were 1 use of pr_warning and 40 uses of pr_warn in drivers/char Signed-off-by: Joe Perches Reviewed-by: Amit Shah Signed-off-by: Greg Kroah-Hartman --- drivers/char/virtio_console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index e9b7e0b3cabe..c6f760b3d9d7 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -2302,7 +2302,7 @@ static int __init init(void) pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL); if (!pdrvdata.debugfs_dir) - pr_warning("Error creating debugfs dir for virtio-ports\n"); + pr_warn("Error creating debugfs dir for virtio-ports\n"); INIT_LIST_HEAD(&pdrvdata.consoles); INIT_LIST_HEAD(&pdrvdata.portdevs); -- cgit v1.2.3-70-g09d2 From 39f8ea46724efbed3ca021863a22337c31be264c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 10 Mar 2017 15:15:17 +0100 Subject: auxdisplay: charlcd: Extract character LCD core from misc/panel Extract the character LCD core from the Parallel port LCD/Keypad Panel driver in the misc subsystem, and convert it into a subdriver in the auxdisplay subsystem. This allows the character LCD core to be used by other drivers later. Compilation is controlled by its own Kconfig symbol CHARLCD, which is to be selected by its users, but can be enabled manually for compile-testing. All functions changed their prefix from "lcd_" to "charlcd_", and gained a "struct charlcd *" parameter to operate on a specific instance. While the driver API thus is ready to support multiple instances, the current limitation of a single display (/dev/lcd has a single misc minor assigned) is retained. No functional changes intended. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/Kconfig | 3 + drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/charlcd.c | 790 +++++++++++++++++++++++++++++++++++++++++ drivers/misc/Kconfig | 1 + drivers/misc/panel.c | 827 +++++-------------------------------------- include/misc/charlcd.h | 40 +++ 6 files changed, 927 insertions(+), 735 deletions(-) create mode 100644 drivers/auxdisplay/charlcd.c create mode 100644 include/misc/charlcd.h diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 8a8e403644d6..b4686380d4e3 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -13,6 +13,9 @@ menuconfig AUXDISPLAY If you say N, all options in this submenu will be skipped and disabled. +config CHARLCD + tristate "Character LCD core support" if COMPILE_TEST + if AUXDISPLAY config KS0108 diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index cb3dd847713b..f56f45dcc78e 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -2,6 +2,7 @@ # Makefile for the kernel auxiliary displays device drivers. # +obj-$(CONFIG_CHARLCD) += charlcd.o obj-$(CONFIG_KS0108) += ks0108.o obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c new file mode 100644 index 000000000000..930ffb2fb317 --- /dev/null +++ b/drivers/auxdisplay/charlcd.c @@ -0,0 +1,790 @@ +/* + * Character LCD driver for Linux + * + * Copyright (C) 2000-2008, Willy Tarreau + * Copyright (C) 2016-2017 Glider bvba + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define LCD_MINOR 156 + +#define DEFAULT_LCD_BWIDTH 40 +#define DEFAULT_LCD_HWIDTH 64 + +/* Keep the backlight on this many seconds for each flash */ +#define LCD_BL_TEMPO_PERIOD 4 + +#define LCD_FLAG_B 0x0004 /* Blink on */ +#define LCD_FLAG_C 0x0008 /* Cursor on */ +#define LCD_FLAG_D 0x0010 /* Display on */ +#define LCD_FLAG_F 0x0020 /* Large font mode */ +#define LCD_FLAG_N 0x0040 /* 2-rows mode */ +#define LCD_FLAG_L 0x0080 /* Backlight enabled */ + +/* LCD commands */ +#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ + +#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ +#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ + +#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ +#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ +#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ +#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ + +#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ +#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ +#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ + +#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ +#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ +#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ +#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ + +#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ + +#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ + +#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ +#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ + +struct charlcd_priv { + struct charlcd lcd; + + struct delayed_work bl_work; + struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ + bool bl_tempo; + + bool must_clear; + + /* contains the LCD config state */ + unsigned long int flags; + + /* Contains the LCD X and Y offset */ + struct { + unsigned long int x; + unsigned long int y; + } addr; + + /* Current escape sequence and it's length or -1 if outside */ + struct { + char buf[LCD_ESCAPE_LEN + 1]; + int len; + } esc_seq; + + unsigned long long drvdata[0]; +}; + +#define to_priv(p) container_of(p, struct charlcd_priv, lcd) + +/* Device single-open policy control */ +static atomic_t charlcd_available = ATOMIC_INIT(1); + +/* sleeps that many milliseconds with a reschedule */ +static void long_sleep(int ms) +{ + if (in_interrupt()) + mdelay(ms); + else + schedule_timeout_interruptible(msecs_to_jiffies(ms)); +} + +/* turn the backlight on or off */ +static void charlcd_backlight(struct charlcd *lcd, int on) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (!lcd->ops->backlight) + return; + + mutex_lock(&priv->bl_tempo_lock); + if (!priv->bl_tempo) + lcd->ops->backlight(lcd, on); + mutex_unlock(&priv->bl_tempo_lock); +} + +static void charlcd_bl_off(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct charlcd_priv *priv = + container_of(dwork, struct charlcd_priv, bl_work); + + mutex_lock(&priv->bl_tempo_lock); + if (priv->bl_tempo) { + priv->bl_tempo = false; + if (!(priv->flags & LCD_FLAG_L)) + priv->lcd.ops->backlight(&priv->lcd, 0); + } + mutex_unlock(&priv->bl_tempo_lock); +} + +/* turn the backlight on for a little while */ +void charlcd_poke(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (!lcd->ops->backlight) + return; + + cancel_delayed_work_sync(&priv->bl_work); + + mutex_lock(&priv->bl_tempo_lock); + if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) + lcd->ops->backlight(lcd, 1); + priv->bl_tempo = true; + schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); + mutex_unlock(&priv->bl_tempo_lock); +} +EXPORT_SYMBOL_GPL(charlcd_poke); + +static void charlcd_gotoxy(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + lcd->ops->write_cmd(lcd, + LCD_CMD_SET_DDRAM_ADDR | (priv->addr.y ? lcd->hwidth : 0) | + /* + * we force the cursor to stay at the end of the + * line if it wants to go farther + */ + ((priv->addr.x < lcd->bwidth) ? priv->addr.x & (lcd->hwidth - 1) + : lcd->bwidth - 1)); +} + +static void charlcd_home(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + priv->addr.x = 0; + priv->addr.y = 0; + charlcd_gotoxy(lcd); +} + +static void charlcd_print(struct charlcd *lcd, char c) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (priv->addr.x < lcd->bwidth) { + if (lcd->char_conv) + c = lcd->char_conv[(unsigned char)c]; + lcd->ops->write_data(lcd, c); + priv->addr.x++; + } + /* prevents the cursor from wrapping onto the next line */ + if (priv->addr.x == lcd->bwidth) + charlcd_gotoxy(lcd); +} + +static void charlcd_clear_fast(struct charlcd *lcd) +{ + int pos; + + charlcd_home(lcd); + + if (lcd->ops->clear_fast) + lcd->ops->clear_fast(lcd); + else + for (pos = 0; pos < lcd->height * lcd->hwidth; pos++) + lcd->ops->write_data(lcd, ' '); + + charlcd_home(lcd); +} + +/* clears the display and resets X/Y */ +static void charlcd_clear_display(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR); + priv->addr.x = 0; + priv->addr.y = 0; + /* we must wait a few milliseconds (15) */ + long_sleep(15); +} + +static int charlcd_init_display(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | + LCD_FLAG_C | LCD_FLAG_B; + + long_sleep(20); /* wait 20 ms after power-up for the paranoid */ + + /* 8bits, 1 line, small fonts; let's do it 3 times */ + lcd->ops->write_cmd(lcd, LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); + long_sleep(10); + lcd->ops->write_cmd(lcd, LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); + long_sleep(10); + lcd->ops->write_cmd(lcd, LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); + long_sleep(10); + + /* set font height and lines number */ + lcd->ops->write_cmd(lcd, + LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS | + ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | + ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); + long_sleep(10); + + /* display off, cursor off, blink off */ + lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL); + long_sleep(10); + + lcd->ops->write_cmd(lcd, + LCD_CMD_DISPLAY_CTRL | /* set display mode */ + ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | + ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | + ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); + + charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); + + long_sleep(10); + + /* entry mode set : increment, cursor shifting */ + lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); + + charlcd_clear_display(lcd); + return 0; +} + +/* + * These are the file operation function for user access to /dev/lcd + * This function can also be called from inside the kernel, by + * setting file and ppos to NULL. + * + */ + +static inline int handle_lcd_special_code(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + /* LCD special codes */ + + int processed = 0; + + char *esc = priv->esc_seq.buf + 2; + int oldflags = priv->flags; + + /* check for display mode flags */ + switch (*esc) { + case 'D': /* Display ON */ + priv->flags |= LCD_FLAG_D; + processed = 1; + break; + case 'd': /* Display OFF */ + priv->flags &= ~LCD_FLAG_D; + processed = 1; + break; + case 'C': /* Cursor ON */ + priv->flags |= LCD_FLAG_C; + processed = 1; + break; + case 'c': /* Cursor OFF */ + priv->flags &= ~LCD_FLAG_C; + processed = 1; + break; + case 'B': /* Blink ON */ + priv->flags |= LCD_FLAG_B; + processed = 1; + break; + case 'b': /* Blink OFF */ + priv->flags &= ~LCD_FLAG_B; + processed = 1; + break; + case '+': /* Back light ON */ + priv->flags |= LCD_FLAG_L; + processed = 1; + break; + case '-': /* Back light OFF */ + priv->flags &= ~LCD_FLAG_L; + processed = 1; + break; + case '*': /* Flash back light */ + charlcd_poke(lcd); + processed = 1; + break; + case 'f': /* Small Font */ + priv->flags &= ~LCD_FLAG_F; + processed = 1; + break; + case 'F': /* Large Font */ + priv->flags |= LCD_FLAG_F; + processed = 1; + break; + case 'n': /* One Line */ + priv->flags &= ~LCD_FLAG_N; + processed = 1; + break; + case 'N': /* Two Lines */ + priv->flags |= LCD_FLAG_N; + break; + case 'l': /* Shift Cursor Left */ + if (priv->addr.x > 0) { + /* back one char if not at end of line */ + if (priv->addr.x < lcd->bwidth) + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + priv->addr.x--; + } + processed = 1; + break; + case 'r': /* shift cursor right */ + if (priv->addr.x < lcd->width) { + /* allow the cursor to pass the end of the line */ + if (priv->addr.x < (lcd->bwidth - 1)) + lcd->ops->write_cmd(lcd, + LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); + priv->addr.x++; + } + processed = 1; + break; + case 'L': /* shift display left */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); + processed = 1; + break; + case 'R': /* shift display right */ + lcd->ops->write_cmd(lcd, + LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | + LCD_CMD_SHIFT_RIGHT); + processed = 1; + break; + case 'k': { /* kill end of line */ + int x; + + for (x = priv->addr.x; x < lcd->bwidth; x++) + lcd->ops->write_data(lcd, ' '); + + /* restore cursor position */ + charlcd_gotoxy(lcd); + processed = 1; + break; + } + case 'I': /* reinitialize display */ + charlcd_init_display(lcd); + processed = 1; + break; + case 'G': { + /* Generator : LGcxxxxx...xx; must have between '0' + * and '7', representing the numerical ASCII code of the + * redefined character, and a sequence of 16 + * hex digits representing 8 bytes for each character. + * Most LCDs will only use 5 lower bits of the 7 first + * bytes. + */ + + unsigned char cgbytes[8]; + unsigned char cgaddr; + int cgoffset; + int shift; + char value; + int addr; + + if (!strchr(esc, ';')) + break; + + esc++; + + cgaddr = *(esc++) - '0'; + if (cgaddr > 7) { + processed = 1; + break; + } + + cgoffset = 0; + shift = 0; + value = 0; + while (*esc && cgoffset < 8) { + shift ^= 4; + if (*esc >= '0' && *esc <= '9') { + value |= (*esc - '0') << shift; + } else if (*esc >= 'A' && *esc <= 'Z') { + value |= (*esc - 'A' + 10) << shift; + } else if (*esc >= 'a' && *esc <= 'z') { + value |= (*esc - 'a' + 10) << shift; + } else { + esc++; + continue; + } + + if (shift == 0) { + cgbytes[cgoffset++] = value; + value = 0; + } + + esc++; + } + + lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); + for (addr = 0; addr < cgoffset; addr++) + lcd->ops->write_data(lcd, cgbytes[addr]); + + /* ensures that we stop writing to CGRAM */ + charlcd_gotoxy(lcd); + processed = 1; + break; + } + case 'x': /* gotoxy : LxXXX[yYYY]; */ + case 'y': /* gotoxy : LyYYY[xXXX]; */ + if (!strchr(esc, ';')) + break; + + while (*esc) { + if (*esc == 'x') { + esc++; + if (kstrtoul(esc, 10, &priv->addr.x) < 0) + break; + } else if (*esc == 'y') { + esc++; + if (kstrtoul(esc, 10, &priv->addr.y) < 0) + break; + } else { + break; + } + } + + charlcd_gotoxy(lcd); + processed = 1; + break; + } + + /* TODO: This indent party here got ugly, clean it! */ + /* Check whether one flag was changed */ + if (oldflags == priv->flags) + return processed; + + /* check whether one of B,C,D flags were changed */ + if ((oldflags ^ priv->flags) & + (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) + /* set display mode */ + lcd->ops->write_cmd(lcd, + LCD_CMD_DISPLAY_CTRL | + ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | + ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | + ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); + /* check whether one of F,N flags was changed */ + else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) + lcd->ops->write_cmd(lcd, + LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS | + ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | + ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); + /* check whether L flag was changed */ + else if ((oldflags ^ priv->flags) & LCD_FLAG_L) + charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); + + return processed; +} + +static void charlcd_write_char(struct charlcd *lcd, char c) +{ + struct charlcd_priv *priv = to_priv(lcd); + + /* first, we'll test if we're in escape mode */ + if ((c != '\n') && priv->esc_seq.len >= 0) { + /* yes, let's add this char to the buffer */ + priv->esc_seq.buf[priv->esc_seq.len++] = c; + priv->esc_seq.buf[priv->esc_seq.len] = 0; + } else { + /* aborts any previous escape sequence */ + priv->esc_seq.len = -1; + + switch (c) { + case LCD_ESCAPE_CHAR: + /* start of an escape sequence */ + priv->esc_seq.len = 0; + priv->esc_seq.buf[priv->esc_seq.len] = 0; + break; + case '\b': + /* go back one char and clear it */ + if (priv->addr.x > 0) { + /* + * check if we're not at the + * end of the line + */ + if (priv->addr.x < lcd->bwidth) + /* back one char */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + priv->addr.x--; + } + /* replace with a space */ + lcd->ops->write_data(lcd, ' '); + /* back one char again */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + break; + case '\014': + /* quickly clear the display */ + charlcd_clear_fast(lcd); + break; + case '\n': + /* + * flush the remainder of the current line and + * go to the beginning of the next line + */ + for (; priv->addr.x < lcd->bwidth; priv->addr.x++) + lcd->ops->write_data(lcd, ' '); + priv->addr.x = 0; + priv->addr.y = (priv->addr.y + 1) % lcd->height; + charlcd_gotoxy(lcd); + break; + case '\r': + /* go to the beginning of the same line */ + priv->addr.x = 0; + charlcd_gotoxy(lcd); + break; + case '\t': + /* print a space instead of the tab */ + charlcd_print(lcd, ' '); + break; + default: + /* simply print this char */ + charlcd_print(lcd, c); + break; + } + } + + /* + * now we'll see if we're in an escape mode and if the current + * escape sequence can be understood. + */ + if (priv->esc_seq.len >= 2) { + int processed = 0; + + if (!strcmp(priv->esc_seq.buf, "[2J")) { + /* clear the display */ + charlcd_clear_fast(lcd); + processed = 1; + } else if (!strcmp(priv->esc_seq.buf, "[H")) { + /* cursor to home */ + charlcd_home(lcd); + processed = 1; + } + /* codes starting with ^[[L */ + else if ((priv->esc_seq.len >= 3) && + (priv->esc_seq.buf[0] == '[') && + (priv->esc_seq.buf[1] == 'L')) { + processed = handle_lcd_special_code(lcd); + } + + /* LCD special escape codes */ + /* + * flush the escape sequence if it's been processed + * or if it is getting too long. + */ + if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) + priv->esc_seq.len = -1; + } /* escape codes */ +} + +static struct charlcd *the_charlcd; + +static ssize_t charlcd_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + const char __user *tmp = buf; + char c; + + for (; count-- > 0; (*ppos)++, tmp++) { + if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) + /* + * let's be a little nice with other processes + * that need some CPU + */ + schedule(); + + if (get_user(c, tmp)) + return -EFAULT; + + charlcd_write_char(the_charlcd, c); + } + + return tmp - buf; +} + +static int charlcd_open(struct inode *inode, struct file *file) +{ + struct charlcd_priv *priv = to_priv(the_charlcd); + + if (!atomic_dec_and_test(&charlcd_available)) + return -EBUSY; /* open only once at a time */ + + if (file->f_mode & FMODE_READ) /* device is write-only */ + return -EPERM; + + if (priv->must_clear) { + charlcd_clear_display(&priv->lcd); + priv->must_clear = false; + } + return nonseekable_open(inode, file); +} + +static int charlcd_release(struct inode *inode, struct file *file) +{ + atomic_inc(&charlcd_available); + return 0; +} + +static const struct file_operations charlcd_fops = { + .write = charlcd_write, + .open = charlcd_open, + .release = charlcd_release, + .llseek = no_llseek, +}; + +static struct miscdevice charlcd_dev = { + .minor = LCD_MINOR, + .name = "lcd", + .fops = &charlcd_fops, +}; + +static void charlcd_puts(struct charlcd *lcd, const char *s) +{ + const char *tmp = s; + int count = strlen(s); + + for (; count-- > 0; tmp++) { + if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) + /* + * let's be a little nice with other processes + * that need some CPU + */ + schedule(); + + charlcd_write_char(lcd, *tmp); + } +} + +/* initialize the LCD driver */ +static int charlcd_init(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + int ret; + + if (lcd->ops->backlight) { + mutex_init(&priv->bl_tempo_lock); + INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); + } + + /* + * before this line, we must NOT send anything to the display. + * Since charlcd_init_display() needs to write data, we have to + * enable mark the LCD initialized just before. + */ + ret = charlcd_init_display(lcd); + if (ret) + return ret; + + /* display a short message */ +#ifdef CONFIG_PANEL_CHANGE_MESSAGE +#ifdef CONFIG_PANEL_BOOT_MESSAGE + charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); +#endif +#else + charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n"); +#endif + /* clear the display on the next device opening */ + priv->must_clear = true; + charlcd_home(lcd); + return 0; +} + +struct charlcd *charlcd_alloc(unsigned int drvdata_size) +{ + struct charlcd_priv *priv; + struct charlcd *lcd; + + priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); + if (!priv) + return NULL; + + priv->esc_seq.len = -1; + + lcd = &priv->lcd; + lcd->bwidth = DEFAULT_LCD_BWIDTH; + lcd->hwidth = DEFAULT_LCD_HWIDTH; + lcd->drvdata = priv->drvdata; + + return lcd; +} +EXPORT_SYMBOL_GPL(charlcd_alloc); + +static int panel_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + struct charlcd *lcd = the_charlcd; + + switch (code) { + case SYS_DOWN: + charlcd_puts(lcd, + "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); + break; + case SYS_HALT: + charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); + break; + case SYS_POWER_OFF: + charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block panel_notifier = { + panel_notify_sys, + NULL, + 0 +}; + +int charlcd_register(struct charlcd *lcd) +{ + int ret; + + ret = charlcd_init(lcd); + if (ret) + return ret; + + ret = misc_register(&charlcd_dev); + if (ret) + return ret; + + the_charlcd = lcd; + register_reboot_notifier(&panel_notifier); + return 0; +} +EXPORT_SYMBOL_GPL(charlcd_register); + +int charlcd_unregister(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + unregister_reboot_notifier(&panel_notifier); + charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); + misc_deregister(&charlcd_dev); + the_charlcd = NULL; + if (lcd->ops->backlight) { + cancel_delayed_work_sync(&priv->bl_work); + priv->lcd.ops->backlight(&priv->lcd, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(charlcd_unregister); + +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 77b001e7cf85..fb933b0b9297 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -495,6 +495,7 @@ config VEXPRESS_SYSCFG config PANEL tristate "Parallel port LCD/Keypad Panel support" depends on PARPORT + select CHARLCD ---help--- Say Y here if you have an HD44780 or KS-0074 LCD connected to your parallel port. This driver also features 4 and 6-key keypads. The LCD diff --git a/drivers/misc/panel.c b/drivers/misc/panel.c index ef2ece0f26af..e0c014c2356f 100644 --- a/drivers/misc/panel.c +++ b/drivers/misc/panel.c @@ -1,6 +1,7 @@ /* * Front panel driver for Linux * Copyright (C) 2000-2008, Willy Tarreau + * Copyright (C) 2016-2017 Glider bvba * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -54,15 +55,12 @@ #include #include #include -#include -#include -#include -#include #include #include -#define LCD_MINOR 156 +#include + #define KEYPAD_MINOR 185 #define LCD_MAXBYTES 256 /* max burst write */ @@ -76,9 +74,6 @@ /* a key repeats this times INPUT_POLL_TIME */ #define KEYPAD_REP_DELAY (2) -/* keep the light on this many seconds for each flash */ -#define FLASH_LIGHT_TEMPO (4) - /* converts an r_str() input to an active high, bits string : 000BAOSE */ #define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3) @@ -120,40 +115,6 @@ #define PIN_SELECP 17 #define PIN_NOT_SET 127 -#define LCD_FLAG_B 0x0004 /* blink on */ -#define LCD_FLAG_C 0x0008 /* cursor on */ -#define LCD_FLAG_D 0x0010 /* display on */ -#define LCD_FLAG_F 0x0020 /* large font mode */ -#define LCD_FLAG_N 0x0040 /* 2-rows mode */ -#define LCD_FLAG_L 0x0080 /* backlight enabled */ - -/* LCD commands */ -#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ - -#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ -#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ - -#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ -#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ -#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ -#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ - -#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ -#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ -#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ - -#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ -#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ -#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ -#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ - -#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ - -#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ - -#define LCD_ESCAPE_LEN 24 /* max chars for LCD escape command */ -#define LCD_ESCAPE_CHAR 27 /* use char 27 for escape command */ - #define NOT_SET -1 /* macros to simplify use of the parallel port */ @@ -245,19 +206,10 @@ static wait_queue_head_t keypad_read_wait; static struct { bool enabled; bool initialized; - bool must_clear; - int height; - int width; - int bwidth; - int hwidth; int charset; int proto; - struct delayed_work bl_work; - struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ - bool bl_tempo; - /* TODO: use union here? */ struct { int e; @@ -268,20 +220,7 @@ static struct { int bl; } pins; - /* contains the LCD config state */ - unsigned long int flags; - - /* Contains the LCD X and Y offset */ - struct { - unsigned long int x; - unsigned long int y; - } addr; - - /* Current escape sequence and it's length or -1 if outside */ - struct { - char buf[LCD_ESCAPE_LEN + 1]; - int len; - } esc_seq; + struct charlcd *charlcd; } lcd; /* Needed only for init */ @@ -464,17 +403,12 @@ static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES]; /* global variables */ /* Device single-open policy control */ -static atomic_t lcd_available = ATOMIC_INIT(1); static atomic_t keypad_available = ATOMIC_INIT(1); static struct pardevice *pprt; static int keypad_initialized; -static void (*lcd_write_cmd)(int); -static void (*lcd_write_data)(int); -static void (*lcd_clear_fast)(void); - static DEFINE_SPINLOCK(pprt_lock); static struct timer_list scan_timer; @@ -574,8 +508,6 @@ static int keypad_enabled = NOT_SET; module_param(keypad_enabled, int, 0000); MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead"); -static const unsigned char *lcd_char_conv; - /* for some LCD drivers (ks0074) we need a charset conversion table. */ static const unsigned char lcd_char_conv_ks0074[256] = { /* 0|8 1|9 2|A 3|B 4|C 5|D 6|E 7|F */ @@ -752,15 +684,6 @@ static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val) } } -/* sleeps that many milliseconds with a reschedule */ -static void long_sleep(int ms) -{ - if (in_interrupt()) - mdelay(ms); - else - schedule_timeout_interruptible(msecs_to_jiffies(ms)); -} - /* * send a serial byte to the LCD panel. The caller is responsible for locking * if needed. @@ -792,8 +715,11 @@ static void lcd_send_serial(int byte) } /* turn the backlight on or off */ -static void __lcd_backlight(int on) +static void lcd_backlight(struct charlcd *charlcd, int on) { + if (lcd.pins.bl == PIN_NONE) + return; + /* The backlight is activated by setting the AUTOFEED line to +5V */ spin_lock_irq(&pprt_lock); if (on) @@ -804,46 +730,8 @@ static void __lcd_backlight(int on) spin_unlock_irq(&pprt_lock); } -static void lcd_backlight(int on) -{ - if (lcd.pins.bl == PIN_NONE) - return; - - mutex_lock(&lcd.bl_tempo_lock); - if (!lcd.bl_tempo) - __lcd_backlight(on); - mutex_unlock(&lcd.bl_tempo_lock); -} - -static void lcd_bl_off(struct work_struct *work) -{ - mutex_lock(&lcd.bl_tempo_lock); - if (lcd.bl_tempo) { - lcd.bl_tempo = false; - if (!(lcd.flags & LCD_FLAG_L)) - __lcd_backlight(0); - } - mutex_unlock(&lcd.bl_tempo_lock); -} - -/* turn the backlight on for a little while */ -static void lcd_poke(void) -{ - if (lcd.pins.bl == PIN_NONE) - return; - - cancel_delayed_work_sync(&lcd.bl_work); - - mutex_lock(&lcd.bl_tempo_lock); - if (!lcd.bl_tempo && !(lcd.flags & LCD_FLAG_L)) - __lcd_backlight(1); - lcd.bl_tempo = true; - schedule_delayed_work(&lcd.bl_work, FLASH_LIGHT_TEMPO * HZ); - mutex_unlock(&lcd.bl_tempo_lock); -} - /* send a command to the LCD panel in serial mode */ -static void lcd_write_cmd_s(int cmd) +static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd) { spin_lock_irq(&pprt_lock); lcd_send_serial(0x1F); /* R/W=W, RS=0 */ @@ -854,7 +742,7 @@ static void lcd_write_cmd_s(int cmd) } /* send data to the LCD panel in serial mode */ -static void lcd_write_data_s(int data) +static void lcd_write_data_s(struct charlcd *charlcd, int data) { spin_lock_irq(&pprt_lock); lcd_send_serial(0x5F); /* R/W=W, RS=1 */ @@ -865,7 +753,7 @@ static void lcd_write_data_s(int data) } /* send a command to the LCD panel in 8 bits parallel mode */ -static void lcd_write_cmd_p8(int cmd) +static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd) { spin_lock_irq(&pprt_lock); /* present the data to the data port */ @@ -887,7 +775,7 @@ static void lcd_write_cmd_p8(int cmd) } /* send data to the LCD panel in 8 bits parallel mode */ -static void lcd_write_data_p8(int data) +static void lcd_write_data_p8(struct charlcd *charlcd, int data) { spin_lock_irq(&pprt_lock); /* present the data to the data port */ @@ -909,7 +797,7 @@ static void lcd_write_data_p8(int data) } /* send a command to the TI LCD panel */ -static void lcd_write_cmd_tilcd(int cmd) +static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd) { spin_lock_irq(&pprt_lock); /* present the data to the control port */ @@ -919,7 +807,7 @@ static void lcd_write_cmd_tilcd(int cmd) } /* send data to the TI LCD panel */ -static void lcd_write_data_tilcd(int data) +static void lcd_write_data_tilcd(struct charlcd *charlcd, int data) { spin_lock_irq(&pprt_lock); /* present the data to the data port */ @@ -928,47 +816,13 @@ static void lcd_write_data_tilcd(int data) spin_unlock_irq(&pprt_lock); } -static void lcd_gotoxy(void) -{ - lcd_write_cmd(LCD_CMD_SET_DDRAM_ADDR - | (lcd.addr.y ? lcd.hwidth : 0) - /* - * we force the cursor to stay at the end of the - * line if it wants to go farther - */ - | ((lcd.addr.x < lcd.bwidth) ? lcd.addr.x & - (lcd.hwidth - 1) : lcd.bwidth - 1)); -} - -static void lcd_home(void) -{ - lcd.addr.x = 0; - lcd.addr.y = 0; - lcd_gotoxy(); -} - -static void lcd_print(char c) -{ - if (lcd.addr.x < lcd.bwidth) { - if (lcd_char_conv) - c = lcd_char_conv[(unsigned char)c]; - lcd_write_data(c); - lcd.addr.x++; - } - /* prevents the cursor from wrapping onto the next line */ - if (lcd.addr.x == lcd.bwidth) - lcd_gotoxy(); -} - /* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_s(void) +static void lcd_clear_fast_s(struct charlcd *charlcd) { int pos; - lcd_home(); - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { lcd_send_serial(0x5F); /* R/W=W, RS=1 */ lcd_send_serial(' ' & 0x0F); lcd_send_serial((' ' >> 4) & 0x0F); @@ -976,19 +830,15 @@ static void lcd_clear_fast_s(void) udelay(40); } spin_unlock_irq(&pprt_lock); - - lcd_home(); } /* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_p8(void) +static void lcd_clear_fast_p8(struct charlcd *charlcd) { int pos; - lcd_home(); - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { /* present the data to the data port */ w_dtr(pprt, ' '); @@ -1010,488 +860,62 @@ static void lcd_clear_fast_p8(void) udelay(45); } spin_unlock_irq(&pprt_lock); - - lcd_home(); } /* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_tilcd(void) +static void lcd_clear_fast_tilcd(struct charlcd *charlcd) { int pos; - lcd_home(); - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) { + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { /* present the data to the data port */ w_dtr(pprt, ' '); udelay(60); } spin_unlock_irq(&pprt_lock); - - lcd_home(); -} - -/* clears the display and resets X/Y */ -static void lcd_clear_display(void) -{ - lcd_write_cmd(LCD_CMD_DISPLAY_CLEAR); - lcd.addr.x = 0; - lcd.addr.y = 0; - /* we must wait a few milliseconds (15) */ - long_sleep(15); } -static void lcd_init_display(void) -{ - lcd.flags = ((lcd.height > 1) ? LCD_FLAG_N : 0) - | LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B; - - long_sleep(20); /* wait 20 ms after power-up for the paranoid */ - - /* 8bits, 1 line, small fonts; let's do it 3 times */ - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); - long_sleep(10); - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); - long_sleep(10); - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); - long_sleep(10); - - /* set font height and lines number */ - lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS - | ((lcd.flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) - | ((lcd.flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0) - ); - long_sleep(10); - - /* display off, cursor off, blink off */ - lcd_write_cmd(LCD_CMD_DISPLAY_CTRL); - long_sleep(10); - - lcd_write_cmd(LCD_CMD_DISPLAY_CTRL /* set display mode */ - | ((lcd.flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) - | ((lcd.flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) - | ((lcd.flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0) - ); - - lcd_backlight((lcd.flags & LCD_FLAG_L) ? 1 : 0); - - long_sleep(10); - - /* entry mode set : increment, cursor shifting */ - lcd_write_cmd(LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); +static struct charlcd_ops charlcd_serial_ops = { + .write_cmd = lcd_write_cmd_s, + .write_data = lcd_write_data_s, + .clear_fast = lcd_clear_fast_s, + .backlight = lcd_backlight, +}; - lcd_clear_display(); -} +static struct charlcd_ops charlcd_parallel_ops = { + .write_cmd = lcd_write_cmd_p8, + .write_data = lcd_write_data_p8, + .clear_fast = lcd_clear_fast_p8, + .backlight = lcd_backlight, +}; -/* - * These are the file operation function for user access to /dev/lcd - * This function can also be called from inside the kernel, by - * setting file and ppos to NULL. - * - */ +static struct charlcd_ops charlcd_tilcd_ops = { + .write_cmd = lcd_write_cmd_tilcd, + .write_data = lcd_write_data_tilcd, + .clear_fast = lcd_clear_fast_tilcd, + .backlight = lcd_backlight, +}; -static inline int handle_lcd_special_code(void) +/* initialize the LCD driver */ +static void lcd_init(void) { - /* LCD special codes */ - - int processed = 0; + struct charlcd *charlcd; - char *esc = lcd.esc_seq.buf + 2; - int oldflags = lcd.flags; - - /* check for display mode flags */ - switch (*esc) { - case 'D': /* Display ON */ - lcd.flags |= LCD_FLAG_D; - processed = 1; - break; - case 'd': /* Display OFF */ - lcd.flags &= ~LCD_FLAG_D; - processed = 1; - break; - case 'C': /* Cursor ON */ - lcd.flags |= LCD_FLAG_C; - processed = 1; - break; - case 'c': /* Cursor OFF */ - lcd.flags &= ~LCD_FLAG_C; - processed = 1; - break; - case 'B': /* Blink ON */ - lcd.flags |= LCD_FLAG_B; - processed = 1; - break; - case 'b': /* Blink OFF */ - lcd.flags &= ~LCD_FLAG_B; - processed = 1; - break; - case '+': /* Back light ON */ - lcd.flags |= LCD_FLAG_L; - processed = 1; - break; - case '-': /* Back light OFF */ - lcd.flags &= ~LCD_FLAG_L; - processed = 1; - break; - case '*': - /* flash back light */ - lcd_poke(); - processed = 1; - break; - case 'f': /* Small Font */ - lcd.flags &= ~LCD_FLAG_F; - processed = 1; - break; - case 'F': /* Large Font */ - lcd.flags |= LCD_FLAG_F; - processed = 1; - break; - case 'n': /* One Line */ - lcd.flags &= ~LCD_FLAG_N; - processed = 1; - break; - case 'N': /* Two Lines */ - lcd.flags |= LCD_FLAG_N; - break; - case 'l': /* Shift Cursor Left */ - if (lcd.addr.x > 0) { - /* back one char if not at end of line */ - if (lcd.addr.x < lcd.bwidth) - lcd_write_cmd(LCD_CMD_SHIFT); - lcd.addr.x--; - } - processed = 1; - break; - case 'r': /* shift cursor right */ - if (lcd.addr.x < lcd.width) { - /* allow the cursor to pass the end of the line */ - if (lcd.addr.x < (lcd.bwidth - 1)) - lcd_write_cmd(LCD_CMD_SHIFT | - LCD_CMD_SHIFT_RIGHT); - lcd.addr.x++; - } - processed = 1; - break; - case 'L': /* shift display left */ - lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); - processed = 1; - break; - case 'R': /* shift display right */ - lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | - LCD_CMD_SHIFT_RIGHT); - processed = 1; - break; - case 'k': { /* kill end of line */ - int x; - - for (x = lcd.addr.x; x < lcd.bwidth; x++) - lcd_write_data(' '); - - /* restore cursor position */ - lcd_gotoxy(); - processed = 1; - break; - } - case 'I': /* reinitialize display */ - lcd_init_display(); - processed = 1; - break; - case 'G': { - /* Generator : LGcxxxxx...xx; must have between '0' - * and '7', representing the numerical ASCII code of the - * redefined character, and a sequence of 16 - * hex digits representing 8 bytes for each character. - * Most LCDs will only use 5 lower bits of the 7 first - * bytes. - */ - - unsigned char cgbytes[8]; - unsigned char cgaddr; - int cgoffset; - int shift; - char value; - int addr; - - if (!strchr(esc, ';')) - break; - - esc++; - - cgaddr = *(esc++) - '0'; - if (cgaddr > 7) { - processed = 1; - break; - } - - cgoffset = 0; - shift = 0; - value = 0; - while (*esc && cgoffset < 8) { - shift ^= 4; - if (*esc >= '0' && *esc <= '9') { - value |= (*esc - '0') << shift; - } else if (*esc >= 'A' && *esc <= 'Z') { - value |= (*esc - 'A' + 10) << shift; - } else if (*esc >= 'a' && *esc <= 'z') { - value |= (*esc - 'a' + 10) << shift; - } else { - esc++; - continue; - } - - if (shift == 0) { - cgbytes[cgoffset++] = value; - value = 0; - } - - esc++; - } - - lcd_write_cmd(LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); - for (addr = 0; addr < cgoffset; addr++) - lcd_write_data(cgbytes[addr]); - - /* ensures that we stop writing to CGRAM */ - lcd_gotoxy(); - processed = 1; - break; - } - case 'x': /* gotoxy : LxXXX[yYYY]; */ - case 'y': /* gotoxy : LyYYY[xXXX]; */ - if (!strchr(esc, ';')) - break; - - while (*esc) { - if (*esc == 'x') { - esc++; - if (kstrtoul(esc, 10, &lcd.addr.x) < 0) - break; - } else if (*esc == 'y') { - esc++; - if (kstrtoul(esc, 10, &lcd.addr.y) < 0) - break; - } else { - break; - } - } - - lcd_gotoxy(); - processed = 1; - break; - } - - /* TODO: This indent party here got ugly, clean it! */ - /* Check whether one flag was changed */ - if (oldflags != lcd.flags) { - /* check whether one of B,C,D flags were changed */ - if ((oldflags ^ lcd.flags) & - (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) - /* set display mode */ - lcd_write_cmd(LCD_CMD_DISPLAY_CTRL - | ((lcd.flags & LCD_FLAG_D) - ? LCD_CMD_DISPLAY_ON : 0) - | ((lcd.flags & LCD_FLAG_C) - ? LCD_CMD_CURSOR_ON : 0) - | ((lcd.flags & LCD_FLAG_B) - ? LCD_CMD_BLINK_ON : 0)); - /* check whether one of F,N flags was changed */ - else if ((oldflags ^ lcd.flags) & (LCD_FLAG_F | LCD_FLAG_N)) - lcd_write_cmd(LCD_CMD_FUNCTION_SET - | LCD_CMD_DATA_LEN_8BITS - | ((lcd.flags & LCD_FLAG_F) - ? LCD_CMD_FONT_5X10_DOTS - : 0) - | ((lcd.flags & LCD_FLAG_N) - ? LCD_CMD_TWO_LINES - : 0)); - /* check whether L flag was changed */ - else if ((oldflags ^ lcd.flags) & (LCD_FLAG_L)) - lcd_backlight(!!(lcd.flags & LCD_FLAG_L)); - } - - return processed; -} - -static void lcd_write_char(char c) -{ - /* first, we'll test if we're in escape mode */ - if ((c != '\n') && lcd.esc_seq.len >= 0) { - /* yes, let's add this char to the buffer */ - lcd.esc_seq.buf[lcd.esc_seq.len++] = c; - lcd.esc_seq.buf[lcd.esc_seq.len] = 0; - } else { - /* aborts any previous escape sequence */ - lcd.esc_seq.len = -1; - - switch (c) { - case LCD_ESCAPE_CHAR: - /* start of an escape sequence */ - lcd.esc_seq.len = 0; - lcd.esc_seq.buf[lcd.esc_seq.len] = 0; - break; - case '\b': - /* go back one char and clear it */ - if (lcd.addr.x > 0) { - /* - * check if we're not at the - * end of the line - */ - if (lcd.addr.x < lcd.bwidth) - /* back one char */ - lcd_write_cmd(LCD_CMD_SHIFT); - lcd.addr.x--; - } - /* replace with a space */ - lcd_write_data(' '); - /* back one char again */ - lcd_write_cmd(LCD_CMD_SHIFT); - break; - case '\014': - /* quickly clear the display */ - lcd_clear_fast(); - break; - case '\n': - /* - * flush the remainder of the current line and - * go to the beginning of the next line - */ - for (; lcd.addr.x < lcd.bwidth; lcd.addr.x++) - lcd_write_data(' '); - lcd.addr.x = 0; - lcd.addr.y = (lcd.addr.y + 1) % lcd.height; - lcd_gotoxy(); - break; - case '\r': - /* go to the beginning of the same line */ - lcd.addr.x = 0; - lcd_gotoxy(); - break; - case '\t': - /* print a space instead of the tab */ - lcd_print(' '); - break; - default: - /* simply print this char */ - lcd_print(c); - break; - } - } + charlcd = charlcd_alloc(0); + if (!charlcd) + return; /* - * now we'll see if we're in an escape mode and if the current - * escape sequence can be understood. + * Init lcd struct with load-time values to preserve exact + * current functionality (at least for now). */ - if (lcd.esc_seq.len >= 2) { - int processed = 0; - - if (!strcmp(lcd.esc_seq.buf, "[2J")) { - /* clear the display */ - lcd_clear_fast(); - processed = 1; - } else if (!strcmp(lcd.esc_seq.buf, "[H")) { - /* cursor to home */ - lcd_home(); - processed = 1; - } - /* codes starting with ^[[L */ - else if ((lcd.esc_seq.len >= 3) && - (lcd.esc_seq.buf[0] == '[') && - (lcd.esc_seq.buf[1] == 'L')) { - processed = handle_lcd_special_code(); - } - - /* LCD special escape codes */ - /* - * flush the escape sequence if it's been processed - * or if it is getting too long. - */ - if (processed || (lcd.esc_seq.len >= LCD_ESCAPE_LEN)) - lcd.esc_seq.len = -1; - } /* escape codes */ -} + charlcd->height = lcd_height; + charlcd->width = lcd_width; + charlcd->bwidth = lcd_bwidth; + charlcd->hwidth = lcd_hwidth; -static ssize_t lcd_write(struct file *file, - const char __user *buf, size_t count, loff_t *ppos) -{ - const char __user *tmp = buf; - char c; - - for (; count-- > 0; (*ppos)++, tmp++) { - if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) - /* - * let's be a little nice with other processes - * that need some CPU - */ - schedule(); - - if (get_user(c, tmp)) - return -EFAULT; - - lcd_write_char(c); - } - - return tmp - buf; -} - -static int lcd_open(struct inode *inode, struct file *file) -{ - if (!atomic_dec_and_test(&lcd_available)) - return -EBUSY; /* open only once at a time */ - - if (file->f_mode & FMODE_READ) /* device is write-only */ - return -EPERM; - - if (lcd.must_clear) { - lcd_clear_display(); - lcd.must_clear = false; - } - return nonseekable_open(inode, file); -} - -static int lcd_release(struct inode *inode, struct file *file) -{ - atomic_inc(&lcd_available); - return 0; -} - -static const struct file_operations lcd_fops = { - .write = lcd_write, - .open = lcd_open, - .release = lcd_release, - .llseek = no_llseek, -}; - -static struct miscdevice lcd_dev = { - .minor = LCD_MINOR, - .name = "lcd", - .fops = &lcd_fops, -}; - -/* public function usable from the kernel for any purpose */ -static void panel_lcd_print(const char *s) -{ - const char *tmp = s; - int count = strlen(s); - - if (lcd.enabled && lcd.initialized) { - for (; count-- > 0; tmp++) { - if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) - /* - * let's be a little nice with other processes - * that need some CPU - */ - schedule(); - - lcd_write_char(*tmp); - } - } -} - -/* initialize the LCD driver */ -static void lcd_init(void) -{ switch (selected_lcd_type) { case LCD_TYPE_OLD: /* parallel mode, 8 bits */ @@ -1500,10 +924,10 @@ static void lcd_init(void) lcd.pins.e = PIN_STROBE; lcd.pins.rs = PIN_AUTOLF; - lcd.width = 40; - lcd.bwidth = 40; - lcd.hwidth = 64; - lcd.height = 2; + charlcd->width = 40; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; break; case LCD_TYPE_KS0074: /* serial mode, ks0074 */ @@ -1513,10 +937,10 @@ static void lcd_init(void) lcd.pins.cl = PIN_STROBE; lcd.pins.da = PIN_D0; - lcd.width = 16; - lcd.bwidth = 40; - lcd.hwidth = 16; - lcd.height = 2; + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 16; + charlcd->height = 2; break; case LCD_TYPE_NEXCOM: /* parallel mode, 8 bits, generic */ @@ -1526,10 +950,10 @@ static void lcd_init(void) lcd.pins.rs = PIN_SELECP; lcd.pins.rw = PIN_INITP; - lcd.width = 16; - lcd.bwidth = 40; - lcd.hwidth = 64; - lcd.height = 2; + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; break; case LCD_TYPE_CUSTOM: /* customer-defined */ @@ -1545,22 +969,22 @@ static void lcd_init(void) lcd.pins.e = PIN_STROBE; lcd.pins.rs = PIN_SELECP; - lcd.width = 16; - lcd.bwidth = 40; - lcd.hwidth = 64; - lcd.height = 2; + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; break; } /* Overwrite with module params set on loading */ if (lcd_height != NOT_SET) - lcd.height = lcd_height; + charlcd->height = lcd_height; if (lcd_width != NOT_SET) - lcd.width = lcd_width; + charlcd->width = lcd_width; if (lcd_bwidth != NOT_SET) - lcd.bwidth = lcd_bwidth; + charlcd->bwidth = lcd_bwidth; if (lcd_hwidth != NOT_SET) - lcd.hwidth = lcd_hwidth; + charlcd->hwidth = lcd_hwidth; if (lcd_charset != NOT_SET) lcd.charset = lcd_charset; if (lcd_proto != NOT_SET) @@ -1579,19 +1003,17 @@ static void lcd_init(void) lcd.pins.bl = lcd_bl_pin; /* this is used to catch wrong and default values */ - if (lcd.width <= 0) - lcd.width = DEFAULT_LCD_WIDTH; - if (lcd.bwidth <= 0) - lcd.bwidth = DEFAULT_LCD_BWIDTH; - if (lcd.hwidth <= 0) - lcd.hwidth = DEFAULT_LCD_HWIDTH; - if (lcd.height <= 0) - lcd.height = DEFAULT_LCD_HEIGHT; + if (charlcd->width <= 0) + charlcd->width = DEFAULT_LCD_WIDTH; + if (charlcd->bwidth <= 0) + charlcd->bwidth = DEFAULT_LCD_BWIDTH; + if (charlcd->hwidth <= 0) + charlcd->hwidth = DEFAULT_LCD_HWIDTH; + if (charlcd->height <= 0) + charlcd->height = DEFAULT_LCD_HEIGHT; if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */ - lcd_write_cmd = lcd_write_cmd_s; - lcd_write_data = lcd_write_data_s; - lcd_clear_fast = lcd_clear_fast_s; + charlcd->ops = &charlcd_serial_ops; if (lcd.pins.cl == PIN_NOT_SET) lcd.pins.cl = DEFAULT_LCD_PIN_SCL; @@ -1599,9 +1021,7 @@ static void lcd_init(void) lcd.pins.da = DEFAULT_LCD_PIN_SDA; } else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */ - lcd_write_cmd = lcd_write_cmd_p8; - lcd_write_data = lcd_write_data_p8; - lcd_clear_fast = lcd_clear_fast_p8; + charlcd->ops = &charlcd_parallel_ops; if (lcd.pins.e == PIN_NOT_SET) lcd.pins.e = DEFAULT_LCD_PIN_E; @@ -1610,9 +1030,7 @@ static void lcd_init(void) if (lcd.pins.rw == PIN_NOT_SET) lcd.pins.rw = DEFAULT_LCD_PIN_RW; } else { - lcd_write_cmd = lcd_write_cmd_tilcd; - lcd_write_data = lcd_write_data_tilcd; - lcd_clear_fast = lcd_clear_fast_tilcd; + charlcd->ops = &charlcd_tilcd_ops; } if (lcd.pins.bl == PIN_NOT_SET) @@ -1635,14 +1053,9 @@ static void lcd_init(void) lcd.charset = DEFAULT_LCD_CHARSET; if (lcd.charset == LCD_CHARSET_KS0074) - lcd_char_conv = lcd_char_conv_ks0074; + charlcd->char_conv = lcd_char_conv_ks0074; else - lcd_char_conv = NULL; - - if (lcd.pins.bl != PIN_NONE) { - mutex_init(&lcd.bl_tempo_lock); - INIT_DELAYED_WORK(&lcd.bl_work, lcd_bl_off); - } + charlcd->char_conv = NULL; pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E], lcd_bits[LCD_PORT_C][LCD_BIT_E]); @@ -1657,25 +1070,8 @@ static void lcd_init(void) pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA], lcd_bits[LCD_PORT_C][LCD_BIT_DA]); - /* - * before this line, we must NOT send anything to the display. - * Since lcd_init_display() needs to write data, we have to - * enable mark the LCD initialized just before. - */ + lcd.charlcd = charlcd; lcd.initialized = true; - lcd_init_display(); - - /* display a short message */ -#ifdef CONFIG_PANEL_CHANGE_MESSAGE -#ifdef CONFIG_PANEL_BOOT_MESSAGE - panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); -#endif -#else - panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE); -#endif - /* clear the display on the next device opening */ - lcd.must_clear = true; - lcd_home(); } /* @@ -2011,7 +1407,7 @@ static void panel_scan_timer(void) } if (keypressed && lcd.enabled && lcd.initialized) - lcd_poke(); + charlcd_poke(lcd.charlcd); mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); } @@ -2175,35 +1571,6 @@ static void keypad_init(void) /* device initialization */ /**************************************************/ -static int panel_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (lcd.enabled && lcd.initialized) { - switch (code) { - case SYS_DOWN: - panel_lcd_print - ("\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); - break; - case SYS_HALT: - panel_lcd_print - ("\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); - break; - case SYS_POWER_OFF: - panel_lcd_print("\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); - break; - default: - break; - } - } - return NOTIFY_DONE; -} - -static struct notifier_block panel_notifier = { - panel_notify_sys, - NULL, - 0 -}; - static void panel_attach(struct parport *port) { struct pardev_cb panel_cb; @@ -2239,7 +1606,7 @@ static void panel_attach(struct parport *port) */ if (lcd.enabled) { lcd_init(); - if (misc_register(&lcd_dev)) + if (!lcd.charlcd || charlcd_register(lcd.charlcd)) goto err_unreg_device; } @@ -2248,13 +1615,14 @@ static void panel_attach(struct parport *port) if (misc_register(&keypad_dev)) goto err_lcd_unreg; } - register_reboot_notifier(&panel_notifier); return; err_lcd_unreg: if (lcd.enabled) - misc_deregister(&lcd_dev); + charlcd_unregister(lcd.charlcd); err_unreg_device: + kfree(lcd.charlcd); + lcd.charlcd = NULL; parport_unregister_device(pprt); pprt = NULL; } @@ -2278,20 +1646,16 @@ static void panel_detach(struct parport *port) } if (lcd.enabled) { - panel_lcd_print("\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); - misc_deregister(&lcd_dev); - if (lcd.pins.bl != PIN_NONE) { - cancel_delayed_work_sync(&lcd.bl_work); - __lcd_backlight(0); - } + charlcd_unregister(lcd.charlcd); lcd.initialized = false; + kfree(lcd.charlcd); + lcd.charlcd = NULL; } /* TODO: free all input signals */ parport_release(pprt); parport_unregister_device(pprt); pprt = NULL; - unregister_reboot_notifier(&panel_notifier); } static struct parport_driver panel_driver = { @@ -2369,10 +1733,6 @@ static int __init panel_init_module(void) * Init lcd struct with load-time values to preserve exact * current functionality (at least for now). */ - lcd.height = lcd_height; - lcd.width = lcd_width; - lcd.bwidth = lcd_bwidth; - lcd.hwidth = lcd_hwidth; lcd.charset = lcd_charset; lcd.proto = lcd_proto; lcd.pins.e = lcd_e_pin; @@ -2381,9 +1741,6 @@ static int __init panel_init_module(void) lcd.pins.cl = lcd_cl_pin; lcd.pins.da = lcd_da_pin; lcd.pins.bl = lcd_bl_pin; - - /* Leave it for now, just in case */ - lcd.esc_seq.len = -1; } switch (selected_keypad_type) { diff --git a/include/misc/charlcd.h b/include/misc/charlcd.h new file mode 100644 index 000000000000..c40047b673c9 --- /dev/null +++ b/include/misc/charlcd.h @@ -0,0 +1,40 @@ +/* + * Character LCD driver for Linux + * + * Copyright (C) 2000-2008, Willy Tarreau + * Copyright (C) 2016-2017 Glider bvba + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +struct charlcd { + const struct charlcd_ops *ops; + const unsigned char *char_conv; /* Optional */ + + int height; + int width; + int bwidth; /* Default set by charlcd_alloc() */ + int hwidth; /* Default set by charlcd_alloc() */ + + void *drvdata; /* Set by charlcd_alloc() */ +}; + +struct charlcd_ops { + /* Required */ + void (*write_cmd)(struct charlcd *lcd, int cmd); + void (*write_data)(struct charlcd *lcd, int data); + + /* Optional */ + void (*clear_fast)(struct charlcd *lcd); + void (*backlight)(struct charlcd *lcd, int on); +}; + +struct charlcd *charlcd_alloc(unsigned int drvdata_size); + +int charlcd_register(struct charlcd *lcd); +int charlcd_unregister(struct charlcd *lcd); + +void charlcd_poke(struct charlcd *lcd); -- cgit v1.2.3-70-g09d2 From ac201479cc695cb0140e425b9ca8ab2ecdcd2f0d Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 10 Mar 2017 15:15:18 +0100 Subject: auxdisplay: charlcd: Add support for 4-bit interfaces In 4-bit mode, 8-bit commands and data are written using two raw writes to the data interface: high nibble first, low nibble last. This must be handled by the low-level driver. However, as we don't know in which mode (4-bit or 8-bit) nor 4-bit phase the LCD was left, initialization must always be handled using raw writes, and needs to configure the LCD for 8-bit mode first. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/charlcd.c | 36 ++++++++++++++++++++++++++++++------ include/misc/charlcd.h | 2 ++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c index 930ffb2fb317..b3a84e90a268 100644 --- a/drivers/auxdisplay/charlcd.c +++ b/drivers/auxdisplay/charlcd.c @@ -223,24 +223,46 @@ static void charlcd_clear_display(struct charlcd *lcd) static int charlcd_init_display(struct charlcd *lcd) { + void (*write_cmd_raw)(struct charlcd *lcd, int cmd); struct charlcd_priv *priv = to_priv(lcd); + u8 init; + + if (lcd->ifwidth != 4 && lcd->ifwidth != 8) + return -EINVAL; priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B; long_sleep(20); /* wait 20 ms after power-up for the paranoid */ - /* 8bits, 1 line, small fonts; let's do it 3 times */ - lcd->ops->write_cmd(lcd, LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); + /* + * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure + * the LCD is in 8-bit mode afterwards + */ + init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; + if (lcd->ifwidth == 4) { + init >>= 4; + write_cmd_raw = lcd->ops->write_cmd_raw4; + } else { + write_cmd_raw = lcd->ops->write_cmd; + } + write_cmd_raw(lcd, init); long_sleep(10); - lcd->ops->write_cmd(lcd, LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); + write_cmd_raw(lcd, init); long_sleep(10); - lcd->ops->write_cmd(lcd, LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS); + write_cmd_raw(lcd, init); long_sleep(10); + if (lcd->ifwidth == 4) { + /* Switch to 4-bit mode, 1 line, small fonts */ + lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4); + long_sleep(10); + } + /* set font height and lines number */ lcd->ops->write_cmd(lcd, - LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS | + LCD_CMD_FUNCTION_SET | + ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); long_sleep(10); @@ -482,7 +504,8 @@ static inline int handle_lcd_special_code(struct charlcd *lcd) /* check whether one of F,N flags was changed */ else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) lcd->ops->write_cmd(lcd, - LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS | + LCD_CMD_FUNCTION_SET | + ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); /* check whether L flag was changed */ @@ -716,6 +739,7 @@ struct charlcd *charlcd_alloc(unsigned int drvdata_size) priv->esc_seq.len = -1; lcd = &priv->lcd; + lcd->ifwidth = 8; lcd->bwidth = DEFAULT_LCD_BWIDTH; lcd->hwidth = DEFAULT_LCD_HWIDTH; lcd->drvdata = priv->drvdata; diff --git a/include/misc/charlcd.h b/include/misc/charlcd.h index c40047b673c9..23f61850f363 100644 --- a/include/misc/charlcd.h +++ b/include/misc/charlcd.h @@ -14,6 +14,7 @@ struct charlcd { const struct charlcd_ops *ops; const unsigned char *char_conv; /* Optional */ + int ifwidth; /* 4-bit or 8-bit (default) */ int height; int width; int bwidth; /* Default set by charlcd_alloc() */ @@ -28,6 +29,7 @@ struct charlcd_ops { void (*write_data)(struct charlcd *lcd, int data); /* Optional */ + void (*write_cmd_raw4)(struct charlcd *lcd, int cmd); /* 4-bit only */ void (*clear_fast)(struct charlcd *lcd); void (*backlight)(struct charlcd *lcd, int on); }; -- cgit v1.2.3-70-g09d2 From 1d3b2af213902059d9f4b35eb15b53f8062dc3b3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 10 Mar 2017 15:15:19 +0100 Subject: auxdisplay: charlcd: Add support for displays with more than two lines On displays with more than two lines, the additional lines are stored in the buffers used for the first two lines, but beyond the visible parts. Adjust the DDRAM address calculation to cater for this. When clearing the display, avoid writing more spaces than the actual size of the physical buffer. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/charlcd.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c index b3a84e90a268..cfeb049a01ef 100644 --- a/drivers/auxdisplay/charlcd.c +++ b/drivers/auxdisplay/charlcd.c @@ -159,15 +159,19 @@ EXPORT_SYMBOL_GPL(charlcd_poke); static void charlcd_gotoxy(struct charlcd *lcd) { struct charlcd_priv *priv = to_priv(lcd); + unsigned int addr; - lcd->ops->write_cmd(lcd, - LCD_CMD_SET_DDRAM_ADDR | (priv->addr.y ? lcd->hwidth : 0) | - /* - * we force the cursor to stay at the end of the - * line if it wants to go farther - */ - ((priv->addr.x < lcd->bwidth) ? priv->addr.x & (lcd->hwidth - 1) - : lcd->bwidth - 1)); + /* + * we force the cursor to stay at the end of the + * line if it wants to go farther + */ + addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1) + : lcd->bwidth - 1; + if (priv->addr.y & 1) + addr += lcd->hwidth; + if (priv->addr.y & 2) + addr += lcd->bwidth; + lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr); } static void charlcd_home(struct charlcd *lcd) @@ -203,7 +207,7 @@ static void charlcd_clear_fast(struct charlcd *lcd) if (lcd->ops->clear_fast) lcd->ops->clear_fast(lcd); else - for (pos = 0; pos < lcd->height * lcd->hwidth; pos++) + for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++) lcd->ops->write_data(lcd, ' '); charlcd_home(lcd); -- cgit v1.2.3-70-g09d2 From dd9502a9e9156dd854dd0bec0dd385b4662bab3e Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 10 Mar 2017 15:15:20 +0100 Subject: dt-bindings: auxdisplay: Add bindings for Hitachi HD44780 Add DT bindings for an Hitachi HD44780 Character LCD Controller where its M6800 bus interface is connected to GPIOs. Memory-mapped configurations are not yet supported. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/auxdisplay/hit,hd44780.txt | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt diff --git a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt new file mode 100644 index 000000000000..ee4054da458d --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt @@ -0,0 +1,44 @@ +DT bindings for the Hitachi HD44780 Character LCD Controller + +The Hitachi HD44780 Character LCD Controller is commonly used on character LCDs +that can display one or more lines of text. It exposes an M6800 bus interface, +which can be used in either 4-bit or 8-bit mode. + +Required properties: + - compatible: Must contain "hit,hd44780", + - data-gpios: Must contain an array of either 4 or 8 GPIO specifiers, + referring to the GPIO pins connected to the data signal lines DB0-DB7 + (8-bit mode) or DB4-DB7 (4-bit mode) of the LCD Controller's bus interface, + - enable-gpios: Must contain a GPIO specifier, referring to the GPIO pin + connected to the "E" (Enable) signal line of the LCD Controller's bus + interface, + - rs-gpios: Must contain a GPIO specifier, referring to the GPIO pin + connected to the "RS" (Register Select) signal line of the LCD Controller's + bus interface, + - display-height: Height of the display, in character cells, + - display-width: Width of the display, in character cells. + +Optional properties: + - rw-gpios: Must contain a GPIO specifier, referring to the GPIO pin + connected to the "RW" (Read/Write) signal line of the LCD Controller's bus + interface, + - backlight-gpios: Must contain a GPIO specifier, referring to the GPIO pin + used for enabling the LCD's backlight, + - internal-buffer-width: Internal buffer width (default is 40 for displays + with 1 or 2 lines, and display-width for displays with more than 2 lines). + +Example: + + auxdisplay { + compatible = "hit,hd44780"; + + data-gpios = <&hc595 0 GPIO_ACTIVE_HIGH>, + <&hc595 1 GPIO_ACTIVE_HIGH>, + <&hc595 2 GPIO_ACTIVE_HIGH>, + <&hc595 3 GPIO_ACTIVE_HIGH>; + enable-gpios = <&hc595 4 GPIO_ACTIVE_HIGH>; + rs-gpios = <&hc595 5 GPIO_ACTIVE_HIGH>; + + display-height = <2>; + display-width = <16>; + }; -- cgit v1.2.3-70-g09d2 From d47d88361feea2ce11f39bd70467ffc19a61d2d3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 10 Mar 2017 15:15:21 +0100 Subject: auxdisplay: Add HD44780 Character LCD support The Hitachi HD44780 Character LCD Controller is commonly used on character LCDs that can display one or more lines of text. This driver supports character LCDs connected to GPIOs, using either a 4-bit or 8-bit data bus, and provides access through the charlcd core and /dev/lcd. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/Kconfig | 11 ++ drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/hd44780.c | 325 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 drivers/auxdisplay/hd44780.c diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index b4686380d4e3..c317dc515617 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -18,6 +18,17 @@ config CHARLCD if AUXDISPLAY +config HD44780 + tristate "HD44780 Character LCD support" + depends on GPIOLIB || COMPILE_TEST + select CHARLCD + ---help--- + Enable support for Character LCDs using a HD44780 controller. + The LCD is accessible through the /dev/lcd char device (10, 156). + This code can either be compiled as a module, or linked into the + kernel and started at boot. + If you don't understand what all this is about, say N. + config KS0108 tristate "KS0108 LCD Controller" depends on PARPORT_PC diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index f56f45dcc78e..fcefa686be0f 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_CHARLCD) += charlcd.o obj-$(CONFIG_KS0108) += ks0108.o obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o +obj-$(CONFIG_HD44780) += hd44780.o obj-$(CONFIG_HT16K33) += ht16k33.o diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c new file mode 100644 index 000000000000..1665ac6ef9ff --- /dev/null +++ b/drivers/auxdisplay/hd44780.c @@ -0,0 +1,325 @@ +/* + * HD44780 Character LCD driver for Linux + * + * Copyright (C) 2000-2008, Willy Tarreau + * Copyright (C) 2016-2017 Glider bvba + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include + + +enum hd44780_pin { + /* Order does matter due to writing to GPIO array subsets! */ + PIN_DATA0, /* Optional */ + PIN_DATA1, /* Optional */ + PIN_DATA2, /* Optional */ + PIN_DATA3, /* Optional */ + PIN_DATA4, + PIN_DATA5, + PIN_DATA6, + PIN_DATA7, + PIN_CTRL_RS, + PIN_CTRL_RW, /* Optional */ + PIN_CTRL_E, + PIN_CTRL_BL, /* Optional */ + PIN_NUM +}; + +struct hd44780 { + struct gpio_desc *pins[PIN_NUM]; +}; + +static void hd44780_backlight(struct charlcd *lcd, int on) +{ + struct hd44780 *hd = lcd->drvdata; + + if (hd->pins[PIN_CTRL_BL]) + gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on); +} + +static void hd44780_strobe_gpio(struct hd44780 *hd) +{ + /* Maintain the data during 20 us before the strobe */ + udelay(20); + + gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1); + + /* Maintain the strobe during 40 us */ + udelay(40); + + gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0); +} + +/* write to an LCD panel register in 8 bit GPIO mode */ +static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs) +{ + int values[10]; /* for DATA[0-7], RS, RW */ + unsigned int i, n; + + for (i = 0; i < 8; i++) + values[PIN_DATA0 + i] = !!(val & BIT(i)); + values[PIN_CTRL_RS] = rs; + n = 9; + if (hd->pins[PIN_CTRL_RW]) { + values[PIN_CTRL_RW] = 0; + n++; + } + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], values); + + hd44780_strobe_gpio(hd); +} + +/* write to an LCD panel register in 4 bit GPIO mode */ +static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs) +{ + int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */ + unsigned int i, n; + + /* High nibble + RS, RW */ + for (i = 4; i < 8; i++) + values[PIN_DATA0 + i] = !!(val & BIT(i)); + values[PIN_CTRL_RS] = rs; + n = 5; + if (hd->pins[PIN_CTRL_RW]) { + values[PIN_CTRL_RW] = 0; + n++; + } + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], + &values[PIN_DATA4]); + + hd44780_strobe_gpio(hd); + + /* Low nibble */ + for (i = 0; i < 4; i++) + values[PIN_DATA4 + i] = !!(val & BIT(i)); + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], + &values[PIN_DATA4]); + + hd44780_strobe_gpio(hd); +} + +/* Send a command to the LCD panel in 8 bit GPIO mode */ +static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio8(hd, cmd, 0); + + /* The shortest command takes at least 120 us */ + udelay(120); +} + +/* Send data to the LCD panel in 8 bit GPIO mode */ +static void hd44780_write_data_gpio8(struct charlcd *lcd, int data) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio8(hd, data, 1); + + /* The shortest data takes at least 45 us */ + udelay(45); +} + +static const struct charlcd_ops hd44780_ops_gpio8 = { + .write_cmd = hd44780_write_cmd_gpio8, + .write_data = hd44780_write_data_gpio8, + .backlight = hd44780_backlight, +}; + +/* Send a command to the LCD panel in 4 bit GPIO mode */ +static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio4(hd, cmd, 0); + + /* The shortest command takes at least 120 us */ + udelay(120); +} + +/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */ +static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd) +{ + int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */ + struct hd44780 *hd = lcd->drvdata; + unsigned int i, n; + + /* Command nibble + RS, RW */ + for (i = 0; i < 4; i++) + values[PIN_DATA4 + i] = !!(cmd & BIT(i)); + values[PIN_CTRL_RS] = 0; + n = 5; + if (hd->pins[PIN_CTRL_RW]) { + values[PIN_CTRL_RW] = 0; + n++; + } + + /* Present the data to the port */ + gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4], + &values[PIN_DATA4]); + + hd44780_strobe_gpio(hd); +} + +/* Send data to the LCD panel in 4 bit GPIO mode */ +static void hd44780_write_data_gpio4(struct charlcd *lcd, int data) +{ + struct hd44780 *hd = lcd->drvdata; + + hd44780_write_gpio4(hd, data, 1); + + /* The shortest data takes at least 45 us */ + udelay(45); +} + +static const struct charlcd_ops hd44780_ops_gpio4 = { + .write_cmd = hd44780_write_cmd_gpio4, + .write_cmd_raw4 = hd44780_write_cmd_raw_gpio4, + .write_data = hd44780_write_data_gpio4, + .backlight = hd44780_backlight, +}; + +static int hd44780_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned int i, base; + struct charlcd *lcd; + struct hd44780 *hd; + int ifwidth, ret; + + /* Required pins */ + ifwidth = gpiod_count(dev, "data"); + if (ifwidth < 0) + return ifwidth; + + switch (ifwidth) { + case 4: + base = PIN_DATA4; + break; + case 8: + base = PIN_DATA0; + break; + default: + return -EINVAL; + } + + lcd = charlcd_alloc(sizeof(struct hd44780)); + if (!lcd) + return -ENOMEM; + + hd = lcd->drvdata; + + for (i = 0; i < ifwidth; i++) { + hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i, + GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[base + i])) { + ret = PTR_ERR(hd->pins[base + i]); + goto fail; + } + } + + hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[PIN_CTRL_E])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_E]); + goto fail; + } + + hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH); + if (IS_ERR(hd->pins[PIN_CTRL_RS])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_RS]); + goto fail; + } + + /* Optional pins */ + hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw", + GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[PIN_CTRL_RW])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_RW]); + goto fail; + } + + hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight", + GPIOD_OUT_LOW); + if (IS_ERR(hd->pins[PIN_CTRL_BL])) { + ret = PTR_ERR(hd->pins[PIN_CTRL_BL]); + goto fail; + } + + /* Required properties */ + ret = device_property_read_u32(dev, "display-height", &lcd->height); + if (ret) + goto fail; + ret = device_property_read_u32(dev, "display-width", &lcd->width); + if (ret) + goto fail; + + /* + * On displays with more than two rows, the internal buffer width is + * usually equal to the display width + */ + if (lcd->height > 2) + lcd->bwidth = lcd->width; + + /* Optional properties */ + device_property_read_u32(dev, "internal-buffer-width", &lcd->bwidth); + + lcd->ifwidth = ifwidth; + lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4; + + ret = charlcd_register(lcd); + if (ret) + goto fail; + + platform_set_drvdata(pdev, lcd); + return 0; + +fail: + kfree(lcd); + return ret; +} + +static int hd44780_remove(struct platform_device *pdev) +{ + struct charlcd *lcd = platform_get_drvdata(pdev); + + charlcd_unregister(lcd); + return 0; +} + +static const struct of_device_id hd44780_of_match[] = { + { .compatible = "hit,hd44780" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hd44780_of_match); + +static struct platform_driver hd44780_driver = { + .probe = hd44780_probe, + .remove = hd44780_remove, + .driver = { + .name = "hd44780", + .of_match_table = hd44780_of_match, + }, +}; + +module_platform_driver(hd44780_driver); +MODULE_DESCRIPTION("HD44780 Character LCD driver"); +MODULE_AUTHOR("Geert Uytterhoeven "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From 233ed09d7fdacf592ee91e6c97ce5f4364fbe7c0 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:08 -0600 Subject: chardev: add helper function to register char devs with a struct device Credit for this patch goes is shared with Dan Williams [1]. I've taken things one step further to make the helper function more useful and clean up calling code. There's a common pattern in the kernel whereby a struct cdev is placed in a structure along side a struct device which manages the life-cycle of both. In the naive approach, the reference counting is broken and the struct device can free everything before the chardev code is entirely released. Many developers have solved this problem by linking the internal kobjs in this fashion: cdev.kobj.parent = &parent_dev.kobj; The cdev code explicitly gets and puts a reference to it's kobj parent. So this seems like it was intended to be used this way. Dmitrty Torokhov first put this in place in 2012 with this commit: 2f0157f char_dev: pin parent kobject and the first instance of the fix was then done in the input subsystem in the following commit: 4a215aa Input: fix use-after-free introduced with dynamic minor changes Subsequently over the years, however, this issue seems to have tripped up multiple developers independently. For example, see these commits: 0d5b7da iio: Prevent race between IIO chardev opening and IIO device (by Lars-Peter Clausen in 2013) ba0ef85 tpm: Fix initialization of the cdev (by Jason Gunthorpe in 2015) 5b28dde [media] media: fix use-after-free in cdev_put() when app exits after driver unbind (by Shauh Khan in 2016) This technique is similarly done in at least 15 places within the kernel and probably should have been done so in another, at least, 5 places. The kobj line also looks very suspect in that one would not expect drivers to have to mess with kobject internals in this way. Even highly experienced kernel developers can be surprised by this code, as seen in [2]. To help alleviate this situation, and hopefully prevent future wasted effort on this problem, this patch introduces a helper function to register a char device along with its parent struct device. This creates a more regular API for tying a char device to its parent without the developer having to set members in the underlying kobject. This patch introduce cdev_device_add and cdev_device_del which replaces a common pattern including setting the kobj parent, calling cdev_add and then calling device_add. It also introduces cdev_set_parent for the few cases that set the kobject parent without using device_add. [1] https://lkml.org/lkml/2017/2/13/700 [2] https://lkml.org/lkml/2017/2/10/370 Signed-off-by: Logan Gunthorpe Signed-off-by: Dan Williams Reviewed-by: Hans Verkuil Reviewed-by: Alexandre Belloni Signed-off-by: Greg Kroah-Hartman --- fs/char_dev.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/cdev.h | 5 +++ 2 files changed, 91 insertions(+) diff --git a/fs/char_dev.c b/fs/char_dev.c index 44a240c4bb65..fb8507f521b2 100644 --- a/fs/char_dev.c +++ b/fs/char_dev.c @@ -471,6 +471,85 @@ int cdev_add(struct cdev *p, dev_t dev, unsigned count) return 0; } +/** + * cdev_set_parent() - set the parent kobject for a char device + * @p: the cdev structure + * @kobj: the kobject to take a reference to + * + * cdev_set_parent() sets a parent kobject which will be referenced + * appropriately so the parent is not freed before the cdev. This + * should be called before cdev_add. + */ +void cdev_set_parent(struct cdev *p, struct kobject *kobj) +{ + WARN_ON(!kobj->state_initialized); + p->kobj.parent = kobj; +} + +/** + * cdev_device_add() - add a char device and it's corresponding + * struct device, linkink + * @dev: the device structure + * @cdev: the cdev structure + * + * cdev_device_add() adds the char device represented by @cdev to the system, + * just as cdev_add does. It then adds @dev to the system using device_add + * The dev_t for the char device will be taken from the struct device which + * needs to be initialized first. This helper function correctly takes a + * reference to the parent device so the parent will not get released until + * all references to the cdev are released. + * + * This helper uses dev->devt for the device number. If it is not set + * it will not add the cdev and it will be equivalent to device_add. + * + * This function should be used whenever the struct cdev and the + * struct device are members of the same structure whose lifetime is + * managed by the struct device. + * + * NOTE: Callers must assume that userspace was able to open the cdev and + * can call cdev fops callbacks at any time, even if this function fails. + */ +int cdev_device_add(struct cdev *cdev, struct device *dev) +{ + int rc = 0; + + if (dev->devt) { + cdev_set_parent(cdev, &dev->kobj); + + rc = cdev_add(cdev, dev->devt, 1); + if (rc) + return rc; + } + + rc = device_add(dev); + if (rc) + cdev_del(cdev); + + return rc; +} + +/** + * cdev_device_del() - inverse of cdev_device_add + * @dev: the device structure + * @cdev: the cdev structure + * + * cdev_device_del() is a helper function to call cdev_del and device_del. + * It should be used whenever cdev_device_add is used. + * + * If dev->devt is not set it will not remove the cdev and will be equivalent + * to device_del. + * + * NOTE: This guarantees that associated sysfs callbacks are not running + * or runnable, however any cdevs already open will remain and their fops + * will still be callable even after this function returns. + */ +void cdev_device_del(struct cdev *cdev, struct device *dev) +{ + device_del(dev); + if (dev->devt) + cdev_del(cdev); +} + static void cdev_unmap(dev_t dev, unsigned count) { kobj_unmap(cdev_map, dev, count); @@ -482,6 +561,10 @@ static void cdev_unmap(dev_t dev, unsigned count) * * cdev_del() removes @p from the system, possibly freeing the structure * itself. + * + * NOTE: This guarantees that cdev device will no longer be able to be + * opened, however any cdevs already open will remain and their fops will + * still be callable even after cdev_del returns. */ void cdev_del(struct cdev *p) { @@ -570,5 +653,8 @@ EXPORT_SYMBOL(cdev_init); EXPORT_SYMBOL(cdev_alloc); EXPORT_SYMBOL(cdev_del); EXPORT_SYMBOL(cdev_add); +EXPORT_SYMBOL(cdev_set_parent); +EXPORT_SYMBOL(cdev_device_add); +EXPORT_SYMBOL(cdev_device_del); EXPORT_SYMBOL(__register_chrdev); EXPORT_SYMBOL(__unregister_chrdev); diff --git a/include/linux/cdev.h b/include/linux/cdev.h index f8763615a5f2..408bc09ce497 100644 --- a/include/linux/cdev.h +++ b/include/linux/cdev.h @@ -4,6 +4,7 @@ #include #include #include +#include struct file_operations; struct inode; @@ -26,6 +27,10 @@ void cdev_put(struct cdev *p); int cdev_add(struct cdev *, dev_t, unsigned); +void cdev_set_parent(struct cdev *p, struct kobject *kobj); +int cdev_device_add(struct cdev *cdev, struct device *dev); +void cdev_device_del(struct cdev *cdev, struct device *dev); + void cdev_del(struct cdev *); void cd_forget(struct inode *); -- cgit v1.2.3-70-g09d2 From ed01e50acdd3e4a640cf9ebd28a7e810c3ceca97 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 17 Mar 2017 12:48:09 -0600 Subject: device-dax: fix cdev leak If device_add() fails, cleanup the cdev. Otherwise, we leak a kobj_map() with a stale device number. As Jason points out, there is a small possibility that userspace has opened and mapped the device in the time between cdev_add() and the device_add() failure. We need a new kill_dax_dev() helper to invalidate any established mappings. Fixes: ba09c01d2fa8 ("dax: convert to the cdev api") Cc: Reported-by: Jason Gunthorpe Signed-off-by: Dan Williams Signed-off-by: Logan Gunthorpe Reviewed-by: Johannes Thumshirn Signed-off-by: Greg Kroah-Hartman --- drivers/dax/dax.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/dax/dax.c b/drivers/dax/dax.c index 8d9829ff2a78..a5ed59a5a968 100644 --- a/drivers/dax/dax.c +++ b/drivers/dax/dax.c @@ -675,13 +675,10 @@ static void dax_dev_release(struct device *dev) kfree(dax_dev); } -static void unregister_dax_dev(void *dev) +static void kill_dax_dev(struct dax_dev *dax_dev) { - struct dax_dev *dax_dev = to_dax_dev(dev); struct cdev *cdev = &dax_dev->cdev; - dev_dbg(dev, "%s\n", __func__); - /* * Note, rcu is not protecting the liveness of dax_dev, rcu is * ensuring that any fault handlers that might have seen @@ -693,6 +690,15 @@ static void unregister_dax_dev(void *dev) synchronize_rcu(); unmap_mapping_range(dax_dev->inode->i_mapping, 0, 0, 1); cdev_del(cdev); +} + +static void unregister_dax_dev(void *dev) +{ + struct dax_dev *dax_dev = to_dax_dev(dev); + + dev_dbg(dev, "%s\n", __func__); + + kill_dax_dev(dax_dev); device_unregister(dev); } @@ -769,6 +775,7 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, dev_set_name(dev, "dax%d.%d", dax_region->id, dax_dev->id); rc = device_add(dev); if (rc) { + kill_dax_dev(dax_dev); put_device(dev); return ERR_PTR(rc); } -- cgit v1.2.3-70-g09d2 From 92a3fa075d3d2716b9c662814540f1d660cbf2f5 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:10 -0600 Subject: device-dax: utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. Signed-off-by: Logan Gunthorpe Reviewed-by: Dan Williams Reviewed-by: Johannes Thumshirn Signed-off-by: Greg Kroah-Hartman --- drivers/dax/dax.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/dax/dax.c b/drivers/dax/dax.c index a5ed59a5a968..e5f85550dc25 100644 --- a/drivers/dax/dax.c +++ b/drivers/dax/dax.c @@ -677,8 +677,6 @@ static void dax_dev_release(struct device *dev) static void kill_dax_dev(struct dax_dev *dax_dev) { - struct cdev *cdev = &dax_dev->cdev; - /* * Note, rcu is not protecting the liveness of dax_dev, rcu is * ensuring that any fault handlers that might have seen @@ -689,7 +687,6 @@ static void kill_dax_dev(struct dax_dev *dax_dev) dax_dev->alive = false; synchronize_rcu(); unmap_mapping_range(dax_dev->inode->i_mapping, 0, 0, 1); - cdev_del(cdev); } static void unregister_dax_dev(void *dev) @@ -699,7 +696,8 @@ static void unregister_dax_dev(void *dev) dev_dbg(dev, "%s\n", __func__); kill_dax_dev(dax_dev); - device_unregister(dev); + cdev_device_del(&dax_dev->cdev, dev); + put_device(dev); } struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, @@ -750,18 +748,13 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, goto err_inode; } - /* device_initialize() so cdev can reference kobj parent */ + /* from here on we're committed to teardown via dax_dev_release() */ device_initialize(dev); cdev = &dax_dev->cdev; cdev_init(cdev, &dax_fops); cdev->owner = parent->driver->owner; - cdev->kobj.parent = &dev->kobj; - rc = cdev_add(&dax_dev->cdev, dev_t, 1); - if (rc) - goto err_cdev; - /* from here on we're committed to teardown via dax_dev_release() */ dax_dev->num_resources = count; dax_dev->alive = true; dax_dev->region = dax_region; @@ -773,7 +766,8 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, dev->groups = dax_attribute_groups; dev->release = dax_dev_release; dev_set_name(dev, "dax%d.%d", dax_region->id, dax_dev->id); - rc = device_add(dev); + + rc = cdev_device_add(cdev, dev); if (rc) { kill_dax_dev(dax_dev); put_device(dev); @@ -786,8 +780,6 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region, return dax_dev; - err_cdev: - iput(dax_dev->inode); err_inode: ida_simple_remove(&dax_minor_ida, minor); err_minor: -- cgit v1.2.3-70-g09d2 From 358a89ca2c9680ceddc219ab25d1583619e9159f Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:11 -0600 Subject: input: utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper in evdev, joydev and mousedev. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. Signed-off-by: Logan Gunthorpe Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/input/evdev.c | 11 ++--------- drivers/input/joydev.c | 11 ++--------- drivers/input/mousedev.c | 11 ++--------- 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index e9ae3d500a55..925571475005 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -1354,8 +1354,6 @@ static void evdev_cleanup(struct evdev *evdev) evdev_mark_dead(evdev); evdev_hangup(evdev); - cdev_del(&evdev->cdev); - /* evdev is marked dead so no one else accesses evdev->open */ if (evdev->open) { input_flush_device(handle, NULL); @@ -1416,12 +1414,8 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, goto err_free_evdev; cdev_init(&evdev->cdev, &evdev_fops); - evdev->cdev.kobj.parent = &evdev->dev.kobj; - error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); - if (error) - goto err_unregister_handle; - error = device_add(&evdev->dev); + error = cdev_device_add(&evdev->cdev, &evdev->dev); if (error) goto err_cleanup_evdev; @@ -1429,7 +1423,6 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, err_cleanup_evdev: evdev_cleanup(evdev); - err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); @@ -1442,7 +1435,7 @@ static void evdev_disconnect(struct input_handle *handle) { struct evdev *evdev = handle->private; - device_del(&evdev->dev); + cdev_device_del(&evdev->cdev, &evdev->dev); evdev_cleanup(evdev); input_free_minor(MINOR(evdev->dev.devt)); input_unregister_handle(handle); diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c index 065e67bf56dd..29d677c714d2 100644 --- a/drivers/input/joydev.c +++ b/drivers/input/joydev.c @@ -742,8 +742,6 @@ static void joydev_cleanup(struct joydev *joydev) joydev_mark_dead(joydev); joydev_hangup(joydev); - cdev_del(&joydev->cdev); - /* joydev is marked dead so no one else accesses joydev->open */ if (joydev->open) input_close_device(handle); @@ -913,12 +911,8 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, goto err_free_joydev; cdev_init(&joydev->cdev, &joydev_fops); - joydev->cdev.kobj.parent = &joydev->dev.kobj; - error = cdev_add(&joydev->cdev, joydev->dev.devt, 1); - if (error) - goto err_unregister_handle; - error = device_add(&joydev->dev); + error = cdev_device_add(&joydev->cdev, &joydev->dev); if (error) goto err_cleanup_joydev; @@ -926,7 +920,6 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, err_cleanup_joydev: joydev_cleanup(joydev); - err_unregister_handle: input_unregister_handle(&joydev->handle); err_free_joydev: put_device(&joydev->dev); @@ -939,7 +932,7 @@ static void joydev_disconnect(struct input_handle *handle) { struct joydev *joydev = handle->private; - device_del(&joydev->dev); + cdev_device_del(&joydev->cdev, &joydev->dev); joydev_cleanup(joydev); input_free_minor(MINOR(joydev->dev.devt)); input_unregister_handle(handle); diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c index b604564dec5c..0e0ff84088fd 100644 --- a/drivers/input/mousedev.c +++ b/drivers/input/mousedev.c @@ -812,8 +812,6 @@ static void mousedev_cleanup(struct mousedev *mousedev) mousedev_mark_dead(mousedev); mousedev_hangup(mousedev); - cdev_del(&mousedev->cdev); - /* mousedev is marked dead so no one else accesses mousedev->open */ if (mousedev->open) input_close_device(handle); @@ -901,12 +899,8 @@ static struct mousedev *mousedev_create(struct input_dev *dev, } cdev_init(&mousedev->cdev, &mousedev_fops); - mousedev->cdev.kobj.parent = &mousedev->dev.kobj; - error = cdev_add(&mousedev->cdev, mousedev->dev.devt, 1); - if (error) - goto err_unregister_handle; - error = device_add(&mousedev->dev); + error = cdev_device_add(&mousedev->cdev, &mousedev->dev); if (error) goto err_cleanup_mousedev; @@ -914,7 +908,6 @@ static struct mousedev *mousedev_create(struct input_dev *dev, err_cleanup_mousedev: mousedev_cleanup(mousedev); - err_unregister_handle: if (!mixdev) input_unregister_handle(&mousedev->handle); err_free_mousedev: @@ -927,7 +920,7 @@ static struct mousedev *mousedev_create(struct input_dev *dev, static void mousedev_destroy(struct mousedev *mousedev) { - device_del(&mousedev->dev); + cdev_device_del(&mousedev->cdev, &mousedev->dev); mousedev_cleanup(mousedev); input_free_minor(MINOR(mousedev->dev.devt)); if (mousedev != mousedev_mix) -- cgit v1.2.3-70-g09d2 From 111379dcccebc3eaf50ccf7daeb7d72210c1314c Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:12 -0600 Subject: gpiolib: utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. Signed-off-by: Logan Gunthorpe Reviewed-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 8b4d721d6d63..3ce2a27ee78f 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1035,18 +1035,14 @@ static int gpiochip_setup_dev(struct gpio_device *gdev) cdev_init(&gdev->chrdev, &gpio_fileops); gdev->chrdev.owner = THIS_MODULE; - gdev->chrdev.kobj.parent = &gdev->dev.kobj; gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id); - status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1); - if (status < 0) - chip_warn(gdev->chip, "failed to add char device %d:%d\n", - MAJOR(gpio_devt), gdev->id); - else - chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n", - MAJOR(gpio_devt), gdev->id); - status = device_add(&gdev->dev); + + status = cdev_device_add(&gdev->chrdev, &gdev->dev); if (status) - goto err_remove_chardev; + return status; + + chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n", + MAJOR(gpio_devt), gdev->id); status = gpiochip_sysfs_register(gdev); if (status) @@ -1061,9 +1057,7 @@ static int gpiochip_setup_dev(struct gpio_device *gdev) return 0; err_remove_device: - device_del(&gdev->dev); -err_remove_chardev: - cdev_del(&gdev->chrdev); + cdev_device_del(&gdev->chrdev, &gdev->dev); return status; } @@ -1347,8 +1341,7 @@ void gpiochip_remove(struct gpio_chip *chip) * be removed, else it will be dangling until the last user is * gone. */ - cdev_del(&gdev->chrdev); - device_del(&gdev->dev); + cdev_device_del(&gdev->chrdev, &gdev->dev); put_device(&gdev->dev); } EXPORT_SYMBOL_GPL(gpiochip_remove); -- cgit v1.2.3-70-g09d2 From 8dbbf582518190b22681c0fa0d9a129b047138a2 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:13 -0600 Subject: tpm-chip: utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. Signed-off-by: Logan Gunthorpe Reviewed-by: Jason Gunthorpe Reviewed-by: Jarkko Sakkinen Signed-off-by: Greg Kroah-Hartman --- drivers/char/tpm/tpm-chip.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c index c406343848da..935f0e92ad61 100644 --- a/drivers/char/tpm/tpm-chip.c +++ b/drivers/char/tpm/tpm-chip.c @@ -187,7 +187,6 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev, cdev_init(&chip->cdev, &tpm_fops); chip->cdev.owner = THIS_MODULE; - chip->cdev.kobj.parent = &chip->dev.kobj; return chip; @@ -230,27 +229,16 @@ static int tpm_add_char_device(struct tpm_chip *chip) { int rc; - rc = cdev_add(&chip->cdev, chip->dev.devt, 1); + rc = cdev_device_add(&chip->cdev, &chip->dev); if (rc) { dev_err(&chip->dev, - "unable to cdev_add() %s, major %d, minor %d, err=%d\n", + "unable to cdev_device_add() %s, major %d, minor %d, err=%d\n", dev_name(&chip->dev), MAJOR(chip->dev.devt), MINOR(chip->dev.devt), rc); return rc; } - rc = device_add(&chip->dev); - if (rc) { - dev_err(&chip->dev, - "unable to device_register() %s, major %d, minor %d, err=%d\n", - dev_name(&chip->dev), MAJOR(chip->dev.devt), - MINOR(chip->dev.devt), rc); - - cdev_del(&chip->cdev); - return rc; - } - /* Make the chip available. */ mutex_lock(&idr_lock); idr_replace(&dev_nums_idr, chip, chip->dev_num); @@ -261,8 +249,7 @@ static int tpm_add_char_device(struct tpm_chip *chip) static void tpm_del_char_device(struct tpm_chip *chip) { - cdev_del(&chip->cdev); - device_del(&chip->dev); + cdev_device_del(&chip->cdev, &chip->dev); /* Make the chip unavailable. */ mutex_lock(&idr_lock); -- cgit v1.2.3-70-g09d2 From 1c1d152cc5ac3a67d199728a9ba0b4f54a498f8b Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:14 -0600 Subject: platform/chrome: cros_ec_dev - utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. At the same time we cleanup the error path through device_probe function: we use put_device instead of kfree directly as recommended by the device_initialize documentation. Signed-off-by: Logan Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_dev.c | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 6f09da4dadb8..6aa120cd0574 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -391,7 +391,6 @@ static int ec_device_probe(struct platform_device *pdev) 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); if (!ec) @@ -406,24 +405,12 @@ static int ec_device_probe(struct platform_device *pdev) 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(dev, ": failed to add character device\n"); - goto cdev_add_failed; - } - /* * 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.devt = MKDEV(ec_major, pdev->id); ec->class_dev.class = &cros_class; ec->class_dev.parent = dev; ec->class_dev.release = __remove; @@ -431,13 +418,13 @@ static int ec_device_probe(struct platform_device *pdev) 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; + goto failed; } - retval = device_add(&ec->class_dev); + retval = cdev_device_add(&ec->cdev, &ec->class_dev); if (retval) { - dev_err(dev, "device_register failed => %d\n", retval); - goto dev_reg_failed; + dev_err(dev, "cdev_device_add failed => %d\n", retval); + goto failed; } /* check whether this EC is a sensor hub. */ @@ -446,12 +433,8 @@ static int ec_device_probe(struct platform_device *pdev) return 0; -dev_reg_failed: -set_named_failed: - dev_set_drvdata(dev, NULL); - cdev_del(&ec->cdev); -cdev_add_failed: - kfree(ec); +failed: + put_device(&ec->class_dev); return retval; } -- cgit v1.2.3-70-g09d2 From a0d78193dcf2bfa7c3d169c55a7a30a5a89fca7a Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 17 Mar 2017 12:48:15 -0600 Subject: IB/ucm: utilize new cdev_device_add helper function The use after free is not triggerable here because the cdev holds the module lock and the only device_unregister is only triggered by module unload, however make the change for consistency. To make this work the cdev_del needs to move out of the struct device release function. This cleans up the error path significantly and thus also fixes a minor bug where the devnum would not be released if cdev_add failed. Signed-off-by: Jason Gunthorpe Signed-off-by: Logan Gunthorpe Reviewed-by: Logan Gunthorpe Reviewed-by: Leon Romanovsky Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/core/ucm.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/drivers/infiniband/core/ucm.c b/drivers/infiniband/core/ucm.c index cc0d51fb06e3..d15efa4be9ab 100644 --- a/drivers/infiniband/core/ucm.c +++ b/drivers/infiniband/core/ucm.c @@ -1205,12 +1205,15 @@ static void ib_ucm_release_dev(struct device *dev) struct ib_ucm_device *ucm_dev; ucm_dev = container_of(dev, struct ib_ucm_device, dev); - cdev_del(&ucm_dev->cdev); + kfree(ucm_dev); +} + +static void ib_ucm_free_dev(struct ib_ucm_device *ucm_dev) +{ if (ucm_dev->devnum < IB_UCM_MAX_DEVICES) clear_bit(ucm_dev->devnum, dev_map); else clear_bit(ucm_dev->devnum - IB_UCM_MAX_DEVICES, overflow_map); - kfree(ucm_dev); } static const struct file_operations ucm_fops = { @@ -1266,7 +1269,9 @@ static void ib_ucm_add_one(struct ib_device *device) if (!ucm_dev) return; + device_initialize(&ucm_dev->dev); ucm_dev->ib_dev = device; + ucm_dev->dev.release = ib_ucm_release_dev; devnum = find_first_zero_bit(dev_map, IB_UCM_MAX_DEVICES); if (devnum >= IB_UCM_MAX_DEVICES) { @@ -1286,16 +1291,14 @@ static void ib_ucm_add_one(struct ib_device *device) cdev_init(&ucm_dev->cdev, &ucm_fops); ucm_dev->cdev.owner = THIS_MODULE; kobject_set_name(&ucm_dev->cdev.kobj, "ucm%d", ucm_dev->devnum); - if (cdev_add(&ucm_dev->cdev, base, 1)) - goto err; ucm_dev->dev.class = &cm_class; ucm_dev->dev.parent = device->dev.parent; - ucm_dev->dev.devt = ucm_dev->cdev.dev; - ucm_dev->dev.release = ib_ucm_release_dev; + ucm_dev->dev.devt = base; + dev_set_name(&ucm_dev->dev, "ucm%d", ucm_dev->devnum); - if (device_register(&ucm_dev->dev)) - goto err_cdev; + if (cdev_device_add(&ucm_dev->cdev, &ucm_dev->dev)) + goto err_devnum; if (device_create_file(&ucm_dev->dev, &dev_attr_ibdev)) goto err_dev; @@ -1304,15 +1307,11 @@ static void ib_ucm_add_one(struct ib_device *device) return; err_dev: - device_unregister(&ucm_dev->dev); -err_cdev: - cdev_del(&ucm_dev->cdev); - if (ucm_dev->devnum < IB_UCM_MAX_DEVICES) - clear_bit(devnum, dev_map); - else - clear_bit(devnum, overflow_map); + cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev); +err_devnum: + ib_ucm_free_dev(ucm_dev); err: - kfree(ucm_dev); + put_device(&ucm_dev->dev); return; } @@ -1323,7 +1322,9 @@ static void ib_ucm_remove_one(struct ib_device *device, void *client_data) if (!ucm_dev) return; - device_unregister(&ucm_dev->dev); + cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev); + ib_ucm_free_dev(ucm_dev); + put_device(&ucm_dev->dev); } static CLASS_ATTR_STRING(abi_version, S_IRUGO, -- cgit v1.2.3-70-g09d2 From 985087157cebd2e710c3e65d8c3eabde4c591f1c Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:16 -0600 Subject: infiniband: utilize the new cdev_set_parent function This replaces the suspect looking cdev.kobj.parent lines with the equivalent cdev_set_parent function. This is a straightforward change that's largely cosmetic but it does push the kobj.parent ownership into char_dev.c where it belongs. Signed-off-by: Logan Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/core/user_mad.c | 4 ++-- drivers/infiniband/core/uverbs_main.c | 2 +- drivers/infiniband/hw/hfi1/device.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/user_mad.c b/drivers/infiniband/core/user_mad.c index aca7ff7abedc..25071b880c94 100644 --- a/drivers/infiniband/core/user_mad.c +++ b/drivers/infiniband/core/user_mad.c @@ -1183,7 +1183,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num, cdev_init(&port->cdev, &umad_fops); port->cdev.owner = THIS_MODULE; - port->cdev.kobj.parent = &umad_dev->kobj; + cdev_set_parent(&port->cdev, &umad_dev->kobj); kobject_set_name(&port->cdev.kobj, "umad%d", port->dev_num); if (cdev_add(&port->cdev, base, 1)) goto err_cdev; @@ -1202,7 +1202,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num, base += IB_UMAD_MAX_PORTS; cdev_init(&port->sm_cdev, &umad_sm_fops); port->sm_cdev.owner = THIS_MODULE; - port->sm_cdev.kobj.parent = &umad_dev->kobj; + cdev_set_parent(&port->sm_cdev, &umad_dev->kobj); kobject_set_name(&port->sm_cdev.kobj, "issm%d", port->dev_num); if (cdev_add(&port->sm_cdev, base, 1)) goto err_sm_cdev; diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index 35c788a32e26..f02d7b9cebea 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c @@ -1189,7 +1189,7 @@ static void ib_uverbs_add_one(struct ib_device *device) cdev_init(&uverbs_dev->cdev, NULL); uverbs_dev->cdev.owner = THIS_MODULE; uverbs_dev->cdev.ops = device->mmap ? &uverbs_mmap_fops : &uverbs_fops; - uverbs_dev->cdev.kobj.parent = &uverbs_dev->kobj; + cdev_set_parent(&uverbs_dev->cdev, &uverbs_dev->kobj); kobject_set_name(&uverbs_dev->cdev.kobj, "uverbs%d", uverbs_dev->devnum); if (cdev_add(&uverbs_dev->cdev, base, 1)) goto err_cdev; diff --git a/drivers/infiniband/hw/hfi1/device.c b/drivers/infiniband/hw/hfi1/device.c index bf64b5a7bfd7..bbb6069dec2a 100644 --- a/drivers/infiniband/hw/hfi1/device.c +++ b/drivers/infiniband/hw/hfi1/device.c @@ -69,7 +69,7 @@ int hfi1_cdev_init(int minor, const char *name, cdev_init(cdev, fops); cdev->owner = THIS_MODULE; - cdev->kobj.parent = parent; + cdev_set_parent(cdev, parent); kobject_set_name(&cdev->kobj, name); ret = cdev_add(cdev, dev, 1); -- cgit v1.2.3-70-g09d2 From 38923911dcc343f7f6624fc77a4ad63f2528fbcd Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:17 -0600 Subject: iio:core: utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. In doing so we have to remove a guard statement from cdev_del, but this doesn't appear to be required in any way. Signed-off-by: Logan Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/iio/industrialio-core.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index d18ded45bedd..26a03c6eeb47 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1720,18 +1720,13 @@ int iio_device_register(struct iio_dev *indio_dev) cdev_init(&indio_dev->chrdev, &iio_buffer_fileops); indio_dev->chrdev.owner = indio_dev->info->driver_module; - indio_dev->chrdev.kobj.parent = &indio_dev->dev.kobj; - ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1); - if (ret < 0) - goto error_unreg_eventset; - ret = device_add(&indio_dev->dev); + ret = cdev_device_add(&indio_dev->chrdev, &indio_dev->dev); if (ret < 0) - goto error_cdev_del; + goto error_unreg_eventset; return 0; -error_cdev_del: - cdev_del(&indio_dev->chrdev); + error_unreg_eventset: iio_device_unregister_eventset(indio_dev); error_free_sysfs: @@ -1752,10 +1747,8 @@ void iio_device_unregister(struct iio_dev *indio_dev) { mutex_lock(&indio_dev->info_exist_lock); - device_del(&indio_dev->dev); + cdev_device_del(&indio_dev->chrdev, &indio_dev->dev); - if (indio_dev->chrdev.dev) - cdev_del(&indio_dev->chrdev); iio_device_unregister_debugfs(indio_dev); iio_disable_all_buffers(indio_dev); -- cgit v1.2.3-70-g09d2 From 857313e51006ff51524579bcd8808b70f9a80812 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:18 -0600 Subject: media: utilize new cdev_device_add helper function Replace the open coded registration of the cdev and dev with the new device_add_cdev() helper. The helper replaces a common pattern by taking the proper reference against the parent device and adding both the cdev and the device. Signed-off-by: Logan Gunthorpe Acked-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/cec/cec-core.c | 16 ++++------------ drivers/media/media-devnode.c | 20 +++++--------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c index 37217e205040..3163e038a364 100644 --- a/drivers/media/cec/cec-core.c +++ b/drivers/media/cec/cec-core.c @@ -137,24 +137,17 @@ static int __must_check cec_devnode_register(struct cec_devnode *devnode, /* Part 2: Initialize and register the character device */ cdev_init(&devnode->cdev, &cec_devnode_fops); - devnode->cdev.kobj.parent = &devnode->dev.kobj; devnode->cdev.owner = owner; - ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1); - if (ret < 0) { - pr_err("%s: cdev_add failed\n", __func__); + ret = cdev_device_add(&devnode->cdev, &devnode->dev); + if (ret) { + pr_err("%s: cdev_device_add failed\n", __func__); goto clr_bit; } - ret = device_add(&devnode->dev); - if (ret) - goto cdev_del; - devnode->registered = true; return 0; -cdev_del: - cdev_del(&devnode->cdev); clr_bit: mutex_lock(&cec_devnode_lock); clear_bit(devnode->minor, cec_devnode_nums); @@ -190,8 +183,7 @@ static void cec_devnode_unregister(struct cec_devnode *devnode) devnode->unregistered = true; mutex_unlock(&devnode->lock); - device_del(&devnode->dev); - cdev_del(&devnode->cdev); + cdev_device_del(&devnode->cdev, &devnode->dev); put_device(&devnode->dev); } diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c index ae46753c90cb..423248f577b6 100644 --- a/drivers/media/media-devnode.c +++ b/drivers/media/media-devnode.c @@ -248,31 +248,22 @@ int __must_check media_devnode_register(struct media_device *mdev, dev_set_name(&devnode->dev, "media%d", devnode->minor); device_initialize(&devnode->dev); - /* Part 2: Initialize and register the character device */ + /* Part 2: Initialize the character device */ cdev_init(&devnode->cdev, &media_devnode_fops); devnode->cdev.owner = owner; - devnode->cdev.kobj.parent = &devnode->dev.kobj; - ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1); + /* Part 3: Add the media and char device */ + ret = cdev_device_add(&devnode->cdev, &devnode->dev); if (ret < 0) { - pr_err("%s: cdev_add failed\n", __func__); + pr_err("%s: cdev_device_add failed\n", __func__); goto cdev_add_error; } - /* Part 3: Add the media device */ - ret = device_add(&devnode->dev); - if (ret < 0) { - pr_err("%s: device_add failed\n", __func__); - goto device_add_error; - } - /* Part 4: Activate this minor. The char device can now be used. */ set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); return 0; -device_add_error: - cdev_del(&devnode->cdev); cdev_add_error: mutex_lock(&media_devnode_lock); clear_bit(devnode->minor, media_devnode_nums); @@ -298,9 +289,8 @@ void media_devnode_unregister(struct media_devnode *devnode) { mutex_lock(&media_devnode_lock); /* Delete the cdev on this minor as well */ - cdev_del(&devnode->cdev); + cdev_device_del(&devnode->cdev, &devnode->dev); mutex_unlock(&media_devnode_lock); - device_del(&devnode->dev); devnode->media_dev = NULL; put_device(&devnode->dev); } -- cgit v1.2.3-70-g09d2 From 493cfaeaa0c9bc0c79ce5751193d49fdac9aaaec Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:19 -0600 Subject: mtd: utilize new cdev_device_add helper function This is not as straightforward a conversion as the others in this series. These drivers did not originally make use of kobj.parent so they likely suffered from a use after free bug if someone unregistered the devices while they are being used. In order to make the conversions, switch from device_register to device_initialize / cdev_device_add. In build.c, this patch unwinds a complicated mess of extra get_device/put_devices and reference tracking by moving device_initialize early in the attach process. Then it always uses put_device and instead of using device_unregister and extra get_devices everywhere we just use cdev_device_del and one put_device once everything is completely done. This simplifies things dramatically and makes it easier to reason about. In vmt.c, the patch pushes device initialization up to the beginning of the device creation and then that function only needs to use put_device in the error path which simplifies things a good deal. Signed-off-by: Logan Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/ubi/build.c | 91 +++++++++---------------------------------------- drivers/mtd/ubi/vmt.c | 49 +++++++++----------------- 2 files changed, 33 insertions(+), 107 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 77513195f50e..8bae3731d039 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -420,41 +420,6 @@ static void dev_release(struct device *dev) kfree(ubi); } -/** - * ubi_sysfs_init - initialize sysfs for an UBI device. - * @ubi: UBI device description object - * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was - * taken - * - * This function returns zero in case of success and a negative error code in - * case of failure. - */ -static int ubi_sysfs_init(struct ubi_device *ubi, int *ref) -{ - int err; - - ubi->dev.release = dev_release; - ubi->dev.devt = ubi->cdev.dev; - ubi->dev.class = &ubi_class; - ubi->dev.groups = ubi_dev_groups; - dev_set_name(&ubi->dev, UBI_NAME_STR"%d", ubi->ubi_num); - err = device_register(&ubi->dev); - if (err) - return err; - - *ref = 1; - return 0; -} - -/** - * ubi_sysfs_close - close sysfs for an UBI device. - * @ubi: UBI device description object - */ -static void ubi_sysfs_close(struct ubi_device *ubi) -{ - device_unregister(&ubi->dev); -} - /** * kill_volumes - destroy all user volumes. * @ubi: UBI device description object @@ -471,27 +436,19 @@ static void kill_volumes(struct ubi_device *ubi) /** * uif_init - initialize user interfaces for an UBI device. * @ubi: UBI device description object - * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was - * taken, otherwise set to %0 * * This function initializes various user interfaces for an UBI device. If the * initialization fails at an early stage, this function frees all the - * resources it allocated, returns an error, and @ref is set to %0. However, - * if the initialization fails after the UBI device was registered in the - * driver core subsystem, this function takes a reference to @ubi->dev, because - * otherwise the release function ('dev_release()') would free whole @ubi - * object. The @ref argument is set to %1 in this case. The caller has to put - * this reference. + * resources it allocated, returns an error. * * This function returns zero in case of success and a negative error code in * case of failure. */ -static int uif_init(struct ubi_device *ubi, int *ref) +static int uif_init(struct ubi_device *ubi) { int i, err; dev_t dev; - *ref = 0; sprintf(ubi->ubi_name, UBI_NAME_STR "%d", ubi->ubi_num); /* @@ -508,20 +465,17 @@ static int uif_init(struct ubi_device *ubi, int *ref) return err; } + ubi->dev.devt = dev; + ubi_assert(MINOR(dev) == 0); cdev_init(&ubi->cdev, &ubi_cdev_operations); dbg_gen("%s major is %u", ubi->ubi_name, MAJOR(dev)); ubi->cdev.owner = THIS_MODULE; - err = cdev_add(&ubi->cdev, dev, 1); - if (err) { - ubi_err(ubi, "cannot add character device"); - goto out_unreg; - } - - err = ubi_sysfs_init(ubi, ref); + dev_set_name(&ubi->dev, UBI_NAME_STR "%d", ubi->ubi_num); + err = cdev_device_add(&ubi->cdev, &ubi->dev); if (err) - goto out_sysfs; + goto out_unreg; for (i = 0; i < ubi->vtbl_slots; i++) if (ubi->volumes[i]) { @@ -536,11 +490,7 @@ static int uif_init(struct ubi_device *ubi, int *ref) out_volumes: kill_volumes(ubi); -out_sysfs: - if (*ref) - get_device(&ubi->dev); - ubi_sysfs_close(ubi); - cdev_del(&ubi->cdev); + cdev_device_del(&ubi->cdev, &ubi->dev); out_unreg: unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1); ubi_err(ubi, "cannot initialize UBI %s, error %d", @@ -559,8 +509,7 @@ out_unreg: static void uif_close(struct ubi_device *ubi) { kill_volumes(ubi); - ubi_sysfs_close(ubi); - cdev_del(&ubi->cdev); + cdev_device_del(&ubi->cdev, &ubi->dev); unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1); } @@ -857,7 +806,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024) { struct ubi_device *ubi; - int i, err, ref = 0; + int i, err; if (max_beb_per1024 < 0 || max_beb_per1024 > MAX_MTD_UBI_BEB_LIMIT) return -EINVAL; @@ -919,6 +868,11 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, if (!ubi) return -ENOMEM; + device_initialize(&ubi->dev); + ubi->dev.release = dev_release; + ubi->dev.class = &ubi_class; + ubi->dev.groups = ubi_dev_groups; + ubi->mtd = mtd; ubi->ubi_num = ubi_num; ubi->vid_hdr_offset = vid_hdr_offset; @@ -995,7 +949,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, /* Make device "available" before it becomes accessible via sysfs */ ubi_devices[ubi_num] = ubi; - err = uif_init(ubi, &ref); + err = uif_init(ubi); if (err) goto out_detach; @@ -1045,8 +999,6 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, out_debugfs: ubi_debugfs_exit_dev(ubi); out_uif: - get_device(&ubi->dev); - ubi_assert(ref); uif_close(ubi); out_detach: ubi_devices[ubi_num] = NULL; @@ -1056,10 +1008,7 @@ out_detach: out_free: vfree(ubi->peb_buf); vfree(ubi->fm_buf); - if (ref) - put_device(&ubi->dev); - else - kfree(ubi); + put_device(&ubi->dev); return err; } @@ -1120,12 +1069,6 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway) if (ubi->bgt_thread) kthread_stop(ubi->bgt_thread); - /* - * Get a reference to the device in order to prevent 'dev_release()' - * from freeing the @ubi object. - */ - get_device(&ubi->dev); - ubi_debugfs_exit_dev(ubi); uif_close(ubi); diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c index 7ac78c13dd1c..85237cf661f9 100644 --- a/drivers/mtd/ubi/vmt.c +++ b/drivers/mtd/ubi/vmt.c @@ -155,11 +155,10 @@ static void vol_release(struct device *dev) */ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) { - int i, err, vol_id = req->vol_id, do_free = 1; + int i, err, vol_id = req->vol_id; struct ubi_volume *vol; struct ubi_vtbl_record vtbl_rec; struct ubi_eba_table *eba_tbl = NULL; - dev_t dev; if (ubi->ro_mode) return -EROFS; @@ -168,6 +167,12 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) if (!vol) return -ENOMEM; + device_initialize(&vol->dev); + vol->dev.release = vol_release; + vol->dev.parent = &ubi->dev; + vol->dev.class = &ubi_class; + vol->dev.groups = volume_dev_groups; + spin_lock(&ubi->volumes_lock); if (vol_id == UBI_VOL_NUM_AUTO) { /* Find unused volume ID */ @@ -268,24 +273,13 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) /* Register character device for the volume */ cdev_init(&vol->cdev, &ubi_vol_cdev_operations); vol->cdev.owner = THIS_MODULE; - dev = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1); - err = cdev_add(&vol->cdev, dev, 1); - if (err) { - ubi_err(ubi, "cannot add character device"); - goto out_mapping; - } - - vol->dev.release = vol_release; - vol->dev.parent = &ubi->dev; - vol->dev.devt = dev; - vol->dev.class = &ubi_class; - vol->dev.groups = volume_dev_groups; + vol->dev.devt = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1); dev_set_name(&vol->dev, "%s_%d", ubi->ubi_name, vol->vol_id); - err = device_register(&vol->dev); + err = cdev_device_add(&vol->cdev, &vol->dev); if (err) { - ubi_err(ubi, "cannot register device"); - goto out_cdev; + ubi_err(ubi, "cannot add device"); + goto out_mapping; } /* Fill volume table record */ @@ -318,28 +312,17 @@ out_sysfs: * We have registered our device, we should not free the volume * description object in this function in case of an error - it is * freed by the release function. - * - * Get device reference to prevent the release function from being - * called just after sysfs has been closed. */ - do_free = 0; - get_device(&vol->dev); - device_unregister(&vol->dev); -out_cdev: - cdev_del(&vol->cdev); + cdev_device_del(&vol->cdev, &vol->dev); out_mapping: - if (do_free) - ubi_eba_destroy_table(eba_tbl); + ubi_eba_destroy_table(eba_tbl); out_acc: spin_lock(&ubi->volumes_lock); ubi->rsvd_pebs -= vol->reserved_pebs; ubi->avail_pebs += vol->reserved_pebs; out_unlock: spin_unlock(&ubi->volumes_lock); - if (do_free) - kfree(vol); - else - put_device(&vol->dev); + put_device(&vol->dev); ubi_err(ubi, "cannot create volume %d, error %d", vol_id, err); return err; } @@ -391,8 +374,8 @@ int ubi_remove_volume(struct ubi_volume_desc *desc, int no_vtbl) goto out_err; } - cdev_del(&vol->cdev); - device_unregister(&vol->dev); + cdev_device_del(&vol->cdev, &vol->dev); + put_device(&vol->dev); spin_lock(&ubi->volumes_lock); ubi->rsvd_pebs -= reserved_pebs; -- cgit v1.2.3-70-g09d2 From dbef390d2eb0a7b0a6be21381a326d4e13953aba Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:20 -0600 Subject: rapidio: utilize new cdev_device_add helper function This driver did not originally set kobj.parent so it likely had potential a use after free bug which this patch fixes. We convert from device_register to device_initialize/cdev_device_add. While we are at it we use put_device instead of kfree (as recommended by the device_initialize documentation). We also remove an unnecessary extra get_device from the code. Signed-off-by: Logan Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/rapidio/devices/rio_mport_cdev.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c index 50b617af81bd..5beb0c361076 100644 --- a/drivers/rapidio/devices/rio_mport_cdev.c +++ b/drivers/rapidio/devices/rio_mport_cdev.c @@ -2444,31 +2444,25 @@ static struct mport_dev *mport_cdev_add(struct rio_mport *mport) mutex_init(&md->buf_mutex); mutex_init(&md->file_mutex); INIT_LIST_HEAD(&md->file_list); - cdev_init(&md->cdev, &mport_fops); - md->cdev.owner = THIS_MODULE; - ret = cdev_add(&md->cdev, MKDEV(MAJOR(dev_number), mport->id), 1); - if (ret < 0) { - kfree(md); - rmcd_error("Unable to register a device, err=%d", ret); - return NULL; - } - md->dev.devt = md->cdev.dev; + device_initialize(&md->dev); + md->dev.devt = MKDEV(MAJOR(dev_number), mport->id); md->dev.class = dev_class; md->dev.parent = &mport->dev; md->dev.release = mport_device_release; dev_set_name(&md->dev, DEV_NAME "%d", mport->id); atomic_set(&md->active, 1); - ret = device_register(&md->dev); + cdev_init(&md->cdev, &mport_fops); + md->cdev.owner = THIS_MODULE; + + ret = cdev_device_add(&md->cdev, &md->dev); if (ret) { rmcd_error("Failed to register mport %d (err=%d)", mport->id, ret); goto err_cdev; } - get_device(&md->dev); - INIT_LIST_HEAD(&md->doorbells); spin_lock_init(&md->db_lock); INIT_LIST_HEAD(&md->portwrites); @@ -2513,8 +2507,7 @@ static struct mport_dev *mport_cdev_add(struct rio_mport *mport) return md; err_cdev: - cdev_del(&md->cdev); - kfree(md); + put_device(&md->dev); return NULL; } @@ -2578,7 +2571,7 @@ static void mport_cdev_remove(struct mport_dev *md) atomic_set(&md->active, 0); mport_cdev_terminate_dma(md); rio_del_mport_pw_handler(md->mport, md, rio_mport_pw_handler); - cdev_del(&(md->cdev)); + cdev_device_del(&md->cdev, &md->dev); mport_cdev_kill_fasync(md); flush_workqueue(dma_wq); @@ -2603,7 +2596,6 @@ static void mport_cdev_remove(struct mport_dev *md) rio_release_inb_dbell(md->mport, 0, 0x0fff); - device_unregister(&md->dev); put_device(&md->dev); } -- cgit v1.2.3-70-g09d2 From d5ed9177f64fe95d9de79e6504d41612d9127e8a Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:21 -0600 Subject: rtc: utilize new cdev_device_add helper function Mostly straightforward, but we had to remove the rtc_dev_add/del_device functions as they split up the cdev_add and the device_add. Doing this also revealed that there was likely another subtle bug: seeing cdev_add was done after device_register, the cdev probably was not ready before device_add when the uevent occurs. This would race with userspace, if it tried to use the device directly after the uevent. This is fixed just by using the new helper function. Another weird thing is this driver would, in some error cases, call cdev_add() without calling cdev_init. This patchset corrects this by avoiding calling cdev_add if the devt is not set. Signed-off-by: Logan Gunthorpe Acked-by: Alexandre Belloni Signed-off-by: Greg Kroah-Hartman --- drivers/rtc/class.c | 14 ++++++++++---- drivers/rtc/rtc-core.h | 10 ---------- drivers/rtc/rtc-dev.c | 17 ----------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 74fd9746aeca..5fb439897fe1 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c @@ -195,6 +195,8 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev, goto exit_ida; } + device_initialize(&rtc->dev); + rtc->id = id; rtc->ops = ops; rtc->owner = owner; @@ -233,14 +235,19 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev, rtc_dev_prepare(rtc); - err = device_register(&rtc->dev); + err = cdev_device_add(&rtc->char_dev, &rtc->dev); if (err) { + dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n", + rtc->name, MAJOR(rtc->dev.devt), rtc->id); + /* This will free both memory and the ID */ put_device(&rtc->dev); goto exit; + } else { + dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name, + MAJOR(rtc->dev.devt), rtc->id); } - rtc_dev_add_device(rtc); rtc_proc_add_device(rtc); dev_info(dev, "rtc core: registered %s as %s\n", @@ -271,9 +278,8 @@ void rtc_device_unregister(struct rtc_device *rtc) * Remove innards of this RTC, then disable it, before * letting any rtc_class_open() users access it again */ - rtc_dev_del_device(rtc); rtc_proc_del_device(rtc); - device_del(&rtc->dev); + cdev_device_del(&rtc->char_dev, &rtc->dev); rtc->ops = NULL; mutex_unlock(&rtc->ops_lock); put_device(&rtc->dev); diff --git a/drivers/rtc/rtc-core.h b/drivers/rtc/rtc-core.h index a098aea197fc..7a4ed2f7c7d7 100644 --- a/drivers/rtc/rtc-core.h +++ b/drivers/rtc/rtc-core.h @@ -3,8 +3,6 @@ extern void __init rtc_dev_init(void); extern void __exit rtc_dev_exit(void); extern void rtc_dev_prepare(struct rtc_device *rtc); -extern void rtc_dev_add_device(struct rtc_device *rtc); -extern void rtc_dev_del_device(struct rtc_device *rtc); #else @@ -20,14 +18,6 @@ static inline void rtc_dev_prepare(struct rtc_device *rtc) { } -static inline void rtc_dev_add_device(struct rtc_device *rtc) -{ -} - -static inline void rtc_dev_del_device(struct rtc_device *rtc) -{ -} - #endif #ifdef CONFIG_RTC_INTF_PROC diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c index 6dc8f29697ab..e81a8711fea7 100644 --- a/drivers/rtc/rtc-dev.c +++ b/drivers/rtc/rtc-dev.c @@ -477,23 +477,6 @@ void rtc_dev_prepare(struct rtc_device *rtc) cdev_init(&rtc->char_dev, &rtc_dev_fops); rtc->char_dev.owner = rtc->owner; - rtc->char_dev.kobj.parent = &rtc->dev.kobj; -} - -void rtc_dev_add_device(struct rtc_device *rtc) -{ - if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1)) - dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n", - rtc->name, MAJOR(rtc_devt), rtc->id); - else - dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name, - MAJOR(rtc_devt), rtc->id); -} - -void rtc_dev_del_device(struct rtc_device *rtc) -{ - if (rtc->dev.devt) - cdev_del(&rtc->char_dev); } void __init rtc_dev_init(void) -- cgit v1.2.3-70-g09d2 From ac1ddc584e9817d9d0a4572f3245305d907c6e61 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 17 Mar 2017 12:48:22 -0600 Subject: scsi: utilize new cdev_device_add helper function This driver did not set kobj.parent so it likely suffered from a potential use after free race if the user unregistered the device while it was in use. This was not so straightforward a conversion but I think this patch cleans up its probe's error path significantly. This patch adds device_initialize, which is required for cdev_device_add. Then it switches to put_device instead of kfree as recommended by device_initialize's documentation. This removes a lot from the error path which was already in __remove. A couple things needed to be re-ordered to be entirely correct, though. ida_remove is also moved out of __remove and into unregister to simplify things and follow the pattern other devices are using. This also drop an extra unnecessary get_device/put_device in the code. Signed-off-by: Logan Gunthorpe Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/osd/osd_uld.c | 56 +++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/drivers/scsi/osd/osd_uld.c b/drivers/scsi/osd/osd_uld.c index e0ce5d2fd14d..4101c3178411 100644 --- a/drivers/scsi/osd/osd_uld.c +++ b/drivers/scsi/osd/osd_uld.c @@ -400,9 +400,6 @@ static void __remove(struct device *dev) kfree(oud->odi.osdname); - if (oud->cdev.owner) - cdev_del(&oud->cdev); - osd_dev_fini(&oud->od); scsi_device_put(scsi_device); @@ -411,7 +408,6 @@ static void __remove(struct device *dev) if (oud->disk) put_disk(oud->disk); - ida_remove(&osd_minor_ida, oud->minor); kfree(oud); } @@ -446,8 +442,20 @@ static int osd_probe(struct device *dev) if (NULL == oud) goto err_retract_minor; + /* class device member */ + device_initialize(&oud->class_dev); dev_set_drvdata(dev, oud); oud->minor = minor; + oud->class_dev.devt = MKDEV(SCSI_OSD_MAJOR, oud->minor); + oud->class_dev.class = &osd_uld_class; + oud->class_dev.parent = dev; + oud->class_dev.release = __remove; + + /* hold one more reference to the scsi_device that will get released + * in __release, in case a logout is happening while fs is mounted + */ + scsi_device_get(scsi_device); + osd_dev_init(&oud->od, scsi_device); /* allocate a disk and set it up */ /* FIXME: do we need this since sg has already done that */ @@ -461,59 +469,34 @@ static int osd_probe(struct device *dev) sprintf(disk->disk_name, "osd%d", oud->minor); oud->disk = disk; - /* hold one more reference to the scsi_device that will get released - * in __release, in case a logout is happening while fs is mounted - */ - scsi_device_get(scsi_device); - osd_dev_init(&oud->od, scsi_device); - /* Detect the OSD Version */ error = __detect_osd(oud); if (error) { OSD_ERR("osd detection failed, non-compatible OSD device\n"); - goto err_put_disk; + goto err_free_osd; } /* init the char-device for communication with user-mode */ cdev_init(&oud->cdev, &osd_fops); oud->cdev.owner = THIS_MODULE; - error = cdev_add(&oud->cdev, - MKDEV(SCSI_OSD_MAJOR, oud->minor), 1); - if (error) { - OSD_ERR("cdev_add failed\n"); - goto err_put_disk; - } - /* class device member */ - oud->class_dev.devt = oud->cdev.dev; - oud->class_dev.class = &osd_uld_class; - oud->class_dev.parent = dev; - oud->class_dev.release = __remove; error = dev_set_name(&oud->class_dev, "%s", disk->disk_name); if (error) { OSD_ERR("dev_set_name failed => %d\n", error); - goto err_put_cdev; + goto err_free_osd; } - error = device_register(&oud->class_dev); + error = cdev_device_add(&oud->cdev, &oud->class_dev); if (error) { OSD_ERR("device_register failed => %d\n", error); - goto err_put_cdev; + goto err_free_osd; } - get_device(&oud->class_dev); - OSD_INFO("osd_probe %s\n", disk->disk_name); return 0; -err_put_cdev: - cdev_del(&oud->cdev); -err_put_disk: - scsi_device_put(scsi_device); - put_disk(disk); err_free_osd: - dev_set_drvdata(dev, NULL); - kfree(oud); + put_device(&oud->class_dev); err_retract_minor: ida_remove(&osd_minor_ida, minor); return error; @@ -530,9 +513,10 @@ static int osd_remove(struct device *dev) scsi_device); } - device_unregister(&oud->class_dev); - + cdev_device_del(&oud->cdev, &oud->class_dev); + ida_remove(&osd_minor_ida, oud->minor); put_device(&oud->class_dev); + return 0; } -- cgit v1.2.3-70-g09d2 From 132c93d4215c5ea12f8bb291249ede159d507b6e Mon Sep 17 00:00:00 2001 From: Cyril Bur Date: Wed, 22 Mar 2017 14:13:28 +1100 Subject: drivers/misc: Aspeed LPC control fix compile error and warning pgprot_dmachoerent() is not defined on every architecture. Having COMPILE_TEST set for the driver causes it to be compiled on architectures which do not have pgprot_dmachoerent(): drivers/misc/aspeed-lpc-ctrl.c: In function 'aspeed_lpc_ctrl_mmap': drivers/misc/aspeed-lpc-ctrl.c:51:9: error: implicit declaration of function 'pgprot_dmacoherent' [-Werror=implicit-function-declaration] prot = pgprot_dmacoherent(prot); There are two possible solutions: 1. Remove COMPILE_TEST to ensure the driver is only compiled on ARM 2. Use pgprot_noncached() instead of pgprot_dmachoerent() The first option results in less compile testing of the LPC control driver which is undesirable. The second option uses a function that is declared on all architectures and therefore should always build. Currently there is no practical difference between pgprot_noncached() and pgprot_dmachoerent() for the aspeed chips that this driver is compatible with. The reason for pgprot_dmachoerent() was that there may be chips made at some point in the future that could include hardware that pgprot_dmachoerent() could optimise for. As none of this hardware has even been announced there isn't really a need for pgprot_dmachoerent(). Using pgprot_noncached() is completely correct and optimal for all existing hardware on which the LPC control driver will run. This commit also addresses that phys_addr_t should be printed using %pap rather than %x: In file included from include/linux/miscdevice.h:6:0, from drivers/misc/aspeed-lpc-ctrl.c:11: drivers/misc/aspeed-lpc-ctrl.c: In function 'aspeed_lpc_ctrl_probe': drivers/misc/aspeed-lpc-ctrl.c:232:17: warning: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'phys_addr_t {aka long long unsigned int}' [-Wformat=] dev_info(dev, "Loaded at 0x%08x (0x%08x)\n", Signed-off-by: Cyril Bur Reported-by: kbuild test robot Signed-off-by: Greg Kroah-Hartman --- drivers/misc/aspeed-lpc-ctrl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c index f6acbe1d9378..c654651a7b6d 100644 --- a/drivers/misc/aspeed-lpc-ctrl.c +++ b/drivers/misc/aspeed-lpc-ctrl.c @@ -48,7 +48,7 @@ static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma) return -EINVAL; /* ast2400/2500 AHB accesses are not cache coherent */ - prot = pgprot_dmacoherent(prot); + prot = pgprot_noncached(prot); if (remap_pfn_range(vma, vma->vm_start, (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff, @@ -229,8 +229,8 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) if (rc) dev_err(dev, "Unable to register device\n"); else - dev_info(dev, "Loaded at 0x%08x (0x%08x)\n", - lpc_ctrl->mem_base, lpc_ctrl->mem_size); + dev_info(dev, "Loaded at %pap (0x%08x)\n", + &lpc_ctrl->mem_base, lpc_ctrl->mem_size); return rc; } -- cgit v1.2.3-70-g09d2 From f7d88d24c5a2b66b02770e2776fce72b5fd70193 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Mon, 6 Mar 2017 16:20:50 +0200 Subject: drivers, char: convert vma_data.refcnt from atomic_t to refcount_t refcount_t type and corresponding API should be used instead of atomic_t when the variable is used as a reference counter. This allows to avoid accidental refcounter overflows that might lead to use-after-free situations. Signed-off-by: Elena Reshetova Signed-off-by: Hans Liljestrand Signed-off-by: Kees Cook Signed-off-by: David Windsor Signed-off-by: Greg Kroah-Hartman --- drivers/char/mspec.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/char/mspec.c b/drivers/char/mspec.c index a9c2fa3c81e5..7b75669d3670 100644 --- a/drivers/char/mspec.c +++ b/drivers/char/mspec.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -89,7 +90,7 @@ static int is_sn2; * protect in fork case where multiple tasks share the vma_data. */ struct vma_data { - atomic_t refcnt; /* Number of vmas sharing the data. */ + refcount_t refcnt; /* Number of vmas sharing the data. */ spinlock_t lock; /* Serialize access to this structure. */ int count; /* Number of pages allocated. */ enum mspec_page_type type; /* Type of pages allocated. */ @@ -144,7 +145,7 @@ mspec_open(struct vm_area_struct *vma) struct vma_data *vdata; vdata = vma->vm_private_data; - atomic_inc(&vdata->refcnt); + refcount_inc(&vdata->refcnt); } /* @@ -162,7 +163,7 @@ mspec_close(struct vm_area_struct *vma) vdata = vma->vm_private_data; - if (!atomic_dec_and_test(&vdata->refcnt)) + if (!refcount_dec_and_test(&vdata->refcnt)) return; last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT; @@ -274,7 +275,7 @@ mspec_mmap(struct file *file, struct vm_area_struct *vma, vdata->vm_end = vma->vm_end; vdata->type = type; spin_lock_init(&vdata->lock); - atomic_set(&vdata->refcnt, 1); + refcount_set(&vdata->refcnt, 1); vma->vm_private_data = vdata; vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; -- cgit v1.2.3-70-g09d2 From 392910cf3f8a0161d3da45d02ea17f2910d9193b Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Mon, 6 Mar 2017 16:20:49 +0200 Subject: drivers, firewire: convert fw_node.ref_count from atomic_t to refcount_t refcount_t type and corresponding API should be used instead of atomic_t when the variable is used as a reference counter. This allows to avoid accidental refcounter overflows that might lead to use-after-free situations. Signed-off-by: Elena Reshetova Signed-off-by: Hans Liljestrand Signed-off-by: Kees Cook Signed-off-by: David Windsor Signed-off-by: Greg Kroah-Hartman --- drivers/firewire/core-topology.c | 2 +- drivers/firewire/core.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/firewire/core-topology.c b/drivers/firewire/core-topology.c index 0de83508f321..939d259ddf19 100644 --- a/drivers/firewire/core-topology.c +++ b/drivers/firewire/core-topology.c @@ -124,7 +124,7 @@ static struct fw_node *fw_node_create(u32 sid, int port_count, int color) node->initiated_reset = SELF_ID_PHY_INITIATOR(sid); node->port_count = port_count; - atomic_set(&node->ref_count, 1); + refcount_set(&node->ref_count, 1); INIT_LIST_HEAD(&node->link); return node; diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index e1480ff683d2..c07962ead5e4 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -12,7 +12,7 @@ #include #include -#include +#include struct device; struct fw_card; @@ -184,7 +184,7 @@ struct fw_node { * local node to this node. */ u8 max_depth:4; /* Maximum depth to any leaf node */ u8 max_hops:4; /* Max hops in this sub tree */ - atomic_t ref_count; + refcount_t ref_count; /* For serializing node topology into a list. */ struct list_head link; @@ -197,14 +197,14 @@ struct fw_node { static inline struct fw_node *fw_node_get(struct fw_node *node) { - atomic_inc(&node->ref_count); + refcount_inc(&node->ref_count); return node; } static inline void fw_node_put(struct fw_node *node) { - if (atomic_dec_and_test(&node->ref_count)) + if (refcount_dec_and_test(&node->ref_count)) kfree(node); } -- cgit v1.2.3-70-g09d2 From 366380cd62f4fce82d970e9a510d0510b4dea5ee Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 21 Mar 2017 20:37:47 +0200 Subject: Revert "extcon: usb-gpio: add support for ACPI gpio interface" The commit 942c7924a51e introduced a check for ACPI handle for the device that never appears on any ACPI-enabled platform so far. It seems a confusion with extcon-intel-int3496 which does support ACPI-enabled platforms. Revert commit 942c7924a51e to avoid any confusion in the future. Signed-off-by: Andy Shevchenko Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usb-gpio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index a5e1882b4ca6..a0d7de8675e0 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ @@ -111,7 +110,7 @@ static int usb_extcon_probe(struct platform_device *pdev) struct usb_extcon_info *info; int ret; - if (!np && !ACPI_HANDLE(dev)) + if (!np) return -EINVAL; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); -- cgit v1.2.3-70-g09d2 From e57bb43c7e346943d73b44d05485aac68d8aff9d Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 25 Jan 2017 09:34:06 +0000 Subject: extcon: arizona: Wait for any running HPDETs to complete on jack removal As the HPDET can't be aborted mid way through we should not allow any new insertion to be processed until the previous HPDET has finished. It is very unlikely but with low enough debounce settings you could start a new HPDET before the old one has completed, which results in an erroneous reading. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ed78b7c26627..e2d78cd7030d 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -51,6 +51,9 @@ #define HPDET_DEBOUNCE 500 #define DEFAULT_MICD_TIMEOUT 2000 +#define ARIZONA_HPDET_WAIT_COUNT 15 +#define ARIZONA_HPDET_WAIT_DELAY_MS 20 + #define QUICK_HEADPHONE_MAX_OHM 3 #define MICROPHONE_MIN_OHM 1257 #define MICROPHONE_MAX_OHM 30000 @@ -1049,6 +1052,40 @@ static void arizona_hpdet_work(struct work_struct *work) mutex_unlock(&info->lock); } +static int arizona_hpdet_wait(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + unsigned int val; + int i, ret; + + for (i = 0; i < ARIZONA_HPDET_WAIT_COUNT; i++) { + ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2, + &val); + if (ret) { + dev_err(arizona->dev, + "Failed to read HPDET state: %d\n", ret); + return ret; + } + + switch (info->hpdet_ip_version) { + case 0: + if (val & ARIZONA_HP_DONE) + return 0; + break; + default: + if (val & ARIZONA_HP_DONE_B) + return 0; + break; + } + + msleep(ARIZONA_HPDET_WAIT_DELAY_MS); + } + + dev_warn(arizona->dev, "HPDET did not appear to complete\n"); + + return -ETIMEDOUT; +} + static irqreturn_t arizona_jackdet(int irq, void *data) { struct arizona_extcon_info *info = data; @@ -1155,6 +1192,15 @@ static irqreturn_t arizona_jackdet(int irq, void *data) "Removal report failed: %d\n", ret); } + /* + * If the jack was removed during a headphone detection we + * need to wait for the headphone detection to finish, as + * it can not be aborted. We don't want to be able to start + * a new headphone detection from a fresh insert until this + * one is finished. + */ + arizona_hpdet_wait(info); + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, -- cgit v1.2.3-70-g09d2 From 98fd079297dd274c15c926a337253675573c5832 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 13 Feb 2017 17:21:52 +0800 Subject: extcon: usb-gpio: Do not enable USB as wakeup source by default Whether the USB port as a wakeup source should be determined by user, but not enabled by default. Signed-off-by: Peter Chen Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usb-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index a0d7de8675e0..0ac32dfdda50 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -194,7 +194,7 @@ static int usb_extcon_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); - device_init_wakeup(dev, true); + device_set_wakeup_capable(&pdev->dev, true); /* Perform initial detection */ usb_extcon_detect_cable(&info->wq_detcable.work); -- cgit v1.2.3-70-g09d2 From 8680b4d1933fbe3349d51a4e1fd4513b12abffed Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Wed, 15 Feb 2017 14:31:28 +0200 Subject: extcon: usb-gpio: Don't miss event during suspend/resume We must check for ID/VBUS changes during resume irrespective of whether our device wakeup is enabled or not. Without this we seem to be missing ID/VBUS events after system suspend/resume. Signed-off-by: Roger Quadros Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usb-gpio.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index 0ac32dfdda50..9c925b05b7aa 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -281,9 +281,8 @@ static int usb_extcon_resume(struct device *dev) if (info->vbus_gpiod) enable_irq(info->vbus_irq); - if (!device_may_wakeup(dev)) - queue_delayed_work(system_power_efficient_wq, - &info->wq_detcable, 0); + queue_delayed_work(system_power_efficient_wq, + &info->wq_detcable, 0); return ret; } -- cgit v1.2.3-70-g09d2 From 01944321de3021020886564328684b7603ee216c Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Wed, 15 Feb 2017 14:31:29 +0200 Subject: extcon: palmas: Don't miss GPIO events during suspend/resume The USB cable state can change during suspend/resume so be sure to check and update the extcon state. Signed-off-by: Roger Quadros Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-palmas.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index ca904e8b3235..2af4369a2d73 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -413,6 +413,12 @@ static int palmas_usb_resume(struct device *dev) if (palmas_usb->enable_gpio_id_detection) disable_irq_wake(palmas_usb->gpio_id_irq); } + + /* check if GPIO states changed while suspend/resume */ + if (palmas_usb->enable_gpio_vbus_detection) + palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb); + palmas_gpio_id_detect(&palmas_usb->wq_detectid.work); + return 0; }; #endif -- cgit v1.2.3-70-g09d2 From db0f3baaa38bb587d831b1127082643b5e813074 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 23 Mar 2017 17:01:42 +0100 Subject: extcon: intel-cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver Add a driver for charger detection / control on the Intel Cherrytrail Whiskey Cove PMIC. Signed-off-by: Hans de Goede Signed-off-by: Chanwoo Choi --- drivers/extcon/Kconfig | 7 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-intel-cht-wc.c | 353 +++++++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 drivers/extcon/extcon-intel-cht-wc.c diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index fc09c76248b4..32f2dc8e4702 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496 This ACPI device is typically found on Intel Baytrail or Cherrytrail based tablets, or other Baytrail / Cherrytrail devices. +config EXTCON_INTEL_CHT_WC + tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver" + depends on INTEL_SOC_PMIC_CHTWC + help + Say Y here to enable extcon support for charger detection / control + on the Intel Cherrytrail Whiskey Cove PMIC. + config EXTCON_MAX14577 tristate "Maxim MAX14577/77836 EXTCON Support" depends on MFD_MAX14577 diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 237ac3f953c2..ecfa95804427 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o +obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c new file mode 100644 index 000000000000..f1c43af7682b --- /dev/null +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -0,0 +1,353 @@ +/* + * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC + * Copyright (C) 2017 Hans de Goede + * + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include + +#define CHT_WC_PHYCTRL 0x5e07 + +#define CHT_WC_CHGRCTRL0 0x5e16 +#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0) +#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1) +#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2) +#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3) +#define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4) +#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5) +#define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6) +#define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7) + +#define CHT_WC_CHGRCTRL1 0x5e17 + +#define CHT_WC_USBSRC 0x5e29 +#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0) +#define CHT_WC_USBSRC_STS_SUCCESS 2 +#define CHT_WC_USBSRC_STS_FAIL 3 +#define CHT_WC_USBSRC_TYPE_SHIFT 2 +#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2) +#define CHT_WC_USBSRC_TYPE_NONE 0 +#define CHT_WC_USBSRC_TYPE_SDP 1 +#define CHT_WC_USBSRC_TYPE_DCP 2 +#define CHT_WC_USBSRC_TYPE_CDP 3 +#define CHT_WC_USBSRC_TYPE_ACA 4 +#define CHT_WC_USBSRC_TYPE_SE1 5 +#define CHT_WC_USBSRC_TYPE_MHL 6 +#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7 +#define CHT_WC_USBSRC_TYPE_OTHER 8 +#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9 + +#define CHT_WC_PWRSRC_IRQ 0x6e03 +#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f +#define CHT_WC_PWRSRC_STS 0x6e1e +#define CHT_WC_PWRSRC_VBUS BIT(0) +#define CHT_WC_PWRSRC_DC BIT(1) +#define CHT_WC_PWRSRC_BAT BIT(2) +#define CHT_WC_PWRSRC_ID_GND BIT(3) +#define CHT_WC_PWRSRC_ID_FLOAT BIT(4) + +enum cht_wc_usb_id { + USB_ID_OTG, + USB_ID_GND, + USB_ID_FLOAT, + USB_RID_A, + USB_RID_B, + USB_RID_C, +}; + +enum cht_wc_mux_select { + MUX_SEL_PMIC = 0, + MUX_SEL_SOC, +}; + +static const unsigned int cht_wc_extcon_cables[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_ACA, + EXTCON_NONE, +}; + +struct cht_wc_extcon_data { + struct device *dev; + struct regmap *regmap; + struct extcon_dev *edev; + unsigned int previous_cable; +}; + +static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) +{ + if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND) + return USB_ID_GND; + if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT) + return USB_ID_FLOAT; + + /* + * Once we have iio support for the gpadc we should read the USBID + * gpadc channel here and determine ACA role based on that. + */ + return USB_ID_FLOAT; +} + +static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext) +{ + int ret, usbsrc, status; + unsigned long timeout; + + /* Charger detection can take upto 600ms, wait 800ms max. */ + timeout = jiffies + msecs_to_jiffies(800); + do { + ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc); + if (ret) { + dev_err(ext->dev, "Error reading usbsrc: %d\n", ret); + return ret; + } + + status = usbsrc & CHT_WC_USBSRC_STS_MASK; + if (status == CHT_WC_USBSRC_STS_SUCCESS || + status == CHT_WC_USBSRC_STS_FAIL) + break; + + msleep(50); /* Wait a bit before retrying */ + } while (time_before(jiffies, timeout)); + + if (status != CHT_WC_USBSRC_STS_SUCCESS) { + if (status == CHT_WC_USBSRC_STS_FAIL) + dev_warn(ext->dev, "Could not detect charger type\n"); + else + dev_warn(ext->dev, "Timeout detecting charger type\n"); + return EXTCON_CHG_USB_SDP; /* Save fallback */ + } + + usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; + switch (usbsrc) { + default: + dev_warn(ext->dev, + "Unhandled charger type %d, defaulting to SDP\n", + ret); + /* Fall through, treat as SDP */ + case CHT_WC_USBSRC_TYPE_SDP: + case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN: + case CHT_WC_USBSRC_TYPE_OTHER: + return EXTCON_CHG_USB_SDP; + case CHT_WC_USBSRC_TYPE_CDP: + return EXTCON_CHG_USB_CDP; + case CHT_WC_USBSRC_TYPE_DCP: + case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: + case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ + return EXTCON_CHG_USB_DCP; + case CHT_WC_USBSRC_TYPE_ACA: + return EXTCON_CHG_USB_ACA; + } +} + +static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) +{ + int ret; + + ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state); + if (ret) + dev_err(ext->dev, "Error writing phyctrl: %d\n", ret); +} + +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ +static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, + unsigned int cable, bool state) +{ + extcon_set_state_sync(ext->edev, cable, state); + if (cable == EXTCON_CHG_USB_SDP) + extcon_set_state_sync(ext->edev, EXTCON_USB, state); +} + +static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) +{ + int ret, pwrsrc_sts, id; + unsigned int cable = EXTCON_NONE; + + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); + if (ret) { + dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); + return; + } + + id = cht_wc_extcon_get_id(ext, pwrsrc_sts); + if (id == USB_ID_GND) { + /* The 5v boost causes a false VBUS / SDP detect, skip */ + goto charger_det_done; + } + + /* Plugged into a host/charger or not connected? */ + if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { + /* Route D+ and D- to PMIC for future charger detection */ + cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); + goto set_state; + } + + ret = cht_wc_extcon_get_charger(ext); + if (ret >= 0) + cable = ret; + +charger_det_done: + /* Route D+ and D- to SoC for the host or gadget controller */ + cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC); + +set_state: + if (cable != ext->previous_cable) { + cht_wc_extcon_set_state(ext, cable, true); + cht_wc_extcon_set_state(ext, ext->previous_cable, false); + ext->previous_cable = cable; + } + + extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, + id == USB_ID_GND || id == USB_RID_A); +} + +static irqreturn_t cht_wc_extcon_isr(int irq, void *data) +{ + struct cht_wc_extcon_data *ext = data; + int ret, irqs; + + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs); + if (ret) { + dev_err(ext->dev, "Error reading irqs: %d\n", ret); + return IRQ_NONE; + } + + cht_wc_extcon_pwrsrc_event(ext); + + ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs); + if (ret) { + dev_err(ext->dev, "Error writing irqs: %d\n", ret); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) +{ + int ret, mask, val; + + mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK; + val = enable ? mask : 0; + ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val); + if (ret) + dev_err(ext->dev, "Error setting sw control: %d\n", ret); + + return ret; +} + +static int cht_wc_extcon_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct cht_wc_extcon_data *ext; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL); + if (!ext) + return -ENOMEM; + + ext->dev = &pdev->dev; + ext->regmap = pmic->regmap; + ext->previous_cable = EXTCON_NONE; + + /* Initialize extcon device */ + ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables); + if (IS_ERR(ext->edev)) + return PTR_ERR(ext->edev); + + /* Enable sw control */ + ret = cht_wc_extcon_sw_control(ext, true); + if (ret) + return ret; + + /* Register extcon device */ + ret = devm_extcon_dev_register(ext->dev, ext->edev); + if (ret) { + dev_err(ext->dev, "Error registering extcon device: %d\n", ret); + goto disable_sw_control; + } + + /* Route D+ and D- to PMIC for initial charger detection */ + cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); + + /* Get initial state */ + cht_wc_extcon_pwrsrc_event(ext); + + ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr, + IRQF_ONESHOT, pdev->name, ext); + if (ret) { + dev_err(ext->dev, "Error requesting interrupt: %d\n", ret); + goto disable_sw_control; + } + + /* Unmask irqs */ + ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, + (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND | + CHT_WC_PWRSRC_ID_FLOAT)); + if (ret) { + dev_err(ext->dev, "Error writing irq-mask: %d\n", ret); + goto disable_sw_control; + } + + platform_set_drvdata(pdev, ext); + + return 0; + +disable_sw_control: + cht_wc_extcon_sw_control(ext, false); + return ret; +} + +static int cht_wc_extcon_remove(struct platform_device *pdev) +{ + struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); + + cht_wc_extcon_sw_control(ext, false); + + return 0; +} + +static const struct platform_device_id cht_wc_extcon_table[] = { + { .name = "cht_wcove_pwrsrc" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); + +static struct platform_driver cht_wc_extcon_driver = { + .probe = cht_wc_extcon_probe, + .remove = cht_wc_extcon_remove, + .id_table = cht_wc_extcon_table, + .driver = { + .name = "cht_wcove_pwrsrc", + }, +}; +module_platform_driver(cht_wc_extcon_driver); + +MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From c9d0f1d121cf038afc787ceb03d60798a1db389b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sun, 1 Jan 2017 21:14:26 +0900 Subject: extcon: Remove porting compatibility of swich class This patch removes the porting compatibility for switch class because there is no any usage and requirement of swich class over a couple of years. Signed-off-by: Chanwoo Choi --- Documentation/extcon/porting-android-switch-class | 123 ---------------------- drivers/extcon/extcon.c | 20 ---- 2 files changed, 143 deletions(-) delete mode 100644 Documentation/extcon/porting-android-switch-class diff --git a/Documentation/extcon/porting-android-switch-class b/Documentation/extcon/porting-android-switch-class deleted file mode 100644 index 49c81caef84d..000000000000 --- a/Documentation/extcon/porting-android-switch-class +++ /dev/null @@ -1,123 +0,0 @@ - - Staging/Android Switch Class Porting Guide - (linux/drivers/staging/android/switch) - (c) Copyright 2012 Samsung Electronics - -AUTHORS -MyungJoo Ham - -/***************************************************************** - * CHAPTER 1. * - * PORTING SWITCH CLASS DEVICE DRIVERS * - *****************************************************************/ - -****** STEP 1. Basic Functionality - No extcon extended feature, but switch features only. - -- struct switch_dev (fed to switch_dev_register/unregister) - @name: no change - @dev: no change - @index: drop (not used in switch device driver side anyway) - @state: no change - If you have used @state with magic numbers, keep it - at this step. - @print_name: no change but type change (switch_dev->extcon_dev) - @print_state: no change but type change (switch_dev->extcon_dev) - -- switch_dev_register(sdev, dev) - => extcon_dev_register(edev) - : type change (sdev->edev) - : remove second param('dev'). if edev has parent device, should store - 'dev' to 'edev.dev.parent' before registering extcon device -- switch_dev_unregister(sdev) - => extcon_dev_unregister(edev) - : no change but type change (sdev->edev) -- switch_get_state(sdev) - => extcon_get_state(edev) - : no change but type change (sdev->edev) and (return: int->u32) -- switch_set_state(sdev, state) - => extcon_set_state(edev, state) - : no change but type change (sdev->edev) and (state: int->u32) - -With this changes, the ex-switch extcon class device works as it once -worked as switch class device. However, it will now have additional -interfaces (both ABI and in-kernel API) and different ABI locations. -However, if CONFIG_ANDROID is enabled without CONFIG_ANDROID_SWITCH, -/sys/class/switch/* will be symbolically linked to /sys/class/extcon/ -so that they are still compatible with legacy userspace processes. - -****** STEP 2. Multistate (no more magic numbers in state value) - Extcon's extended features for switch device drivers with - complex features usually required magic numbers in state - value of switch_dev. With extcon, such magic numbers that - support multiple cables are no more required or supported. - - 1. Define cable names at edev->supported_cable. - 2. (Recommended) remove print_state callback. - 3. Use extcon_get_cable_state_(edev, index) or - extcon_get_cable_state(edev, cable_name) instead of - extcon_get_state(edev) if you intend to get a state of a specific - cable. Same for set_state. This way, you can remove the usage of - magic numbers in state value. - 4. Use extcon_update_state() if you are updating specific bits of - the state value. - -Example: a switch device driver w/ magic numbers for two cables. - "0x00": no cables connected. - "0x01": cable 1 connected - "0x02": cable 2 connected - "0x03": cable 1 and 2 connected - 1. edev->supported_cable = {"1", "2", NULL}; - 2. edev->print_state = NULL; - 3. extcon_get_cable_state_(edev, 0) shows cable 1's state. - extcon_get_cable_state(edev, "1") shows cable 1's state. - extcon_set_cable_state_(edev, 1) sets cable 2's state. - extcon_set_cable_state(edev, "2") sets cable 2's state - 4. extcon_update_state(edev, 0x01, 0) sets the least bit's 0. - -****** STEP 3. Notify other device drivers - - You can notify others of the cable attach/detach events with -notifier chains. - - At the side of other device drivers (the extcon device itself -does not need to get notified of its own events), there are two -methods to register notifier_block for cable events: -(a) for a specific cable or (b) for every cable. - - (a) extcon_register_interest(obj, extcon_name, cable_name, nb) - Example: want to get news of "MAX8997_MUIC"'s "USB" cable - - obj = kzalloc(sizeof(struct extcon_specific_cable_nb), - GFP_KERNEL); - nb->notifier_call = the_callback_to_handle_usb; - - extcon_register_intereset(obj, "MAX8997_MUIC", "USB", nb); - - (b) extcon_register_notifier(edev, nb) - Call nb for any changes in edev. - - Please note that in order to properly behave with method (a), -the extcon device driver should support multistate feature (STEP 2). - -****** STEP 4. Inter-cable relation (mutually exclusive) - - You can provide inter-cable mutually exclusiveness information -for an extcon device. When cables A and B are declared to be mutually -exclusive, the two cables cannot be in ATTACHED state simulteneously. - - -/***************************************************************** - * CHAPTER 2. * - * PORTING USERSPACE w/ SWITCH CLASS DEVICE SUPPORT * - *****************************************************************/ - -****** ABI Location - - If "CONFIG_ANDROID" is enabled, /sys/class/switch/* are created -as symbolic links to /sys/class/extcon/*. - - The two files of switch class, name and state, are provided with -extcon, too. When the multistate support (STEP 2 of CHAPTER 1.) is -not enabled or print_state callback is supplied, the output of -state ABI is same with switch class. diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index e7750545469f..6817e047cd40 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -230,9 +230,6 @@ struct extcon_cable { }; static struct class *extcon_class; -#if defined(CONFIG_ANDROID) -static struct class_compat *switch_class; -#endif /* CONFIG_ANDROID */ static LIST_HEAD(extcon_dev_list); static DEFINE_MUTEX(extcon_dev_list_lock); @@ -1032,12 +1029,6 @@ static int create_extcon_class(void) if (IS_ERR(extcon_class)) return PTR_ERR(extcon_class); extcon_class->dev_groups = extcon_groups; - -#if defined(CONFIG_ANDROID) - switch_class = class_compat_register("switch"); - if (WARN(!switch_class, "cannot allocate")) - return -ENOMEM; -#endif /* CONFIG_ANDROID */ } return 0; @@ -1259,10 +1250,6 @@ int extcon_dev_register(struct extcon_dev *edev) put_device(&edev->dev); goto err_dev; } -#if defined(CONFIG_ANDROID) - if (switch_class) - ret = class_compat_create_link(switch_class, &edev->dev, NULL); -#endif /* CONFIG_ANDROID */ spin_lock_init(&edev->lock); @@ -1350,10 +1337,6 @@ void extcon_dev_unregister(struct extcon_dev *edev) kfree(edev->cables); } -#if defined(CONFIG_ANDROID) - if (switch_class) - class_compat_remove_link(switch_class, &edev->dev, NULL); -#endif put_device(&edev->dev); } EXPORT_SYMBOL_GPL(extcon_dev_unregister); @@ -1424,9 +1407,6 @@ module_init(extcon_class_init); static void __exit extcon_class_exit(void) { -#if defined(CONFIG_ANDROID) - class_compat_unregister(switch_class); -#endif class_destroy(extcon_class); } module_exit(extcon_class_exit); -- cgit v1.2.3-70-g09d2 From 585cb239f4de6c11349e900dd8b4d8cf0825e802 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 5 Apr 2017 00:04:51 +0200 Subject: extcon: intel-cht-wc: Disable external 5v boost converter on probe Disable the 5v boost converter on probe in case it was left on by the BIOS, this fixes 2 problems: 1) This gets seen by the external battery charger as a valid Vbus supply and it then tries to feed Vsys from this creating a feedback loop which causes aprox. 300 mA extra battery drain (and unless we drive the external-charger-disable pin high it also tries to charge the battery causing even more feedback). 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply Since the external battery charger has its own 5v boost converter which does not have these issues, we simply turn the separate external 5v boost converter off and leave it off entirely. Signed-off-by: Hans de Goede Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-cht-wc.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index f1c43af7682b..e22df5f2bea5 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -64,6 +64,9 @@ #define CHT_WC_PWRSRC_ID_GND BIT(3) #define CHT_WC_PWRSRC_ID_FLOAT BIT(4) +#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d +#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0) + enum cht_wc_usb_id { USB_ID_OTG, USB_ID_GND, @@ -170,6 +173,23 @@ static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) dev_err(ext->dev, "Error writing phyctrl: %d\n", ret); } +static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, + bool enable) +{ + int ret, val; + + val = enable ? CHT_WC_VBUS_GPIO_CTLO_OUTPUT : 0; + + /* + * The 5V boost converter is enabled through a gpio on the PMIC, since + * there currently is no gpio driver we access the gpio reg directly. + */ + ret = regmap_update_bits(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, + CHT_WC_VBUS_GPIO_CTLO_OUTPUT, val); + if (ret) + dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret); +} + /* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, unsigned int cable, bool state) @@ -280,6 +300,21 @@ static int cht_wc_extcon_probe(struct platform_device *pdev) if (IS_ERR(ext->edev)) return PTR_ERR(ext->edev); + /* + * When a host-cable is detected the BIOS enables an external 5v boost + * converter to power connected devices there are 2 problems with this: + * 1) This gets seen by the external battery charger as a valid Vbus + * supply and it then tries to feed Vsys from this creating a + * feedback loop which causes aprox. 300 mA extra battery drain + * (and unless we drive the external-charger-disable pin high it + * also tries to charge the battery causing even more feedback). + * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply + * Since the external battery charger has its own 5v boost converter + * which does not have these issues, we simply turn the separate + * external 5v boost converter off and leave it off entirely. + */ + cht_wc_extcon_set_5v_boost(ext, false); + /* Enable sw control */ ret = cht_wc_extcon_sw_control(ext, true); if (ret) -- cgit v1.2.3-70-g09d2 From c42a880c3139cde603c5c83950ca7613aef7eedc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 5 Apr 2017 00:04:52 +0200 Subject: extcon: intel-cht-wc: Ignore failure to detect charger-type on host mode exit When we leave host-mode because the id-pin is no longer connected to ground, the 5v boost converter is normally still on, so we will see Vbus, but it is not from a charger (normally) so the charger-type detection will fail. This commit silences the cht_wc_extcon_get_charger() false-positive errors when we're leaving host mode. Signed-off-by: Hans de Goede Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-cht-wc.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index e22df5f2bea5..91a0023074af 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -96,6 +96,7 @@ struct cht_wc_extcon_data { struct regmap *regmap; struct extcon_dev *edev; unsigned int previous_cable; + bool usb_host; }; static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) @@ -112,7 +113,8 @@ static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) return USB_ID_FLOAT; } -static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext) +static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, + bool ignore_errors) { int ret, usbsrc, status; unsigned long timeout; @@ -135,6 +137,9 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext) } while (time_before(jiffies, timeout)); if (status != CHT_WC_USBSRC_STS_SUCCESS) { + if (ignore_errors) + return EXTCON_CHG_USB_SDP; /* Save fallback */ + if (status == CHT_WC_USBSRC_STS_FAIL) dev_warn(ext->dev, "Could not detect charger type\n"); else @@ -203,6 +208,8 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) { int ret, pwrsrc_sts, id; unsigned int cable = EXTCON_NONE; + /* Ignore errors in host mode, as the 5v boost converter is on then */ + bool ignore_get_charger_errors = ext->usb_host; ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); if (ret) { @@ -223,7 +230,7 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) goto set_state; } - ret = cht_wc_extcon_get_charger(ext); + ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors); if (ret >= 0) cable = ret; @@ -238,8 +245,8 @@ set_state: ext->previous_cable = cable; } - extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, - id == USB_ID_GND || id == USB_RID_A); + ext->usb_host = ((id == USB_ID_GND) || (id == USB_RID_A)); + extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host); } static irqreturn_t cht_wc_extcon_isr(int irq, void *data) -- cgit v1.2.3-70-g09d2 From 70641a0a84e1ae3c6eee3d47ed5a482f85b11e3e Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 29 Mar 2017 19:18:36 +0900 Subject: extcon: Use BIT() macro for the left-shift operation This patch just uses the BIT() macro to make the code simple. Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 6817e047cd40..f422a78ba342 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -377,7 +377,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, for (i = 0; i < edev->max_supported; i++) { count += sprintf(buf + count, "%s=%d\n", extcon_info[edev->supported_cable[i]].name, - !!(edev->state & (1 << i))); + !!(edev->state & BIT(i))); } return count; -- cgit v1.2.3-70-g09d2 From 8ff0c5664a634bab05c646960c1730b7fd33ed90 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 23 Mar 2017 17:08:08 +0100 Subject: drivers/misc: aspeed-lpc-ctrl: fix printk format warning again The format string is still broken after the first attempt to fix it: drivers/misc/aspeed-lpc-ctrl.c: In function 'aspeed_lpc_ctrl_probe': drivers/misc/aspeed-lpc-ctrl.c:232:17: error: format '%x' expects argument of type 'unsigned int', but argument 4 has type 'resource_size_t {aka long long unsigned int}' [-Werror=format=] We can actually just print the resource structure directly here. Fixes: 132c93d4215c ("drivers/misc: Aspeed LPC control fix compile error and warning") Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/misc/aspeed-lpc-ctrl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c index c654651a7b6d..b5439643f54b 100644 --- a/drivers/misc/aspeed-lpc-ctrl.c +++ b/drivers/misc/aspeed-lpc-ctrl.c @@ -229,8 +229,7 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) if (rc) dev_err(dev, "Unable to register device\n"); else - dev_info(dev, "Loaded at %pap (0x%08x)\n", - &lpc_ctrl->mem_base, lpc_ctrl->mem_size); + dev_info(dev, "Loaded at %pr\n", &resm); return rc; } -- cgit v1.2.3-70-g09d2 From f580ff0e404e5aad83f02093cd22f2475cad6e71 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 28 Mar 2017 16:10:32 +0200 Subject: aspeed-lpc-ctrl: include linux/types.h for uapi header The newly added header file triggers a sanity check: usr/include/linux/aspeed-lpc-ctrl.h:44: found __[us]{8,16,32,64} type without #include We should include linux/types.h explicitly to ensure the header can be included from user space. Fixes: 6c4e97678501 ("drivers/misc: Add Aspeed LPC control driver") Signed-off-by: Arnd Bergmann Acked-by: Joel Stanley Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/aspeed-lpc-ctrl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/aspeed-lpc-ctrl.h b/include/uapi/linux/aspeed-lpc-ctrl.h index f96fa995a3f0..c328c976c684 100644 --- a/include/uapi/linux/aspeed-lpc-ctrl.h +++ b/include/uapi/linux/aspeed-lpc-ctrl.h @@ -11,6 +11,7 @@ #define _UAPI_LINUX_ASPEED_LPC_CTRL_H #include +#include /* Window types */ #define ASPEED_LPC_CTRL_WINDOW_FLASH 1 -- cgit v1.2.3-70-g09d2 From 5c4c0106e944930315ad3ea8d77043bed13222eb Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 20 Mar 2017 15:04:02 +0200 Subject: mei: add pci driver ops shutdown handler. The shutdown handler quiesces the device, it performs link reset in order to close all connections and notify the device that is not longer managed by the driver. This is essentially a stripped down version of the PCI remove() function where only the necessary amount of work is done to stop any further activity. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/pci-me.c | 31 +++++++++++++++++++++++++++++-- drivers/misc/mei/pci-txe.c | 29 ++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 0a668fdfbbe9..8621a198a2ce 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -241,12 +241,39 @@ end: return err; } +/** + * mei_me_shutdown - Device Removal Routine + * + * @pdev: PCI device structure + * + * mei_me_shutdown is called from the reboot notifier + * it's a simplified version of remove so we go down + * faster. + */ +static void mei_me_shutdown(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + dev_dbg(&pdev->dev, "shutdown\n"); + mei_stop(dev); + + if (!pci_dev_run_wake(pdev)) + mei_me_unset_pm_domain(dev); + + mei_disable_interrupts(dev); + free_irq(pdev->irq, dev); +} + /** * mei_me_remove - Device Removal Routine * * @pdev: PCI device structure * - * mei_remove is called by the PCI subsystem to alert the driver + * mei_me_remove is called by the PCI subsystem to alert the driver * that it should release a PCI device. */ static void mei_me_remove(struct pci_dev *pdev) @@ -456,7 +483,7 @@ static struct pci_driver mei_me_driver = { .id_table = mei_me_pci_tbl, .probe = mei_me_probe, .remove = mei_me_remove, - .shutdown = mei_me_remove, + .shutdown = mei_me_shutdown, .driver.pm = MEI_ME_PM_OPS, }; diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index fe088b40daf9..f811cd524468 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -160,6 +160,33 @@ end: return err; } +/** + * mei_txe_remove - Device Shutdown Routine + * + * @pdev: PCI device structure + * + * mei_txe_shutdown is called from the reboot notifier + * it's a simplified version of remove so we go down + * faster. + */ +static void mei_txe_shutdown(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + dev_dbg(&pdev->dev, "shutdown\n"); + mei_stop(dev); + + if (!pci_dev_run_wake(pdev)) + mei_txe_unset_pm_domain(dev); + + mei_disable_interrupts(dev); + free_irq(pdev->irq, dev); +} + /** * mei_txe_remove - Device Removal Routine * @@ -386,7 +413,7 @@ static struct pci_driver mei_txe_driver = { .id_table = mei_txe_pci_tbl, .probe = mei_txe_probe, .remove = mei_txe_remove, - .shutdown = mei_txe_remove, + .shutdown = mei_txe_shutdown, .driver.pm = MEI_TXE_PM_OPS, }; -- cgit v1.2.3-70-g09d2 From 394a77d0bb63756871750400068d8b0c3582fba7 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 20 Mar 2017 15:04:03 +0200 Subject: mei: drop amthif internal client AMTHIF has special support in the mei drive, it handles multiplexing multiple user space connection above single me client connection. Since there is no additional addressing information there is a strict requirement on the traffic order on each connection and on the "read after write" order within the connection. This creates a lot of complexity mostly because the other client types do not necessarily fall under the same restriction. After carefully studying the use of the AMTHIF client, we came to conclusion that the multiplexing is not really utilized by any application and we may safely remove that support and significantly simplify the driver. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Makefile | 1 - drivers/misc/mei/amthif.c | 340 ------------------------------------------- drivers/misc/mei/bus.c | 6 - drivers/misc/mei/client.c | 14 +- drivers/misc/mei/client.h | 5 - drivers/misc/mei/init.c | 14 +- drivers/misc/mei/interrupt.c | 38 +---- drivers/misc/mei/main.c | 53 +------ drivers/misc/mei/mei_dev.h | 51 ------- 9 files changed, 15 insertions(+), 507 deletions(-) delete mode 100644 drivers/misc/mei/amthif.c diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 59e6b0aede34..12cceb011a23 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -8,7 +8,6 @@ mei-objs += hbm.o mei-objs += interrupt.o mei-objs += client.o mei-objs += main.o -mei-objs += amthif.o mei-objs += bus.o mei-objs += bus-fixup.o mei-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c deleted file mode 100644 index 0e7406ccb6dd..000000000000 --- a/drivers/misc/mei/amthif.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * - * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2012, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "mei_dev.h" -#include "hbm.h" -#include "client.h" - -const uuid_le mei_amthif_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, - 0xac, 0xa8, 0x46, 0xe0, - 0xff, 0x65, 0x81, 0x4c); - -/** - * mei_amthif_reset_params - initializes mei device iamthif - * - * @dev: the device structure - */ -void mei_amthif_reset_params(struct mei_device *dev) -{ - /* reset iamthif parameters. */ - dev->iamthif_canceled = false; - dev->iamthif_state = MEI_IAMTHIF_IDLE; - dev->iamthif_stall_timer = 0; - dev->iamthif_open_count = 0; -} - -/** - * mei_amthif_host_init - mei initialization amthif client. - * - * @dev: the device structure - * @me_cl: me client - * - * Return: 0 on success, <0 on failure. - */ -int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl) -{ - struct mei_cl *cl = &dev->iamthif_cl; - int ret; - - mutex_lock(&dev->device_lock); - - if (mei_cl_is_connected(cl)) { - ret = 0; - goto out; - } - - dev->iamthif_state = MEI_IAMTHIF_IDLE; - - mei_cl_init(cl, dev); - - ret = mei_cl_link(cl); - if (ret < 0) { - dev_err(dev->dev, "amthif: failed cl_link %d\n", ret); - goto out; - } - - ret = mei_cl_connect(cl, me_cl, NULL); - -out: - mutex_unlock(&dev->device_lock); - return ret; -} - -/** - * mei_amthif_read_start - queue message for sending read credential - * - * @cl: host client - * @fp: file pointer of message recipient - * - * Return: 0 on success, <0 on failure. - */ -static int mei_amthif_read_start(struct mei_cl *cl, const struct file *fp) -{ - struct mei_device *dev = cl->dev; - struct mei_cl_cb *cb; - - cb = mei_cl_enqueue_ctrl_wr_cb(cl, mei_cl_mtu(cl), MEI_FOP_READ, fp); - if (!cb) - return -ENOMEM; - - cl->rx_flow_ctrl_creds++; - - dev->iamthif_state = MEI_IAMTHIF_READING; - cl->fp = cb->fp; - - return 0; -} - -/** - * mei_amthif_run_next_cmd - send next amt command from queue - * - * @dev: the device structure - * - * Return: 0 on success, <0 on failure. - */ -int mei_amthif_run_next_cmd(struct mei_device *dev) -{ - struct mei_cl *cl = &dev->iamthif_cl; - struct mei_cl_cb *cb; - int ret; - - dev->iamthif_canceled = false; - - dev_dbg(dev->dev, "complete amthif cmd_list cb.\n"); - - cb = list_first_entry_or_null(&dev->amthif_cmd_list, typeof(*cb), list); - if (!cb) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->fp = NULL; - return 0; - } - - list_del_init(&cb->list); - dev->iamthif_state = MEI_IAMTHIF_WRITING; - cl->fp = cb->fp; - - ret = mei_cl_write(cl, cb); - if (ret < 0) - return ret; - - if (cb->completed) - cb->status = mei_amthif_read_start(cl, cb->fp); - - return 0; -} - -/** - * mei_amthif_write - write amthif data to amthif client - * - * @cl: host client - * @cb: mei call back struct - * - * Return: 0 on success, <0 on failure. - */ -int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb) -{ - - struct mei_device *dev = cl->dev; - - list_add_tail(&cb->list, &dev->amthif_cmd_list); - - /* - * The previous request is still in processing, queue this one. - */ - if (dev->iamthif_state != MEI_IAMTHIF_IDLE) - return 0; - - return mei_amthif_run_next_cmd(dev); -} - -/** - * mei_amthif_poll - the amthif poll function - * - * @file: pointer to file structure - * @wait: pointer to poll_table structure - * - * Return: poll mask - * - * Locking: called under "dev->device_lock" lock - */ -unsigned int mei_amthif_poll(struct file *file, poll_table *wait) -{ - struct mei_cl *cl = file->private_data; - struct mei_cl_cb *cb = mei_cl_read_cb(cl, file); - unsigned int mask = 0; - - poll_wait(file, &cl->rx_wait, wait); - if (cb) - mask |= POLLIN | POLLRDNORM; - - return mask; -} - -/** - * mei_amthif_irq_write - write iamthif command in irq thread context. - * - * @cl: private data of the file object. - * @cb: callback block. - * @cmpl_list: complete list. - * - * Return: 0, OK; otherwise, error. - */ -int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, - struct list_head *cmpl_list) -{ - int ret; - - ret = mei_cl_irq_write(cl, cb, cmpl_list); - if (ret) - return ret; - - if (cb->completed) - cb->status = mei_amthif_read_start(cl, cb->fp); - - return 0; -} - -/** - * mei_amthif_irq_read_msg - read routine after ISR to - * handle the read amthif message - * - * @cl: mei client - * @mei_hdr: header of amthif message - * @cmpl_list: completed callbacks list - * - * Return: -ENODEV if cb is NULL 0 otherwise; error message is in cb->status - */ -int mei_amthif_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct list_head *cmpl_list) -{ - struct mei_device *dev; - int ret; - - dev = cl->dev; - - if (dev->iamthif_state != MEI_IAMTHIF_READING) { - mei_irq_discard_msg(dev, mei_hdr); - return 0; - } - - ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); - if (ret) - return ret; - - if (!mei_hdr->msg_complete) - return 0; - - dev_dbg(dev->dev, "completed amthif read.\n "); - dev->iamthif_stall_timer = 0; - - return 0; -} - -/** - * mei_amthif_complete - complete amthif callback. - * - * @cl: host client - * @cb: callback block. - */ -void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb) -{ - struct mei_device *dev = cl->dev; - - dev_dbg(dev->dev, "completing amthif call back.\n"); - switch (cb->fop_type) { - case MEI_FOP_WRITE: - if (!cb->status) { - dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; - mei_schedule_stall_timer(dev); - mei_io_cb_free(cb); - return; - } - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->fp = NULL; - if (!dev->iamthif_canceled) { - /* - * in case of error enqueue the write cb to complete - * read list so it can be propagated to the reader - */ - list_add_tail(&cb->list, &cl->rd_completed); - wake_up_interruptible(&cl->rx_wait); - } else { - mei_io_cb_free(cb); - } - break; - case MEI_FOP_READ: - if (!dev->iamthif_canceled) { - list_add_tail(&cb->list, &cl->rd_completed); - dev_dbg(dev->dev, "amthif read completed\n"); - wake_up_interruptible(&cl->rx_wait); - } else { - mei_io_cb_free(cb); - } - - dev->iamthif_stall_timer = 0; - mei_amthif_run_next_cmd(dev); - break; - default: - WARN_ON(1); - } -} - -/** -* mei_amthif_release - the release function -* -* @dev: device structure -* @fp: pointer to file structure -* -* Return: 0 on success, <0 on error -*/ -int mei_amthif_release(struct mei_device *dev, struct file *fp) -{ - struct mei_cl *cl = fp->private_data; - - if (dev->iamthif_open_count > 0) - dev->iamthif_open_count--; - - if (cl->fp == fp && dev->iamthif_state != MEI_IAMTHIF_IDLE) { - - dev_dbg(dev->dev, "amthif canceled iamthif state %d\n", - dev->iamthif_state); - dev->iamthif_canceled = true; - } - - /* Don't clean ctrl_rd_list here, the reads has to be completed */ - mei_io_list_free_fp(&dev->amthif_cmd_list, fp); - mei_io_list_free_fp(&cl->rd_completed, fp); - - return 0; -} diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index df5f78ae3d25..d1928fdd0f43 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -1076,12 +1076,6 @@ void mei_cl_bus_rescan_work(struct work_struct *work) { struct mei_device *bus = container_of(work, struct mei_device, bus_rescan_work); - struct mei_me_client *me_cl; - - me_cl = mei_me_cl_by_uuid(bus, &mei_amthif_guid); - if (me_cl) - mei_amthif_host_init(bus, me_cl); - mei_me_cl_put(me_cl); mei_cl_bus_rescan(bus); } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index d3e3372424d6..be64969d986a 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -428,7 +428,7 @@ static inline void mei_io_list_free_cl(struct list_head *head, * @head: io list * @fp: file pointer (matching cb file object), may be NULL */ -void mei_io_list_free_fp(struct list_head *head, const struct file *fp) +static void mei_io_list_free_fp(struct list_head *head, const struct file *fp) { struct mei_cl_cb *cb, *next; @@ -554,7 +554,7 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) * @cl: host client to be initialized * @dev: mei device */ -void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) +static void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) { memset(cl, 0, sizeof(struct mei_cl)); init_waitqueue_head(&cl->wait); @@ -600,7 +600,6 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) int mei_cl_link(struct mei_cl *cl) { struct mei_device *dev; - long open_handle_count; int id; if (WARN_ON(!cl || !cl->dev)) @@ -614,8 +613,7 @@ int mei_cl_link(struct mei_cl *cl) return -EMFILE; } - open_handle_count = dev->open_handle_count + dev->iamthif_open_count; - if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { + if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { dev_err(dev->dev, "open_handle_count exceeded %d", MEI_MAX_OPEN_HANDLE_COUNT); return -EMFILE; @@ -649,8 +647,7 @@ int mei_cl_unlink(struct mei_cl *cl) if (!cl) return 0; - /* amthif might not be initialized */ - if (!cl->dev) + if (WARN_ON(!cl->dev)) return 0; dev = cl->dev; @@ -763,7 +760,6 @@ static void mei_cl_set_disconnected(struct mei_cl *cl) mei_io_list_free_cl(&dev->write_waiting_list, cl); mei_io_list_flush_cl(&dev->ctrl_rd_list, cl); mei_io_list_flush_cl(&dev->ctrl_wr_list, cl); - mei_io_list_free_cl(&dev->amthif_cmd_list, cl); mei_cl_wake_all(cl); cl->rx_flow_ctrl_creds = 0; cl->tx_flow_ctrl_creds = 0; @@ -1478,7 +1474,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) return -ENOTTY; } - if (mei_cl_is_fixed_address(cl) || cl == &dev->iamthif_cl) + if (mei_cl_is_fixed_address(cl)) return 0; /* HW currently supports only one pending read */ diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 545ae319ba90..5371df4d8af3 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -83,15 +83,12 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl) * MEI IO Functions */ void mei_io_cb_free(struct mei_cl_cb *priv_cb); -void mei_io_list_free_fp(struct list_head *head, const struct file *fp); /* * MEI Host Client Functions */ struct mei_cl *mei_cl_allocate(struct mei_device *dev); -void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); - int mei_cl_link(struct mei_cl *cl); int mei_cl_unlink(struct mei_cl *cl); @@ -205,8 +202,6 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp); -int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr, - struct list_head *cmpl_list); int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb); int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 13c55b8f9261..c8ad9ee7cb80 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -146,18 +146,9 @@ int mei_reset(struct mei_device *dev) /* fall through and remove the sw state even if hw reset has failed */ /* no need to clean up software state in case of power up */ - if (state != MEI_DEV_INITIALIZING && - state != MEI_DEV_POWER_UP) { - - /* remove all waiting requests */ + if (state != MEI_DEV_INITIALIZING && state != MEI_DEV_POWER_UP) mei_cl_all_disconnect(dev); - /* remove entry if already in list */ - dev_dbg(dev->dev, "remove iamthif from the file list.\n"); - mei_cl_unlink(&dev->iamthif_cl); - mei_amthif_reset_params(dev); - } - mei_hbm_reset(dev); dev->rd_msg_hdr = 0; @@ -401,9 +392,6 @@ void mei_device_init(struct mei_device *dev, INIT_WORK(&dev->reset_work, mei_reset_work); INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work); - INIT_LIST_HEAD(&dev->iamthif_cl.link); - INIT_LIST_HEAD(&dev->amthif_cmd_list); - bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); dev->open_handle_count = 0; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 406e9e2b2fff..c14e35201721 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -47,10 +47,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list) list_del_init(&cb->list); dev_dbg(dev->dev, "completing call back.\n"); - if (cl == &dev->iamthif_cl) - mei_amthif_complete(cl, cb); - else - mei_cl_complete(cl, cb); + mei_cl_complete(cl, cb); } } EXPORT_SYMBOL_GPL(mei_irq_compl_handler); @@ -76,7 +73,7 @@ static inline int mei_cl_hbm_equal(struct mei_cl *cl, * @dev: mei device * @hdr: message header */ -void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) +static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) { /* * no need to check for size as it is guarantied @@ -96,9 +93,9 @@ void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) * * Return: always 0 */ -int mei_cl_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct list_head *cmpl_list) +static int mei_cl_irq_read_msg(struct mei_cl *cl, + struct mei_msg_hdr *mei_hdr, + struct list_head *cmpl_list) { struct mei_device *dev = cl->dev; struct mei_cl_cb *cb; @@ -313,11 +310,7 @@ int mei_irq_read_handler(struct mei_device *dev, goto end; } - if (cl == &dev->iamthif_cl) { - ret = mei_amthif_irq_read_msg(cl, mei_hdr, cmpl_list); - } else { - ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); - } + ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); reset_slots: @@ -423,10 +416,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) dev_dbg(dev->dev, "complete write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_list, list) { cl = cb->cl; - if (cl == &dev->iamthif_cl) - ret = mei_amthif_irq_write(cl, cb, cmpl_list); - else - ret = mei_cl_irq_write(cl, cb, cmpl_list); + ret = mei_cl_irq_write(cl, cb, cmpl_list); if (ret) return ret; } @@ -512,20 +502,6 @@ void mei_timer(struct work_struct *work) } } - if (!mei_cl_is_connected(&dev->iamthif_cl)) - goto out; - - if (dev->iamthif_stall_timer) { - if (--dev->iamthif_stall_timer == 0) { - dev_err(dev->dev, "timer: amthif hanged.\n"); - mei_reset(dev); - - mei_amthif_run_next_cmd(dev); - goto out; - } - reschedule_timer = true; - } - out: if (dev->dev_state != MEI_DEV_DISABLED && reschedule_timer) mei_schedule_stall_timer(dev); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index bf816449cd40..9802c5930c10 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -103,10 +103,7 @@ static int mei_release(struct inode *inode, struct file *file) dev = cl->dev; mutex_lock(&dev->device_lock); - if (cl == &dev->iamthif_cl) { - rets = mei_amthif_release(dev, file); - goto out; - } + rets = mei_cl_disconnect(cl); mei_cl_flush_queues(cl, file); @@ -117,7 +114,7 @@ static int mei_release(struct inode *inode, struct file *file) file->private_data = NULL; kfree(cl); -out: + mutex_unlock(&dev->device_lock); return rets; } @@ -182,8 +179,6 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - -again: mutex_unlock(&dev->device_lock); if (wait_event_interruptible(cl->rx_wait, !list_empty(&cl->rd_completed) || @@ -201,14 +196,6 @@ again: cb = mei_cl_read_cb(cl, file); if (!cb) { - /* - * For amthif all the waiters are woken up, - * but only fp with matching cb->fp get the cb, - * the others have to return to wait on read. - */ - if (cl == &dev->iamthif_cl) - goto again; - rets = 0; goto out; } @@ -319,13 +306,6 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, goto out; } - if (cl == &dev->iamthif_cl) { - rets = mei_amthif_write(cl, cb); - if (!rets) - rets = length; - goto out; - } - rets = mei_cl_write(cl, cb); out: mutex_unlock(&dev->device_lock); @@ -388,30 +368,6 @@ static int mei_ioctl_connect_client(struct file *file, dev_dbg(dev->dev, "FW Client - Max Msg Len = %d\n", me_cl->props.max_msg_length); - /* if we're connecting to amthif client then we will use the - * existing connection - */ - if (uuid_le_cmp(data->in_client_uuid, mei_amthif_guid) == 0) { - dev_dbg(dev->dev, "FW Client is amthi\n"); - if (!mei_cl_is_connected(&dev->iamthif_cl)) { - rets = -ENODEV; - goto end; - } - mei_cl_unlink(cl); - - kfree(cl); - cl = NULL; - dev->iamthif_open_count++; - file->private_data = &dev->iamthif_cl; - - client = &data->out_client_properties; - client->max_msg_length = me_cl->props.max_msg_length; - client->protocol_version = me_cl->props.protocol_version; - rets = dev->iamthif_cl.status; - - goto end; - } - /* prepare the output buffer */ client = &data->out_client_properties; client->max_msg_length = me_cl->props.max_msg_length; @@ -615,11 +571,6 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) mask |= POLLPRI; } - if (cl == &dev->iamthif_cl) { - mask |= mei_amthif_poll(file, wait); - goto out; - } - if (req_events & (POLLIN | POLLRDNORM)) { poll_wait(file, &cl->rx_wait, wait); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d41aac53a2ac..63a67c99fc78 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -26,12 +26,6 @@ #include "hw.h" #include "hbm.h" - -/* - * AMTHI Client UUID - */ -extern const uuid_le mei_amthif_guid; - #define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32)) /* @@ -78,12 +72,6 @@ enum mei_dev_state { const char *mei_dev_state_str(int state); -enum iamthif_states { - MEI_IAMTHIF_IDLE, - MEI_IAMTHIF_WRITING, - MEI_IAMTHIF_READING, -}; - enum mei_file_transaction_states { MEI_IDLE, MEI_WRITING, @@ -418,13 +406,6 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @allow_fixed_address: allow user space to connect a fixed client * @override_fixed_address: force allow fixed address behavior * - * @amthif_cmd_list : amthif list for cmd waiting - * @iamthif_cl : amthif host client - * @iamthif_open_count : number of opened amthif connections - * @iamthif_stall_timer : timer to detect amthif hang - * @iamthif_state : amthif processor state - * @iamthif_canceled : current amthif command is canceled - * * @reset_work : work item for the device reset * @bus_rescan_work : work item for the bus rescan * @@ -500,14 +481,6 @@ struct mei_device { bool allow_fixed_address; bool override_fixed_address; - /* amthif list for cmd waiting */ - struct list_head amthif_cmd_list; - struct mei_cl iamthif_cl; - long iamthif_open_count; - u32 iamthif_stall_timer; - enum iamthif_states iamthif_state; - bool iamthif_canceled; - struct work_struct reset_work; struct work_struct bus_rescan_work; @@ -578,28 +551,6 @@ int mei_irq_read_handler(struct mei_device *dev, int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list); void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list); -/* - * AMTHIF - AMT Host Interface Functions - */ -void mei_amthif_reset_params(struct mei_device *dev); - -int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl); - -unsigned int mei_amthif_poll(struct file *file, poll_table *wait); - -int mei_amthif_release(struct mei_device *dev, struct file *file); - -int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb); -int mei_amthif_run_next_cmd(struct mei_device *dev); -int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, - struct list_head *cmpl_list); - -void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb); -int mei_amthif_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct list_head *cmpl_list); -int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); - /* * Register Access Function */ @@ -711,8 +662,6 @@ bool mei_hbuf_acquire(struct mei_device *dev); bool mei_write_is_idle(struct mei_device *dev); -void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr); - #if IS_ENABLED(CONFIG_DEBUG_FS) int mei_dbgfs_register(struct mei_device *dev, const char *name); void mei_dbgfs_deregister(struct mei_device *dev); -- cgit v1.2.3-70-g09d2 From 3fcbab6c4a962259b8cb83aea813a4c7fbe11f77 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 20 Mar 2017 15:04:04 +0200 Subject: mei: hbm: remove variable length arrays Though VLA are supported by CC99 there are many cavities and should be avoided. 'const size_t len = sizeof()' that we used may not be set at the compile time hence generating VLA code. This fixes also sparse warning warning: Variable length array is used type. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index ba3a774c8d71..fe6595fe94f1 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -166,9 +166,8 @@ void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len) * * Return: 0 on success, <0 on failure. */ -static inline -int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl, - u8 hbm_cmd, u8 *buf, size_t len) +static inline int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl, + u8 hbm_cmd, void *buf, size_t len) { struct mei_msg_hdr mei_hdr; @@ -632,11 +631,11 @@ static int mei_hbm_stop_req(struct mei_device *dev) */ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_flow_control); - u8 buf[len]; + struct hbm_flow_control req; cl_dbg(dev, cl, "sending flow control\n"); - return mei_hbm_cl_write(dev, cl, MEI_FLOW_CONTROL_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, MEI_FLOW_CONTROL_CMD, + &req, sizeof(req)); } /** @@ -710,10 +709,10 @@ static void mei_hbm_cl_tx_flow_ctrl_creds_res(struct mei_device *dev, */ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_client_connect_request); - u8 buf[len]; + struct hbm_client_connect_request req; - return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_REQ_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_REQ_CMD, + &req, sizeof(req)); } /** @@ -726,10 +725,10 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) */ int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_client_connect_response); - u8 buf[len]; + struct hbm_client_connect_response resp; - return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_RES_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_RES_CMD, + &resp, sizeof(resp)); } /** @@ -763,10 +762,10 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, struct mei_cl *cl, */ int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl) { - const size_t len = sizeof(struct hbm_client_connect_request); - u8 buf[len]; + struct hbm_client_connect_request req; - return mei_hbm_cl_write(dev, cl, CLIENT_CONNECT_REQ_CMD, buf, len); + return mei_hbm_cl_write(dev, cl, CLIENT_CONNECT_REQ_CMD, + &req, sizeof(req)); } /** -- cgit v1.2.3-70-g09d2 From dd04eecc41ae7cf139593b400e74defad62cec54 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 20 Mar 2017 15:04:05 +0200 Subject: mei: bus: elminate variable length arrays Though VLA are supported by CC99 there are many cavities and should be avoided. 'const size_t len = sizeof()' that we used may not be set at the compile time hence generating VLA code. This fixes also sparse warning warning: Variable length array is used type. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 29f2daed37e0..0208c4b027c5 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -110,12 +110,13 @@ struct mkhi_msg { u8 data[0]; } __packed; +#define MKHI_OSVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \ + sizeof(struct mkhi_fwcaps) + \ + sizeof(struct mei_os_ver)) static int mei_osver(struct mei_cl_device *cldev) { - const size_t size = sizeof(struct mkhi_msg_hdr) + - sizeof(struct mkhi_fwcaps) + - sizeof(struct mei_os_ver); - char buf[size]; + const size_t size = MKHI_OSVER_BUF_LEN; + char buf[MKHI_OSVER_BUF_LEN]; struct mkhi_msg *req; struct mkhi_fwcaps *fwcaps; struct mei_os_ver *os_ver; -- cgit v1.2.3-70-g09d2 From 58cde1a64c5e140e6ba498aaa406e063957e35df Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 20 Mar 2017 15:04:06 +0200 Subject: mei: implement fsync When write() returns successfully, it is only assumed that a message was successfully queued. Add fsync syscall implementation to help user-space ensure that all data is written. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/main.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 9802c5930c10..e825f013e54e 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -585,6 +585,77 @@ out: return mask; } +/** + * mei_cl_is_write_queued - check if the client has pending writes. + * + * @cl: writing host client + * + * Return: true if client is writing, false otherwise. + */ +static bool mei_cl_is_write_queued(struct mei_cl *cl) +{ + struct mei_device *dev = cl->dev; + struct mei_cl_cb *cb; + + list_for_each_entry(cb, &dev->write_list, list) + if (cb->cl == cl) + return true; + list_for_each_entry(cb, &dev->write_waiting_list, list) + if (cb->cl == cl) + return true; + return false; +} + +/** + * mei_fsync - the fsync handler + * + * @fp: pointer to file structure + * @start: unused + * @end: unused + * @datasync: unused + * + * Return: 0 on success, -ENODEV if client is not connected + */ +static int mei_fsync(struct file *fp, loff_t start, loff_t end, int datasync) +{ + struct mei_cl *cl = fp->private_data; + struct mei_device *dev; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (dev->dev_state != MEI_DEV_ENABLED || !mei_cl_is_connected(cl)) { + rets = -ENODEV; + goto out; + } + + while (mei_cl_is_write_queued(cl)) { + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE || + !mei_cl_is_connected(cl)); + mutex_lock(&dev->device_lock); + if (rets) { + if (signal_pending(current)) + rets = -EINTR; + goto out; + } + if (!mei_cl_is_connected(cl)) { + rets = -ENODEV; + goto out; + } + } + rets = 0; +out: + mutex_unlock(&dev->device_lock); + return rets; +} + /** * mei_fasync - asynchronous io support * @@ -700,6 +771,7 @@ static const struct file_operations mei_fops = { .release = mei_release, .write = mei_write, .poll = mei_poll, + .fsync = mei_fsync, .fasync = mei_fasync, .llseek = no_llseek }; -- cgit v1.2.3-70-g09d2 From eeedc5421dd3b51de73e6106405c5c77f920f281 Mon Sep 17 00:00:00 2001 From: Mike Leach Date: Mon, 27 Mar 2017 11:09:33 -0600 Subject: coresight: Fixes coresight DT parse to get correct output port ID. Corrected to get the port numbering to allow programmable replicator driver to operate correctly. By convention, CoreSight devices number ports, not endpoints in the .dts files:- port { reg endpoint { } } Existing code read endpoint number - always 0x0, rather than the correct port number. Signed-off-by: Mike Leach Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/of_coresight.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwtracing/coresight/of_coresight.c b/drivers/hwtracing/coresight/of_coresight.c index 629e031b7456..09142e99e915 100644 --- a/drivers/hwtracing/coresight/of_coresight.c +++ b/drivers/hwtracing/coresight/of_coresight.c @@ -149,7 +149,7 @@ struct coresight_platform_data *of_get_coresight_platform_data( continue; /* The local out port number */ - pdata->outports[i] = endpoint.id; + pdata->outports[i] = endpoint.port; /* * Get a handle on the remote port and parent -- cgit v1.2.3-70-g09d2 From 6b539d274e49a7f4b53e0adc55d19041e3b7a863 Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Thu, 23 Mar 2017 19:34:24 -0500 Subject: fpga: altera_freeze_bridge: Constify ops The ops are not changing, make them const. Signed-off-by: Moritz Fischer Cc: Alan Tull Cc: linux-kernel@vger.kernel.org Cc: linux-fpga@vger.kernel.org Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/altera-freeze-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c index 8dcd9fb22cb9..8c1bc7e7ee69 100644 --- a/drivers/fpga/altera-freeze-bridge.c +++ b/drivers/fpga/altera-freeze-bridge.c @@ -203,7 +203,7 @@ static int altera_freeze_br_enable_show(struct fpga_bridge *bridge) return priv->enable; } -static struct fpga_bridge_ops altera_freeze_br_br_ops = { +static const struct fpga_bridge_ops altera_freeze_br_br_ops = { .enable_set = altera_freeze_br_enable_set, .enable_show = altera_freeze_br_enable_show, }; -- cgit v1.2.3-70-g09d2 From 12cc5a123a173d6236903af6f3d9d44c81723751 Mon Sep 17 00:00:00 2001 From: Anatolij Gustschin Date: Thu, 23 Mar 2017 19:34:25 -0500 Subject: dt: bindings: fpga: add xilinx slave-serial binding description Add dt binding documentation details for Xilinx FPGA configuration over slave serial interface. Signed-off-by: Anatolij Gustschin Acked-by: Moritz Fischer Acked-by: Rob Herring Acked-by: Michal Simek Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- .../bindings/fpga/xilinx-slave-serial.txt | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Documentation/devicetree/bindings/fpga/xilinx-slave-serial.txt diff --git a/Documentation/devicetree/bindings/fpga/xilinx-slave-serial.txt b/Documentation/devicetree/bindings/fpga/xilinx-slave-serial.txt new file mode 100644 index 000000000000..9766f7472f51 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/xilinx-slave-serial.txt @@ -0,0 +1,44 @@ +Xilinx Slave Serial SPI FPGA Manager + +Xilinx Spartan-6 FPGAs support a method of loading the bitstream over +what is referred to as "slave serial" interface. +The slave serial link is not technically SPI, and might require extra +circuits in order to play nicely with other SPI slaves on the same bus. + +See https://www.xilinx.com/support/documentation/user_guides/ug380.pdf + +Required properties: +- compatible: should contain "xlnx,fpga-slave-serial" +- reg: spi chip select of the FPGA +- prog_b-gpios: config pin (referred to as PROGRAM_B in the manual) +- done-gpios: config status pin (referred to as DONE in the manual) + +Example for full FPGA configuration: + + fpga-region0 { + compatible = "fpga-region"; + fpga-mgr = <&fpga_mgr_spi>; + #address-cells = <0x1>; + #size-cells = <0x1>; + }; + + spi1: spi@10680 { + compatible = "marvell,armada-xp-spi", "marvell,orion-spi"; + pinctrl-0 = <&spi0_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + cell-index = <1>; + interrupts = <92>; + clocks = <&coreclk 0>; + status = "okay"; + + fpga_mgr_spi: fpga-mgr@0 { + compatible = "xlnx,fpga-slave-serial"; + spi-max-frequency = <60000000>; + spi-cpha; + reg = <0>; + done-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; + prog_b-gpios = <&gpio0 29 GPIO_ACTIVE_LOW>; + }; + }; -- cgit v1.2.3-70-g09d2 From 061c97d13f1a69c0edcab4dc6e97788e5bf3230f Mon Sep 17 00:00:00 2001 From: Anatolij Gustschin Date: Thu, 23 Mar 2017 19:34:26 -0500 Subject: fpga manager: Add Xilinx slave serial SPI driver The driver loads FPGA firmware over SPI, using the "slave serial" configuration interface on Xilinx FPGAs. Signed-off-by: Anatolij Gustschin Acked-by: Michal Simek Reviewed-by: Moritz Fischer Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 7 ++ drivers/fpga/Makefile | 1 + drivers/fpga/xilinx-spi.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 drivers/fpga/xilinx-spi.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index dee470f9113f..c81cb7d9fbd5 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -46,6 +46,13 @@ config FPGA_MGR_TS73XX FPGA manager driver support for the Altera Cyclone II FPGA present on the TS-73xx SBC boards. +config FPGA_MGR_XILINX_SPI + tristate "Xilinx Configuration over Slave Serial (SPI)" + depends on SPI + help + FPGA manager driver support for Xilinx FPGA configuration + over slave serial interface. + config FPGA_MGR_ZYNQ_FPGA tristate "Xilinx Zynq FPGA" depends on ARCH_ZYNQ || COMPILE_TEST diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index a5ee3ffe8b18..c6f5d740b946 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o +obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o # FPGA Bridge Drivers diff --git a/drivers/fpga/xilinx-spi.c b/drivers/fpga/xilinx-spi.c new file mode 100644 index 000000000000..9b62a4c2a3df --- /dev/null +++ b/drivers/fpga/xilinx-spi.c @@ -0,0 +1,198 @@ +/* + * Xilinx Spartan6 Slave Serial SPI Driver + * + * Copyright (C) 2017 DENX Software Engineering + * + * Anatolij Gustschin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * Manage Xilinx FPGA firmware that is loaded over SPI using + * the slave serial configuration interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct xilinx_spi_conf { + struct spi_device *spi; + struct gpio_desc *prog_b; + struct gpio_desc *done; +}; + +static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) +{ + struct xilinx_spi_conf *conf = mgr->priv; + + if (!gpiod_get_value(conf->done)) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static int xilinx_spi_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct xilinx_spi_conf *conf = mgr->priv; + const size_t prog_latency_7500us = 7500; + const size_t prog_pulse_1us = 1; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + + gpiod_set_value(conf->prog_b, 1); + + udelay(prog_pulse_1us); /* min is 500 ns */ + + gpiod_set_value(conf->prog_b, 0); + + if (gpiod_get_value(conf->done)) { + dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); + return -EIO; + } + + /* program latency */ + usleep_range(prog_latency_7500us, prog_latency_7500us + 100); + return 0; +} + +static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct xilinx_spi_conf *conf = mgr->priv; + const char *fw_data = buf; + const char *fw_data_end = fw_data + count; + + while (fw_data < fw_data_end) { + size_t remaining, stride; + int ret; + + remaining = fw_data_end - fw_data; + stride = min_t(size_t, remaining, SZ_4K); + + ret = spi_write(conf->spi, fw_data, stride); + if (ret) { + dev_err(&mgr->dev, "SPI error in firmware write: %d\n", + ret); + return ret; + } + fw_data += stride; + } + + return 0; +} + +static int xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf) +{ + struct spi_device *spi = conf->spi; + const u8 din_data[1] = { 0xff }; + int ret; + + ret = spi_write(conf->spi, din_data, sizeof(din_data)); + if (ret) + dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret); + + return ret; +} + +static int xilinx_spi_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct xilinx_spi_conf *conf = mgr->priv; + unsigned long timeout; + int ret; + + if (gpiod_get_value(conf->done)) + return xilinx_spi_apply_cclk_cycles(conf); + + timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + + while (time_before(jiffies, timeout)) { + + ret = xilinx_spi_apply_cclk_cycles(conf); + if (ret) + return ret; + + if (gpiod_get_value(conf->done)) + return xilinx_spi_apply_cclk_cycles(conf); + } + + dev_err(&mgr->dev, "Timeout after config data transfer.\n"); + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops xilinx_spi_ops = { + .state = xilinx_spi_state, + .write_init = xilinx_spi_write_init, + .write = xilinx_spi_write, + .write_complete = xilinx_spi_write_complete, +}; + +static int xilinx_spi_probe(struct spi_device *spi) +{ + struct xilinx_spi_conf *conf; + + conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + conf->spi = spi; + + /* PROGRAM_B is active low */ + conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW); + if (IS_ERR(conf->prog_b)) { + dev_err(&spi->dev, "Failed to get PROGRAM_B gpio: %ld\n", + PTR_ERR(conf->prog_b)); + return PTR_ERR(conf->prog_b); + } + + conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN); + if (IS_ERR(conf->done)) { + dev_err(&spi->dev, "Failed to get DONE gpio: %ld\n", + PTR_ERR(conf->done)); + return PTR_ERR(conf->done); + } + + return fpga_mgr_register(&spi->dev, "Xilinx Slave Serial FPGA Manager", + &xilinx_spi_ops, conf); +} + +static int xilinx_spi_remove(struct spi_device *spi) +{ + fpga_mgr_unregister(&spi->dev); + + return 0; +} + +static const struct of_device_id xlnx_spi_of_match[] = { + { .compatible = "xlnx,fpga-slave-serial", }, + {} +}; +MODULE_DEVICE_TABLE(of, xlnx_spi_of_match); + +static struct spi_driver xilinx_slave_spi_driver = { + .driver = { + .name = "xlnx-slave-spi", + .of_match_table = of_match_ptr(xlnx_spi_of_match), + }, + .probe = xilinx_spi_probe, + .remove = xilinx_spi_remove, +}; + +module_spi_driver(xilinx_slave_spi_driver) + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Anatolij Gustschin "); +MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SPI"); -- cgit v1.2.3-70-g09d2 From 42d5ec954719917e2b7a9160fe05d2316eece5bf Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Thu, 23 Mar 2017 19:34:27 -0500 Subject: fpga: add config complete timeout Adding timeout for maximum allowed time for FPGA to go to operating mode after a FPGA region has been programmed. Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-region.c | 3 +++ include/linux/fpga/fpga-mgr.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 2fe2a52c66ca..ae4c61aada99 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -385,6 +385,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region, of_property_read_u32(nd->overlay, "region-freeze-timeout-us", &info->disable_timeout_us); + of_property_read_u32(nd->overlay, "config-complete-timeout-us", + &info->config_complete_timeout_us); + /* If FPGA was externally programmed, don't specify firmware */ if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) { pr_err("error: specified firmware and external-fpga-config"); diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index e2ef94fd37af..b4ac24c4411d 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -77,11 +77,14 @@ enum fpga_mgr_states { * @flags: boolean flags as defined above * @enable_timeout_us: maximum time to enable traffic through bridge (uSec) * @disable_timeout_us: maximum time to disable traffic through bridge (uSec) + * @config_complete_timeout_us: maximum time for FPGA to switch to operating + * status in the write_complete op. */ struct fpga_image_info { u32 flags; u32 enable_timeout_us; u32 disable_timeout_us; + u32 config_complete_timeout_us; }; /** -- cgit v1.2.3-70-g09d2 From d201cc17a8a31cc6c4f3944988fe9e2f04b021fb Mon Sep 17 00:00:00 2001 From: Matthew Gerlach Date: Thu, 23 Mar 2017 19:34:28 -0500 Subject: fpga pr ip: Core driver support for Altera Partial Reconfiguration IP. Adding the core functions necessary for a fpga-mgr driver for the Altera Partial IP component. It is intended for these functions to be used by the various bus implementations like the platform bus or the PCIe bus. Signed-off-by: Matthew Gerlach Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 5 + drivers/fpga/Makefile | 1 + drivers/fpga/altera-pr-ip-core.c | 220 +++++++++++++++++++++++++++++++++ include/linux/fpga/altera-pr-ip-core.h | 29 +++++ 4 files changed, 255 insertions(+) create mode 100644 drivers/fpga/altera-pr-ip-core.c create mode 100644 include/linux/fpga/altera-pr-ip-core.h diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index c81cb7d9fbd5..e2cc0ad5656c 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -83,6 +83,11 @@ config ALTERA_FREEZE_BRIDGE isolate one region of the FPGA from the busses while that region is being reprogrammed. +config ALTERA_PR_IP_CORE + tristate "Altera Partial Reconfiguration IP Core" + help + Core driver support for Altera Partial Reconfiguration IP component + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index c6f5d740b946..968fd51cb619 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o +obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o # FPGA Bridge Drivers obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o diff --git a/drivers/fpga/altera-pr-ip-core.c b/drivers/fpga/altera-pr-ip-core.c new file mode 100644 index 000000000000..a7b31f9797ce --- /dev/null +++ b/drivers/fpga/altera-pr-ip-core.c @@ -0,0 +1,220 @@ +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016-2017 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include + +#define ALT_PR_DATA_OFST 0x00 +#define ALT_PR_CSR_OFST 0x04 + +#define ALT_PR_CSR_PR_START BIT(0) +#define ALT_PR_CSR_STATUS_SFT 2 +#define ALT_PR_CSR_STATUS_MSK (7 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_NRESET (0 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_ERR (1 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_CRC_ERR (2 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_BAD_BITS (3 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_IN_PROG (4 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_SUCCESS (5 << ALT_PR_CSR_STATUS_SFT) + +struct alt_pr_priv { + void __iomem *reg_base; +}; + +static enum fpga_mgr_states alt_pr_fpga_state(struct fpga_manager *mgr) +{ + struct alt_pr_priv *priv = mgr->priv; + const char *err = "unknown"; + enum fpga_mgr_states ret = FPGA_MGR_STATE_UNKNOWN; + u32 val; + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + val &= ALT_PR_CSR_STATUS_MSK; + + switch (val) { + case ALT_PR_CSR_STATUS_NRESET: + return FPGA_MGR_STATE_RESET; + + case ALT_PR_CSR_STATUS_PR_ERR: + err = "pr error"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_CRC_ERR: + err = "crc error"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_BAD_BITS: + err = "bad bits"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_PR_IN_PROG: + return FPGA_MGR_STATE_WRITE; + + case ALT_PR_CSR_STATUS_PR_SUCCESS: + return FPGA_MGR_STATE_OPERATING; + + default: + break; + } + + dev_err(&mgr->dev, "encountered error code %d (%s) in %s()\n", + val, err, __func__); + return ret; +} + +static int alt_pr_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct alt_pr_priv *priv = mgr->priv; + u32 val; + + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&mgr->dev, "%s Partial Reconfiguration flag not set\n", + __func__); + return -EINVAL; + } + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + if (val & ALT_PR_CSR_PR_START) { + dev_err(&mgr->dev, + "%s Partial Reconfiguration already started\n", + __func__); + return -EINVAL; + } + + writel(val | ALT_PR_CSR_PR_START, priv->reg_base + ALT_PR_CSR_OFST); + + return 0; +} + +static int alt_pr_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct alt_pr_priv *priv = mgr->priv; + u32 *buffer_32 = (u32 *)buf; + size_t i = 0; + + if (count <= 0) + return -EINVAL; + + /* Write out the complete 32-bit chunks */ + while (count >= sizeof(u32)) { + writel(buffer_32[i++], priv->reg_base); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks */ + switch (count) { + case 3: + writel(buffer_32[i++] & 0x00ffffff, priv->reg_base); + break; + case 2: + writel(buffer_32[i++] & 0x0000ffff, priv->reg_base); + break; + case 1: + writel(buffer_32[i++] & 0x000000ff, priv->reg_base); + break; + case 0: + break; + default: + /* This will never happen */ + return -EFAULT; + } + + if (alt_pr_fpga_state(mgr) == FPGA_MGR_STATE_WRITE_ERR) + return -EIO; + + return 0; +} + +static int alt_pr_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + u32 i = 0; + + do { + switch (alt_pr_fpga_state(mgr)) { + case FPGA_MGR_STATE_WRITE_ERR: + return -EIO; + + case FPGA_MGR_STATE_OPERATING: + dev_info(&mgr->dev, + "successful partial reconfiguration\n"); + return 0; + + default: + break; + } + udelay(1); + } while (info->config_complete_timeout_us > i++); + + dev_err(&mgr->dev, "timed out waiting for write to complete\n"); + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops alt_pr_ops = { + .state = alt_pr_fpga_state, + .write_init = alt_pr_fpga_write_init, + .write = alt_pr_fpga_write, + .write_complete = alt_pr_fpga_write_complete, +}; + +int alt_pr_register(struct device *dev, void __iomem *reg_base) +{ + struct alt_pr_priv *priv; + u32 val; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->reg_base = reg_base; + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + dev_dbg(dev, "%s status=%d start=%d\n", __func__, + (val & ALT_PR_CSR_STATUS_MSK) >> ALT_PR_CSR_STATUS_SFT, + (int)(val & ALT_PR_CSR_PR_START)); + + return fpga_mgr_register(dev, dev_name(dev), &alt_pr_ops, priv); +} +EXPORT_SYMBOL_GPL(alt_pr_register); + +int alt_pr_unregister(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + + fpga_mgr_unregister(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(alt_pr_unregister); + +MODULE_AUTHOR("Matthew Gerlach "); +MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Core"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/fpga/altera-pr-ip-core.h b/include/linux/fpga/altera-pr-ip-core.h new file mode 100644 index 000000000000..3810a9033f49 --- /dev/null +++ b/include/linux/fpga/altera-pr-ip-core.h @@ -0,0 +1,29 @@ +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef _ALT_PR_IP_CORE_H +#define _ALT_PR_IP_CORE_H +#include + +int alt_pr_register(struct device *dev, void __iomem *reg_base); +int alt_pr_unregister(struct device *dev); + +#endif /* _ALT_PR_IP_CORE_H */ -- cgit v1.2.3-70-g09d2 From 6e761cd77b5c88ce2817d34088b7762aa40e1fd6 Mon Sep 17 00:00:00 2001 From: Matthew Gerlach Date: Thu, 23 Mar 2017 19:34:29 -0500 Subject: fpga dt: bindings for Altera Partial Reconfiguration IP. Device Tree bindings for Altera Partial Reconfiguration IP. Signed-off-by: Matthew Gerlach Acked-by: Rob Herring Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/fpga/altera-pr-ip.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Documentation/devicetree/bindings/fpga/altera-pr-ip.txt diff --git a/Documentation/devicetree/bindings/fpga/altera-pr-ip.txt b/Documentation/devicetree/bindings/fpga/altera-pr-ip.txt new file mode 100644 index 000000000000..52a294cf2730 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/altera-pr-ip.txt @@ -0,0 +1,12 @@ +Altera Arria10 Partial Reconfiguration IP + +Required properties: +- compatible : should contain "altr,a10-pr-ip" +- reg : base address and size for memory mapped io. + +Example: + + fpga_mgr: fpga-mgr@ff20c000 { + compatible = "altr,a10-pr-ip"; + reg = <0xff20c000 0x10>; + }; -- cgit v1.2.3-70-g09d2 From 5b73cb5b0167833347fa0ce5525cfb488b2e2290 Mon Sep 17 00:00:00 2001 From: Matthew Gerlach Date: Thu, 23 Mar 2017 19:34:30 -0500 Subject: fpga pr ip: Platform driver for Altera Partial Reconfiguration IP. This adds a platform bus driver for a fpga-mgr driver that uses the Altera Partial Reconfiguration IP component. Signed-off-by: Matthew Gerlach Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 7 ++++ drivers/fpga/Makefile | 1 + drivers/fpga/altera-pr-ip-core-plat.c | 68 +++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 drivers/fpga/altera-pr-ip-core-plat.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index e2cc0ad5656c..116ee92fe034 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -88,6 +88,13 @@ config ALTERA_PR_IP_CORE help Core driver support for Altera Partial Reconfiguration IP component +config ALTERA_PR_IP_CORE_PLAT + tristate "Platform support of Altera Partial Reconfiguration IP Core" + depends on ALTERA_PR_IP_CORE && OF && HAS_IOMEM + help + Platform driver support for Altera Partial Reconfiguration IP + component + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 968fd51cb619..530cf9410dde 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o +obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o # FPGA Bridge Drivers obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o diff --git a/drivers/fpga/altera-pr-ip-core-plat.c b/drivers/fpga/altera-pr-ip-core-plat.c new file mode 100644 index 000000000000..8fb36b8b4648 --- /dev/null +++ b/drivers/fpga/altera-pr-ip-core-plat.c @@ -0,0 +1,68 @@ +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016-2017 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include + +static int alt_pr_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + void __iomem *reg_base; + struct resource *res; + + /* First mmio base is for register access */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + reg_base = devm_ioremap_resource(dev, res); + + if (IS_ERR(reg_base)) + return PTR_ERR(reg_base); + + return alt_pr_register(dev, reg_base); +} + +static int alt_pr_platform_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + return alt_pr_unregister(dev); +} + +static const struct of_device_id alt_pr_of_match[] = { + { .compatible = "altr,a10-pr-ip", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, alt_pr_of_match); + +static struct platform_driver alt_pr_platform_driver = { + .probe = alt_pr_platform_probe, + .remove = alt_pr_platform_remove, + .driver = { + .name = "alt_a10_pr_ip", + .of_match_table = alt_pr_of_match, + }, +}; + +module_platform_driver(alt_pr_platform_driver); +MODULE_AUTHOR("Matthew Gerlach "); +MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Platform Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 83ff2d57f3b6161aee3303ed45322f1d29431aeb Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 23 Mar 2017 19:34:31 -0500 Subject: MAINTAINERS: Add file patterns for fpga device tree bindings Submitters of device tree binding documentation may forget to CC the subsystem maintainer if this is missing. Signed-off-by: Geert Uytterhoeven Acked-by: Moritz Fischer Cc: Alan Tull Cc: Moritz Fischer Cc: linux-fpga@vger.kernel.org Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 819d5e8b668a..f8a959e30710 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5143,6 +5143,7 @@ R: Moritz Fischer L: linux-fpga@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/atull/linux-fpga.git +F: Documentation/devicetree/bindings/fpga/ F: drivers/fpga/ F: include/linux/fpga/fpga-mgr.h W: http://www.rocketboards.org -- cgit v1.2.3-70-g09d2 From deb0b9b21d374f09de0745e0ed29a67503b44384 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Thu, 30 Mar 2017 13:23:12 -0500 Subject: MAINTAINERS: fpga: update email and directory paths A few updates: * Updating my email address * Adding another docs directory: Documentation/fpga * Making the include path not specific to fpga-mgr.h only Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index f8a959e30710..216823d0471d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5138,14 +5138,15 @@ F: include/linux/ipmi-fru.h K: fmc_d.*register FPGA MANAGER FRAMEWORK -M: Alan Tull +M: Alan Tull R: Moritz Fischer L: linux-fpga@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/atull/linux-fpga.git +F: Documentation/fpga/ F: Documentation/devicetree/bindings/fpga/ F: drivers/fpga/ -F: include/linux/fpga/fpga-mgr.h +F: include/linux/fpga/ W: http://www.rocketboards.org FPU EMULATOR -- cgit v1.2.3-70-g09d2 From c7c3f096eb5e721d8522ba060fa621ea906ef738 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 23 Mar 2017 14:25:37 +0100 Subject: auxdisplay: hd44780: Fix DT properties to include units of measurement DT properties specifying physical properties should contain appropriate suffices indicating the units of measurement. Hence amend the HD44780 DT bindings to add "chars" suffixes to the "display-height" and "display-width" properties, and update the driver to parse them. Fixes: dd9502a9e9156dd8 ("dt-bindings: auxdisplay: Add bindings for Hitachi HD44780") Fixes: d47d88361feea2ce ("auxdisplay: Add HD44780 Character LCD support") Signed-off-by: Geert Uytterhoeven Acked-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt | 11 ++++++----- drivers/auxdisplay/hd44780.c | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt index ee4054da458d..2aa24b889923 100644 --- a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt +++ b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.txt @@ -15,8 +15,8 @@ Required properties: - rs-gpios: Must contain a GPIO specifier, referring to the GPIO pin connected to the "RS" (Register Select) signal line of the LCD Controller's bus interface, - - display-height: Height of the display, in character cells, - - display-width: Width of the display, in character cells. + - display-height-chars: Height of the display, in character cells, + - display-width-chars: Width of the display, in character cells. Optional properties: - rw-gpios: Must contain a GPIO specifier, referring to the GPIO pin @@ -25,7 +25,8 @@ Optional properties: - backlight-gpios: Must contain a GPIO specifier, referring to the GPIO pin used for enabling the LCD's backlight, - internal-buffer-width: Internal buffer width (default is 40 for displays - with 1 or 2 lines, and display-width for displays with more than 2 lines). + with 1 or 2 lines, and display-width-chars for displays with more than 2 + lines). Example: @@ -39,6 +40,6 @@ Example: enable-gpios = <&hc595 4 GPIO_ACTIVE_HIGH>; rs-gpios = <&hc595 5 GPIO_ACTIVE_HIGH>; - display-height = <2>; - display-width = <16>; + display-height-chars = <2>; + display-width-chars = <16>; }; diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c index 1665ac6ef9ff..036eec404289 100644 --- a/drivers/auxdisplay/hd44780.c +++ b/drivers/auxdisplay/hd44780.c @@ -264,10 +264,11 @@ static int hd44780_probe(struct platform_device *pdev) } /* Required properties */ - ret = device_property_read_u32(dev, "display-height", &lcd->height); + ret = device_property_read_u32(dev, "display-height-chars", + &lcd->height); if (ret) goto fail; - ret = device_property_read_u32(dev, "display-width", &lcd->width); + ret = device_property_read_u32(dev, "display-width-chars", &lcd->width); if (ret) goto fail; -- cgit v1.2.3-70-g09d2 From e1f990c24d14a80dd9b16ea967c9b76dbba501a3 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 28 Mar 2017 12:11:49 +0200 Subject: auxdisplay: ht16k33: don't access uninitialized data gcc-7.0.1 points out that we copy uninitialized data from the stack into a per-device structure: drivers/auxdisplay/ht16k33.c: In function 'ht16k33_keypad_irq_thread': arch/x86/include/asm/string_32.h:78:16: error: 'new_state' may be used uninitialized in this function [-Werror=maybe-uninitialized] arch/x86/include/asm/string_32.h:79:22: error: '*((void *)&new_state+4)' may be used uninitialized in this function [-Werror=maybe-uninitialized] The access is harmless because we never read the data, but we are better off not doing this, so this changes the code to only copy the data that was actually initialized. To make sure we don't overflow the stack with an incorrect DT, we also need to add a sanity checkin the probe function. Signed-off-by: Arnd Bergmann Reviewed-by: Robin van der Gracht Reviewed-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/ht16k33.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index f66b45b235b0..ba6370974574 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -278,7 +278,7 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad) } } input_sync(keypad->dev); - memcpy(keypad->last_key_state, new_state, sizeof(new_state)); + memcpy(keypad->last_key_state, new_state, sizeof(u16) * keypad->cols); return pressed; } @@ -353,6 +353,12 @@ static int ht16k33_keypad_probe(struct i2c_client *client, err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols); if (err) return err; + if (rows > HT16K33_MATRIX_KEYPAD_MAX_ROWS || + cols > HT16K33_MATRIX_KEYPAD_MAX_COLS) { + dev_err(&client->dev, "%u rows or %u cols out of range in DT\n", + rows, cols); + return -ERANGE; + } keypad->rows = rows; keypad->cols = cols; -- cgit v1.2.3-70-g09d2 From 750100a495be63c1db7b97691556d7d9cac7f978 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 29 Mar 2017 09:37:59 -0400 Subject: auxdisplay: img-ascii-lcd: Fix module autoload If the driver is built as a module, autoload won't work because the module alias information is not filled. So user-space can't match the registered device with the corresponding module. Export the module alias information using the MODULE_DEVICE_TABLE() macro. Before this patch: $ modinfo drivers/auxdisplay/img-ascii-lcd.ko | grep alias $ After this patch: $ modinfo drivers/auxdisplay/img-ascii-lcd.ko | grep alias alias: of:N*T*Cmti,sead3-lcdC* alias: of:N*T*Cmti,sead3-lcd alias: of:N*T*Cmti,malta-lcdC* alias: of:N*T*Cmti,malta-lcd alias: of:N*T*Cimg,boston-lcdC* alias: of:N*T*Cimg,boston-lcd Signed-off-by: Javier Martinez Canillas Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/img-ascii-lcd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 83f1439e57fd..25306fa27251 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -220,6 +220,7 @@ static const struct of_device_id img_ascii_lcd_matches[] = { { .compatible = "mti,sead3-lcd", .data = &sead3_config }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); /** * img_ascii_lcd_scroll() - scroll the display by a character -- cgit v1.2.3-70-g09d2 From 6ef0c3337ac9a0a963208beed92d478a317d7888 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 1 Apr 2017 10:27:14 -0700 Subject: auxdisplay: ht16k33: use le16_to_cpup() to fetch LE16 data The data read from the device is 3 little-endian words, so let's annotate them as such and use le16_to_cpu() to convert them to host endianness - it might turn out to be a bit more performant, and it expresses the conversion more clearly. Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/ht16k33.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index ba6370974574..fbfa5b4cc567 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -254,18 +254,22 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad) { const unsigned short *keycodes = keypad->dev->keycode; u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; - u8 data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2]; + __le16 data[HT16K33_MATRIX_KEYPAD_MAX_COLS]; unsigned long bits_changed; int row, col, code; + int rc; bool pressed = false; - if (i2c_smbus_read_i2c_block_data(keypad->client, 0x40, 6, data) != 6) { - dev_err(&keypad->client->dev, "Failed to read key data\n"); + rc = i2c_smbus_read_i2c_block_data(keypad->client, 0x40, + sizeof(data), (u8 *)data); + if (rc != sizeof(data)) { + dev_err(&keypad->client->dev, + "Failed to read key data, rc=%d\n", rc); return false; } for (col = 0; col < keypad->cols; col++) { - new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2]; + new_state[col] = le16_to_cpu(data[col]); if (new_state[col]) pressed = true; bits_changed = keypad->last_key_state[col] ^ new_state[col]; -- cgit v1.2.3-70-g09d2 From 51c1e9b554c996284d5d3bd44da829f41a49a21b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 4 Apr 2017 20:25:21 +0300 Subject: auxdisplay: Move panel.c to drivers/auxdisplay folder It looks like panel.c belongs to auxdisplay subsystem. Move it to drivers/auxdisplay folder. No functional changes intended. Acked-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/Kconfig | 280 +++++++ drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/panel.c | 1796 +++++++++++++++++++++++++++++++++++++++++++ drivers/misc/Kconfig | 280 ------- drivers/misc/Makefile | 1 - drivers/misc/panel.c | 1796 ------------------------------------------- 6 files changed, 2077 insertions(+), 2077 deletions(-) create mode 100644 drivers/auxdisplay/panel.c delete mode 100644 drivers/misc/panel.c diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index c317dc515617..ad83a0598c67 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -156,3 +156,283 @@ config HT16K33 LED controller driver with keyscan. endif # AUXDISPLAY + +config PANEL + tristate "Parallel port LCD/Keypad Panel support" + depends on PARPORT + select CHARLCD + ---help--- + Say Y here if you have an HD44780 or KS-0074 LCD connected to your + parallel port. This driver also features 4 and 6-key keypads. The LCD + is accessible through the /dev/lcd char device (10, 156), and the + keypad through /dev/keypad (10, 185). This code can either be + compiled as a module, or linked into the kernel and started at boot. + If you don't understand what all this is about, say N. + +if PANEL + +config PANEL_PARPORT + int "Default parallel port number (0=LPT1)" + range 0 255 + default "0" + ---help--- + This is the index of the parallel port the panel is connected to. One + driver instance only supports one parallel port, so if your keypad + and LCD are connected to two separate ports, you have to start two + modules with different arguments. Numbering starts with '0' for LPT1, + and so on. + +config PANEL_PROFILE + int "Default panel profile (0-5, 0=custom)" + range 0 5 + default "5" + ---help--- + To ease configuration, the driver supports different configuration + profiles for past and recent wirings. These profiles can also be + used to define an approximative configuration, completed by a few + other options. Here are the profiles : + + 0 = custom (see further) + 1 = 2x16 parallel LCD, old keypad + 2 = 2x16 serial LCD (KS-0074), new keypad + 3 = 2x16 parallel LCD (Hantronix), no keypad + 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad + 5 = 2x40 parallel LCD (old one), with old keypad + + Custom configurations allow you to define how your display is + wired to the parallel port, and how it works. This is only intended + for experts. + +config PANEL_KEYPAD + depends on PANEL_PROFILE="0" + int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)" + range 0 3 + default 0 + ---help--- + This enables and configures a keypad connected to the parallel port. + The keys will be read from character device 10,185. Valid values are : + + 0 : do not enable this driver + 1 : old 6 keys keypad + 2 : new 6 keys keypad, as used on the server at www.ant-computing.com + 3 : Nexcom NSA1045's 4 keys keypad + + New profiles can be described in the driver source. The driver also + supports simultaneous keys pressed when the keypad supports them. + +config PANEL_LCD + depends on PANEL_PROFILE="0" + int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)" + range 0 5 + default 0 + ---help--- + This enables and configures an LCD connected to the parallel port. + The driver includes an interpreter for escape codes starting with + '\e[L' which are specific to the LCD, and a few ANSI codes. The + driver will be registered as character device 10,156, usually + under the name '/dev/lcd'. There are a total of 6 supported types : + + 0 : do not enable the driver + 1 : custom configuration and wiring (see further) + 2 : 2x16 & 2x40 parallel LCD (old wiring) + 3 : 2x16 serial LCD (KS-0074 based) + 4 : 2x16 parallel LCD (Hantronix wiring) + 5 : 2x16 parallel LCD (Nexcom wiring) + + When type '1' is specified, other options will appear to configure + more precise aspects (wiring, dimensions, protocol, ...). Please note + that those values changed from the 2.4 driver for better consistency. + +config PANEL_LCD_HEIGHT + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Number of lines on the LCD (1-2)" + range 1 2 + default 2 + ---help--- + This is the number of visible character lines on the LCD in custom profile. + It can either be 1 or 2. + +config PANEL_LCD_WIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Number of characters per line on the LCD (1-40)" + range 1 40 + default 40 + ---help--- + This is the number of characters per line on the LCD in custom profile. + Common values are 16,20,24,40. + +config PANEL_LCD_BWIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Internal LCD line width (1-40, 40 by default)" + range 1 40 + default 40 + ---help--- + Most LCDs use a standard controller which supports hardware lines of 40 + characters, although sometimes only 16, 20 or 24 of them are really wired + to the terminal. This results in some non-visible but addressable characters, + and is the case for most parallel LCDs. Other LCDs, and some serial ones, + however, use the same line width internally as what is visible. The KS0074 + for example, uses 16 characters per line for 16 visible characters per line. + + This option lets you configure the value used by your LCD in 'custom' profile. + If you don't know, put '40' here. + +config PANEL_LCD_HWIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Hardware LCD line width (1-64, 64 by default)" + range 1 64 + default 64 + ---help--- + Most LCDs use a single address bit to differentiate line 0 and line 1. Since + some of them need to be able to address 40 chars with the lower bits, they + often use the immediately superior power of 2, which is 64, to address the + next line. + + If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and + 64 here for a 2x40. + +config PANEL_LCD_CHARSET + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "LCD character set (0=normal, 1=KS0074)" + range 0 1 + default 0 + ---help--- + Some controllers such as the KS0074 use a somewhat strange character set + where many symbols are at unusual places. The driver knows how to map + 'standard' ASCII characters to the character sets used by these controllers. + Valid values are : + + 0 : normal (untranslated) character set + 1 : KS0074 character set + + If you don't know, use the normal one (0). + +config PANEL_LCD_PROTO + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "LCD communication mode (0=parallel 8 bits, 1=serial)" + range 0 1 + default 0 + ---help--- + This driver now supports any serial or parallel LCD wired to a parallel + port. But before assigning signals, the driver needs to know if it will + be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires + (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals + (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits + parallel LCD, and 1 for a serial LCD. + +config PANEL_LCD_PIN_E + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) " + range -17 17 + default 14 + ---help--- + This describes the number of the parallel port pin to which the LCD 'E' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'E' pin in custom profile is '14' (AUTOFEED). + +config PANEL_LCD_PIN_RS + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) " + range -17 17 + default 17 + ---help--- + This describes the number of the parallel port pin to which the LCD 'RS' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'RS' pin in custom profile is '17' (SELECT IN). + +config PANEL_LCD_PIN_RW + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) " + range -17 17 + default 16 + ---help--- + This describes the number of the parallel port pin to which the LCD 'RW' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'RW' pin in custom profile is '16' (INIT). + +config PANEL_LCD_PIN_SCL + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" + int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) " + range -17 17 + default 1 + ---help--- + This describes the number of the parallel port pin to which the serial + LCD 'SCL' signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'SCL' pin in custom profile is '1' (STROBE). + +config PANEL_LCD_PIN_SDA + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" + int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) " + range -17 17 + default 2 + ---help--- + This describes the number of the parallel port pin to which the serial + LCD 'SDA' signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'SDA' pin in custom profile is '2' (D0). + +config PANEL_LCD_PIN_BL + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) " + range -17 17 + default 0 + ---help--- + This describes the number of the parallel port pin to which the LCD 'BL' signal + has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'BL' pin in custom profile is '0' (uncontrolled). + +config PANEL_CHANGE_MESSAGE + bool "Change LCD initialization message ?" + default "n" + ---help--- + This allows you to replace the boot message indicating the kernel version + and the driver version with a custom message. This is useful on appliances + where a simple 'Starting system' message can be enough to stop a customer + from worrying. + + If you say 'Y' here, you'll be able to choose a message yourself. Otherwise, + say 'N' and keep the default message with the version. + +config PANEL_BOOT_MESSAGE + depends on PANEL_CHANGE_MESSAGE="y" + string "New initialization message" + default "" + ---help--- + This allows you to replace the boot message indicating the kernel version + and the driver version with a custom message. This is useful on appliances + where a simple 'Starting system' message can be enough to stop a customer + from worrying. + + An empty message will only clear the display at driver init time. Any other + printf()-formatted message is valid with newline and escape codes. + +endif # PANEL diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index fcefa686be0f..58c25e5ece84 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o obj-$(CONFIG_HD44780) += hd44780.o obj-$(CONFIG_HT16K33) += ht16k33.o +obj-$(CONFIG_PANEL) += panel.o diff --git a/drivers/auxdisplay/panel.c b/drivers/auxdisplay/panel.c new file mode 100644 index 000000000000..e0c014c2356f --- /dev/null +++ b/drivers/auxdisplay/panel.c @@ -0,0 +1,1796 @@ +/* + * Front panel driver for Linux + * Copyright (C) 2000-2008, Willy Tarreau + * Copyright (C) 2016-2017 Glider bvba + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This code drives an LCD module (/dev/lcd), and a keypad (/dev/keypad) + * connected to a parallel printer port. + * + * The LCD module may either be an HD44780-like 8-bit parallel LCD, or a 1-bit + * serial module compatible with Samsung's KS0074. The pins may be connected in + * any combination, everything is programmable. + * + * The keypad consists in a matrix of push buttons connecting input pins to + * data output pins or to the ground. The combinations have to be hard-coded + * in the driver, though several profiles exist and adding new ones is easy. + * + * Several profiles are provided for commonly found LCD+keypad modules on the + * market, such as those found in Nexcom's appliances. + * + * FIXME: + * - the initialization/deinitialization process is very dirty and should + * be rewritten. It may even be buggy. + * + * TODO: + * - document 24 keys keyboard (3 rows of 8 cols, 32 diodes + 2 inputs) + * - make the LCD a part of a virtual screen of Vx*Vy + * - make the inputs list smp-safe + * - change the keyboard to a double mapping : signals -> key_id -> values + * so that applications can change values without knowing signals + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define KEYPAD_MINOR 185 + +#define LCD_MAXBYTES 256 /* max burst write */ + +#define KEYPAD_BUFFER 64 + +/* poll the keyboard this every second */ +#define INPUT_POLL_TIME (HZ / 50) +/* a key starts to repeat after this times INPUT_POLL_TIME */ +#define KEYPAD_REP_START (10) +/* a key repeats this times INPUT_POLL_TIME */ +#define KEYPAD_REP_DELAY (2) + +/* converts an r_str() input to an active high, bits string : 000BAOSE */ +#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3) + +#define PNL_PBUSY 0x80 /* inverted input, active low */ +#define PNL_PACK 0x40 /* direct input, active low */ +#define PNL_POUTPA 0x20 /* direct input, active high */ +#define PNL_PSELECD 0x10 /* direct input, active high */ +#define PNL_PERRORP 0x08 /* direct input, active low */ + +#define PNL_PBIDIR 0x20 /* bi-directional ports */ +/* high to read data in or-ed with data out */ +#define PNL_PINTEN 0x10 +#define PNL_PSELECP 0x08 /* inverted output, active low */ +#define PNL_PINITP 0x04 /* direct output, active low */ +#define PNL_PAUTOLF 0x02 /* inverted output, active low */ +#define PNL_PSTROBE 0x01 /* inverted output */ + +#define PNL_PD0 0x01 +#define PNL_PD1 0x02 +#define PNL_PD2 0x04 +#define PNL_PD3 0x08 +#define PNL_PD4 0x10 +#define PNL_PD5 0x20 +#define PNL_PD6 0x40 +#define PNL_PD7 0x80 + +#define PIN_NONE 0 +#define PIN_STROBE 1 +#define PIN_D0 2 +#define PIN_D1 3 +#define PIN_D2 4 +#define PIN_D3 5 +#define PIN_D4 6 +#define PIN_D5 7 +#define PIN_D6 8 +#define PIN_D7 9 +#define PIN_AUTOLF 14 +#define PIN_INITP 16 +#define PIN_SELECP 17 +#define PIN_NOT_SET 127 + +#define NOT_SET -1 + +/* macros to simplify use of the parallel port */ +#define r_ctr(x) (parport_read_control((x)->port)) +#define r_dtr(x) (parport_read_data((x)->port)) +#define r_str(x) (parport_read_status((x)->port)) +#define w_ctr(x, y) (parport_write_control((x)->port, (y))) +#define w_dtr(x, y) (parport_write_data((x)->port, (y))) + +/* this defines which bits are to be used and which ones to be ignored */ +/* logical or of the output bits involved in the scan matrix */ +static __u8 scan_mask_o; +/* logical or of the input bits involved in the scan matrix */ +static __u8 scan_mask_i; + +enum input_type { + INPUT_TYPE_STD, + INPUT_TYPE_KBD, +}; + +enum input_state { + INPUT_ST_LOW, + INPUT_ST_RISING, + INPUT_ST_HIGH, + INPUT_ST_FALLING, +}; + +struct logical_input { + struct list_head list; + __u64 mask; + __u64 value; + enum input_type type; + enum input_state state; + __u8 rise_time, fall_time; + __u8 rise_timer, fall_timer, high_timer; + + union { + struct { /* valid when type == INPUT_TYPE_STD */ + void (*press_fct)(int); + void (*release_fct)(int); + int press_data; + int release_data; + } std; + struct { /* valid when type == INPUT_TYPE_KBD */ + /* strings can be non null-terminated */ + char press_str[sizeof(void *) + sizeof(int)]; + char repeat_str[sizeof(void *) + sizeof(int)]; + char release_str[sizeof(void *) + sizeof(int)]; + } kbd; + } u; +}; + +static LIST_HEAD(logical_inputs); /* list of all defined logical inputs */ + +/* physical contacts history + * Physical contacts are a 45 bits string of 9 groups of 5 bits each. + * The 8 lower groups correspond to output bits 0 to 7, and the 9th group + * corresponds to the ground. + * Within each group, bits are stored in the same order as read on the port : + * BAPSE (busy=4, ack=3, paper empty=2, select=1, error=0). + * So, each __u64 is represented like this : + * 0000000000000000000BAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSE + * <-----unused------> + */ + +/* what has just been read from the I/O ports */ +static __u64 phys_read; +/* previous phys_read */ +static __u64 phys_read_prev; +/* stabilized phys_read (phys_read|phys_read_prev) */ +static __u64 phys_curr; +/* previous phys_curr */ +static __u64 phys_prev; +/* 0 means that at least one logical signal needs be computed */ +static char inputs_stable; + +/* these variables are specific to the keypad */ +static struct { + bool enabled; +} keypad; + +static char keypad_buffer[KEYPAD_BUFFER]; +static int keypad_buflen; +static int keypad_start; +static char keypressed; +static wait_queue_head_t keypad_read_wait; + +/* lcd-specific variables */ +static struct { + bool enabled; + bool initialized; + + int charset; + int proto; + + /* TODO: use union here? */ + struct { + int e; + int rs; + int rw; + int cl; + int da; + int bl; + } pins; + + struct charlcd *charlcd; +} lcd; + +/* Needed only for init */ +static int selected_lcd_type = NOT_SET; + +/* + * Bit masks to convert LCD signals to parallel port outputs. + * _d_ are values for data port, _c_ are for control port. + * [0] = signal OFF, [1] = signal ON, [2] = mask + */ +#define BIT_CLR 0 +#define BIT_SET 1 +#define BIT_MSK 2 +#define BIT_STATES 3 +/* + * one entry for each bit on the LCD + */ +#define LCD_BIT_E 0 +#define LCD_BIT_RS 1 +#define LCD_BIT_RW 2 +#define LCD_BIT_BL 3 +#define LCD_BIT_CL 4 +#define LCD_BIT_DA 5 +#define LCD_BITS 6 + +/* + * each bit can be either connected to a DATA or CTRL port + */ +#define LCD_PORT_C 0 +#define LCD_PORT_D 1 +#define LCD_PORTS 2 + +static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES]; + +/* + * LCD protocols + */ +#define LCD_PROTO_PARALLEL 0 +#define LCD_PROTO_SERIAL 1 +#define LCD_PROTO_TI_DA8XX_LCD 2 + +/* + * LCD character sets + */ +#define LCD_CHARSET_NORMAL 0 +#define LCD_CHARSET_KS0074 1 + +/* + * LCD types + */ +#define LCD_TYPE_NONE 0 +#define LCD_TYPE_CUSTOM 1 +#define LCD_TYPE_OLD 2 +#define LCD_TYPE_KS0074 3 +#define LCD_TYPE_HANTRONIX 4 +#define LCD_TYPE_NEXCOM 5 + +/* + * keypad types + */ +#define KEYPAD_TYPE_NONE 0 +#define KEYPAD_TYPE_OLD 1 +#define KEYPAD_TYPE_NEW 2 +#define KEYPAD_TYPE_NEXCOM 3 + +/* + * panel profiles + */ +#define PANEL_PROFILE_CUSTOM 0 +#define PANEL_PROFILE_OLD 1 +#define PANEL_PROFILE_NEW 2 +#define PANEL_PROFILE_HANTRONIX 3 +#define PANEL_PROFILE_NEXCOM 4 +#define PANEL_PROFILE_LARGE 5 + +/* + * Construct custom config from the kernel's configuration + */ +#define DEFAULT_PARPORT 0 +#define DEFAULT_PROFILE PANEL_PROFILE_LARGE +#define DEFAULT_KEYPAD_TYPE KEYPAD_TYPE_OLD +#define DEFAULT_LCD_TYPE LCD_TYPE_OLD +#define DEFAULT_LCD_HEIGHT 2 +#define DEFAULT_LCD_WIDTH 40 +#define DEFAULT_LCD_BWIDTH 40 +#define DEFAULT_LCD_HWIDTH 64 +#define DEFAULT_LCD_CHARSET LCD_CHARSET_NORMAL +#define DEFAULT_LCD_PROTO LCD_PROTO_PARALLEL + +#define DEFAULT_LCD_PIN_E PIN_AUTOLF +#define DEFAULT_LCD_PIN_RS PIN_SELECP +#define DEFAULT_LCD_PIN_RW PIN_INITP +#define DEFAULT_LCD_PIN_SCL PIN_STROBE +#define DEFAULT_LCD_PIN_SDA PIN_D0 +#define DEFAULT_LCD_PIN_BL PIN_NOT_SET + +#ifdef CONFIG_PANEL_PARPORT +#undef DEFAULT_PARPORT +#define DEFAULT_PARPORT CONFIG_PANEL_PARPORT +#endif + +#ifdef CONFIG_PANEL_PROFILE +#undef DEFAULT_PROFILE +#define DEFAULT_PROFILE CONFIG_PANEL_PROFILE +#endif + +#if DEFAULT_PROFILE == 0 /* custom */ +#ifdef CONFIG_PANEL_KEYPAD +#undef DEFAULT_KEYPAD_TYPE +#define DEFAULT_KEYPAD_TYPE CONFIG_PANEL_KEYPAD +#endif + +#ifdef CONFIG_PANEL_LCD +#undef DEFAULT_LCD_TYPE +#define DEFAULT_LCD_TYPE CONFIG_PANEL_LCD +#endif + +#ifdef CONFIG_PANEL_LCD_HEIGHT +#undef DEFAULT_LCD_HEIGHT +#define DEFAULT_LCD_HEIGHT CONFIG_PANEL_LCD_HEIGHT +#endif + +#ifdef CONFIG_PANEL_LCD_WIDTH +#undef DEFAULT_LCD_WIDTH +#define DEFAULT_LCD_WIDTH CONFIG_PANEL_LCD_WIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_BWIDTH +#undef DEFAULT_LCD_BWIDTH +#define DEFAULT_LCD_BWIDTH CONFIG_PANEL_LCD_BWIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_HWIDTH +#undef DEFAULT_LCD_HWIDTH +#define DEFAULT_LCD_HWIDTH CONFIG_PANEL_LCD_HWIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_CHARSET +#undef DEFAULT_LCD_CHARSET +#define DEFAULT_LCD_CHARSET CONFIG_PANEL_LCD_CHARSET +#endif + +#ifdef CONFIG_PANEL_LCD_PROTO +#undef DEFAULT_LCD_PROTO +#define DEFAULT_LCD_PROTO CONFIG_PANEL_LCD_PROTO +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_E +#undef DEFAULT_LCD_PIN_E +#define DEFAULT_LCD_PIN_E CONFIG_PANEL_LCD_PIN_E +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_RS +#undef DEFAULT_LCD_PIN_RS +#define DEFAULT_LCD_PIN_RS CONFIG_PANEL_LCD_PIN_RS +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_RW +#undef DEFAULT_LCD_PIN_RW +#define DEFAULT_LCD_PIN_RW CONFIG_PANEL_LCD_PIN_RW +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_SCL +#undef DEFAULT_LCD_PIN_SCL +#define DEFAULT_LCD_PIN_SCL CONFIG_PANEL_LCD_PIN_SCL +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_SDA +#undef DEFAULT_LCD_PIN_SDA +#define DEFAULT_LCD_PIN_SDA CONFIG_PANEL_LCD_PIN_SDA +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_BL +#undef DEFAULT_LCD_PIN_BL +#define DEFAULT_LCD_PIN_BL CONFIG_PANEL_LCD_PIN_BL +#endif + +#endif /* DEFAULT_PROFILE == 0 */ + +/* global variables */ + +/* Device single-open policy control */ +static atomic_t keypad_available = ATOMIC_INIT(1); + +static struct pardevice *pprt; + +static int keypad_initialized; + +static DEFINE_SPINLOCK(pprt_lock); +static struct timer_list scan_timer; + +MODULE_DESCRIPTION("Generic parallel port LCD/Keypad driver"); + +static int parport = DEFAULT_PARPORT; +module_param(parport, int, 0000); +MODULE_PARM_DESC(parport, "Parallel port index (0=lpt1, 1=lpt2, ...)"); + +static int profile = DEFAULT_PROFILE; +module_param(profile, int, 0000); +MODULE_PARM_DESC(profile, + "1=16x2 old kp; 2=serial 16x2, new kp; 3=16x2 hantronix; " + "4=16x2 nexcom; default=40x2, old kp"); + +static int keypad_type = NOT_SET; +module_param(keypad_type, int, 0000); +MODULE_PARM_DESC(keypad_type, + "Keypad type: 0=none, 1=old 6 keys, 2=new 6+1 keys, 3=nexcom 4 keys"); + +static int lcd_type = NOT_SET; +module_param(lcd_type, int, 0000); +MODULE_PARM_DESC(lcd_type, + "LCD type: 0=none, 1=compiled-in, 2=old, 3=serial ks0074, 4=hantronix, 5=nexcom"); + +static int lcd_height = NOT_SET; +module_param(lcd_height, int, 0000); +MODULE_PARM_DESC(lcd_height, "Number of lines on the LCD"); + +static int lcd_width = NOT_SET; +module_param(lcd_width, int, 0000); +MODULE_PARM_DESC(lcd_width, "Number of columns on the LCD"); + +static int lcd_bwidth = NOT_SET; /* internal buffer width (usually 40) */ +module_param(lcd_bwidth, int, 0000); +MODULE_PARM_DESC(lcd_bwidth, "Internal LCD line width (40)"); + +static int lcd_hwidth = NOT_SET; /* hardware buffer width (usually 64) */ +module_param(lcd_hwidth, int, 0000); +MODULE_PARM_DESC(lcd_hwidth, "LCD line hardware address (64)"); + +static int lcd_charset = NOT_SET; +module_param(lcd_charset, int, 0000); +MODULE_PARM_DESC(lcd_charset, "LCD character set: 0=standard, 1=KS0074"); + +static int lcd_proto = NOT_SET; +module_param(lcd_proto, int, 0000); +MODULE_PARM_DESC(lcd_proto, + "LCD communication: 0=parallel (//), 1=serial, 2=TI LCD Interface"); + +/* + * These are the parallel port pins the LCD control signals are connected to. + * Set this to 0 if the signal is not used. Set it to its opposite value + * (negative) if the signal is negated. -MAXINT is used to indicate that the + * pin has not been explicitly specified. + * + * WARNING! no check will be performed about collisions with keypad ! + */ + +static int lcd_e_pin = PIN_NOT_SET; +module_param(lcd_e_pin, int, 0000); +MODULE_PARM_DESC(lcd_e_pin, + "# of the // port pin connected to LCD 'E' signal, with polarity (-17..17)"); + +static int lcd_rs_pin = PIN_NOT_SET; +module_param(lcd_rs_pin, int, 0000); +MODULE_PARM_DESC(lcd_rs_pin, + "# of the // port pin connected to LCD 'RS' signal, with polarity (-17..17)"); + +static int lcd_rw_pin = PIN_NOT_SET; +module_param(lcd_rw_pin, int, 0000); +MODULE_PARM_DESC(lcd_rw_pin, + "# of the // port pin connected to LCD 'RW' signal, with polarity (-17..17)"); + +static int lcd_cl_pin = PIN_NOT_SET; +module_param(lcd_cl_pin, int, 0000); +MODULE_PARM_DESC(lcd_cl_pin, + "# of the // port pin connected to serial LCD 'SCL' signal, with polarity (-17..17)"); + +static int lcd_da_pin = PIN_NOT_SET; +module_param(lcd_da_pin, int, 0000); +MODULE_PARM_DESC(lcd_da_pin, + "# of the // port pin connected to serial LCD 'SDA' signal, with polarity (-17..17)"); + +static int lcd_bl_pin = PIN_NOT_SET; +module_param(lcd_bl_pin, int, 0000); +MODULE_PARM_DESC(lcd_bl_pin, + "# of the // port pin connected to LCD backlight, with polarity (-17..17)"); + +/* Deprecated module parameters - consider not using them anymore */ + +static int lcd_enabled = NOT_SET; +module_param(lcd_enabled, int, 0000); +MODULE_PARM_DESC(lcd_enabled, "Deprecated option, use lcd_type instead"); + +static int keypad_enabled = NOT_SET; +module_param(keypad_enabled, int, 0000); +MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead"); + +/* for some LCD drivers (ks0074) we need a charset conversion table. */ +static const unsigned char lcd_char_conv_ks0074[256] = { + /* 0|8 1|9 2|A 3|B 4|C 5|D 6|E 7|F */ + /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + /* 0x08 */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + /* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + /* 0x18 */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + /* 0x20 */ 0x20, 0x21, 0x22, 0x23, 0xa2, 0x25, 0x26, 0x27, + /* 0x28 */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + /* 0x30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + /* 0x38 */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + /* 0x40 */ 0xa0, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + /* 0x48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + /* 0x50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + /* 0x58 */ 0x58, 0x59, 0x5a, 0xfa, 0xfb, 0xfc, 0x1d, 0xc4, + /* 0x60 */ 0x96, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + /* 0x68 */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + /* 0x70 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + /* 0x78 */ 0x78, 0x79, 0x7a, 0xfd, 0xfe, 0xff, 0xce, 0x20, + /* 0x80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + /* 0x88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + /* 0x90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + /* 0x98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + /* 0xA0 */ 0x20, 0x40, 0xb1, 0xa1, 0x24, 0xa3, 0xfe, 0x5f, + /* 0xA8 */ 0x22, 0xc8, 0x61, 0x14, 0x97, 0x2d, 0xad, 0x96, + /* 0xB0 */ 0x80, 0x8c, 0x82, 0x83, 0x27, 0x8f, 0x86, 0xdd, + /* 0xB8 */ 0x2c, 0x81, 0x6f, 0x15, 0x8b, 0x8a, 0x84, 0x60, + /* 0xC0 */ 0xe2, 0xe2, 0xe2, 0x5b, 0x5b, 0xae, 0xbc, 0xa9, + /* 0xC8 */ 0xc5, 0xbf, 0xc6, 0xf1, 0xe3, 0xe3, 0xe3, 0xe3, + /* 0xD0 */ 0x44, 0x5d, 0xa8, 0xe4, 0xec, 0xec, 0x5c, 0x78, + /* 0xD8 */ 0xab, 0xa6, 0xe5, 0x5e, 0x5e, 0xe6, 0xaa, 0xbe, + /* 0xE0 */ 0x7f, 0xe7, 0xaf, 0x7b, 0x7b, 0xaf, 0xbd, 0xc8, + /* 0xE8 */ 0xa4, 0xa5, 0xc7, 0xf6, 0xa7, 0xe8, 0x69, 0x69, + /* 0xF0 */ 0xed, 0x7d, 0xa8, 0xe4, 0xec, 0x5c, 0x5c, 0x25, + /* 0xF8 */ 0xac, 0xa6, 0xea, 0xef, 0x7e, 0xeb, 0xb2, 0x79, +}; + +static const char old_keypad_profile[][4][9] = { + {"S0", "Left\n", "Left\n", ""}, + {"S1", "Down\n", "Down\n", ""}, + {"S2", "Up\n", "Up\n", ""}, + {"S3", "Right\n", "Right\n", ""}, + {"S4", "Esc\n", "Esc\n", ""}, + {"S5", "Ret\n", "Ret\n", ""}, + {"", "", "", ""} +}; + +/* signals, press, repeat, release */ +static const char new_keypad_profile[][4][9] = { + {"S0", "Left\n", "Left\n", ""}, + {"S1", "Down\n", "Down\n", ""}, + {"S2", "Up\n", "Up\n", ""}, + {"S3", "Right\n", "Right\n", ""}, + {"S4s5", "", "Esc\n", "Esc\n"}, + {"s4S5", "", "Ret\n", "Ret\n"}, + {"S4S5", "Help\n", "", ""}, + /* add new signals above this line */ + {"", "", "", ""} +}; + +/* signals, press, repeat, release */ +static const char nexcom_keypad_profile[][4][9] = { + {"a-p-e-", "Down\n", "Down\n", ""}, + {"a-p-E-", "Ret\n", "Ret\n", ""}, + {"a-P-E-", "Esc\n", "Esc\n", ""}, + {"a-P-e-", "Up\n", "Up\n", ""}, + /* add new signals above this line */ + {"", "", "", ""} +}; + +static const char (*keypad_profile)[4][9] = old_keypad_profile; + +static DECLARE_BITMAP(bits, LCD_BITS); + +static void lcd_get_bits(unsigned int port, int *val) +{ + unsigned int bit, state; + + for (bit = 0; bit < LCD_BITS; bit++) { + state = test_bit(bit, bits) ? BIT_SET : BIT_CLR; + *val &= lcd_bits[port][bit][BIT_MSK]; + *val |= lcd_bits[port][bit][state]; + } +} + +/* sets data port bits according to current signals values */ +static int set_data_bits(void) +{ + int val; + + val = r_dtr(pprt); + lcd_get_bits(LCD_PORT_D, &val); + w_dtr(pprt, val); + return val; +} + +/* sets ctrl port bits according to current signals values */ +static int set_ctrl_bits(void) +{ + int val; + + val = r_ctr(pprt); + lcd_get_bits(LCD_PORT_C, &val); + w_ctr(pprt, val); + return val; +} + +/* sets ctrl & data port bits according to current signals values */ +static void panel_set_bits(void) +{ + set_data_bits(); + set_ctrl_bits(); +} + +/* + * Converts a parallel port pin (from -25 to 25) to data and control ports + * masks, and data and control port bits. The signal will be considered + * unconnected if it's on pin 0 or an invalid pin (<-25 or >25). + * + * Result will be used this way : + * out(dport, in(dport) & d_val[2] | d_val[signal_state]) + * out(cport, in(cport) & c_val[2] | c_val[signal_state]) + */ +static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val) +{ + int d_bit, c_bit, inv; + + d_val[0] = 0; + c_val[0] = 0; + d_val[1] = 0; + c_val[1] = 0; + d_val[2] = 0xFF; + c_val[2] = 0xFF; + + if (pin == 0) + return; + + inv = (pin < 0); + if (inv) + pin = -pin; + + d_bit = 0; + c_bit = 0; + + switch (pin) { + case PIN_STROBE: /* strobe, inverted */ + c_bit = PNL_PSTROBE; + inv = !inv; + break; + case PIN_D0...PIN_D7: /* D0 - D7 = 2 - 9 */ + d_bit = 1 << (pin - 2); + break; + case PIN_AUTOLF: /* autofeed, inverted */ + c_bit = PNL_PAUTOLF; + inv = !inv; + break; + case PIN_INITP: /* init, direct */ + c_bit = PNL_PINITP; + break; + case PIN_SELECP: /* select_in, inverted */ + c_bit = PNL_PSELECP; + inv = !inv; + break; + default: /* unknown pin, ignore */ + break; + } + + if (c_bit) { + c_val[2] &= ~c_bit; + c_val[!inv] = c_bit; + } else if (d_bit) { + d_val[2] &= ~d_bit; + d_val[!inv] = d_bit; + } +} + +/* + * send a serial byte to the LCD panel. The caller is responsible for locking + * if needed. + */ +static void lcd_send_serial(int byte) +{ + int bit; + + /* + * the data bit is set on D0, and the clock on STROBE. + * LCD reads D0 on STROBE's rising edge. + */ + for (bit = 0; bit < 8; bit++) { + clear_bit(LCD_BIT_CL, bits); /* CLK low */ + panel_set_bits(); + if (byte & 1) { + set_bit(LCD_BIT_DA, bits); + } else { + clear_bit(LCD_BIT_DA, bits); + } + + panel_set_bits(); + udelay(2); /* maintain the data during 2 us before CLK up */ + set_bit(LCD_BIT_CL, bits); /* CLK high */ + panel_set_bits(); + udelay(1); /* maintain the strobe during 1 us */ + byte >>= 1; + } +} + +/* turn the backlight on or off */ +static void lcd_backlight(struct charlcd *charlcd, int on) +{ + if (lcd.pins.bl == PIN_NONE) + return; + + /* The backlight is activated by setting the AUTOFEED line to +5V */ + spin_lock_irq(&pprt_lock); + if (on) + set_bit(LCD_BIT_BL, bits); + else + clear_bit(LCD_BIT_BL, bits); + panel_set_bits(); + spin_unlock_irq(&pprt_lock); +} + +/* send a command to the LCD panel in serial mode */ +static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd) +{ + spin_lock_irq(&pprt_lock); + lcd_send_serial(0x1F); /* R/W=W, RS=0 */ + lcd_send_serial(cmd & 0x0F); + lcd_send_serial((cmd >> 4) & 0x0F); + udelay(40); /* the shortest command takes at least 40 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send data to the LCD panel in serial mode */ +static void lcd_write_data_s(struct charlcd *charlcd, int data) +{ + spin_lock_irq(&pprt_lock); + lcd_send_serial(0x5F); /* R/W=W, RS=1 */ + lcd_send_serial(data & 0x0F); + lcd_send_serial((data >> 4) & 0x0F); + udelay(40); /* the shortest data takes at least 40 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send a command to the LCD panel in 8 bits parallel mode */ +static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, cmd); + udelay(20); /* maintain the data during 20 us before the strobe */ + + set_bit(LCD_BIT_E, bits); + clear_bit(LCD_BIT_RS, bits); + clear_bit(LCD_BIT_RW, bits); + set_ctrl_bits(); + + udelay(40); /* maintain the strobe during 40 us */ + + clear_bit(LCD_BIT_E, bits); + set_ctrl_bits(); + + udelay(120); /* the shortest command takes at least 120 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send data to the LCD panel in 8 bits parallel mode */ +static void lcd_write_data_p8(struct charlcd *charlcd, int data) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, data); + udelay(20); /* maintain the data during 20 us before the strobe */ + + set_bit(LCD_BIT_E, bits); + set_bit(LCD_BIT_RS, bits); + clear_bit(LCD_BIT_RW, bits); + set_ctrl_bits(); + + udelay(40); /* maintain the strobe during 40 us */ + + clear_bit(LCD_BIT_E, bits); + set_ctrl_bits(); + + udelay(45); /* the shortest data takes at least 45 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send a command to the TI LCD panel */ +static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the control port */ + w_ctr(pprt, cmd); + udelay(60); + spin_unlock_irq(&pprt_lock); +} + +/* send data to the TI LCD panel */ +static void lcd_write_data_tilcd(struct charlcd *charlcd, int data) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, data); + udelay(60); + spin_unlock_irq(&pprt_lock); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_s(struct charlcd *charlcd) +{ + int pos; + + spin_lock_irq(&pprt_lock); + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { + lcd_send_serial(0x5F); /* R/W=W, RS=1 */ + lcd_send_serial(' ' & 0x0F); + lcd_send_serial((' ' >> 4) & 0x0F); + /* the shortest data takes at least 40 us */ + udelay(40); + } + spin_unlock_irq(&pprt_lock); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_p8(struct charlcd *charlcd) +{ + int pos; + + spin_lock_irq(&pprt_lock); + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { + /* present the data to the data port */ + w_dtr(pprt, ' '); + + /* maintain the data during 20 us before the strobe */ + udelay(20); + + set_bit(LCD_BIT_E, bits); + set_bit(LCD_BIT_RS, bits); + clear_bit(LCD_BIT_RW, bits); + set_ctrl_bits(); + + /* maintain the strobe during 40 us */ + udelay(40); + + clear_bit(LCD_BIT_E, bits); + set_ctrl_bits(); + + /* the shortest data takes at least 45 us */ + udelay(45); + } + spin_unlock_irq(&pprt_lock); +} + +/* fills the display with spaces and resets X/Y */ +static void lcd_clear_fast_tilcd(struct charlcd *charlcd) +{ + int pos; + + spin_lock_irq(&pprt_lock); + for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { + /* present the data to the data port */ + w_dtr(pprt, ' '); + udelay(60); + } + + spin_unlock_irq(&pprt_lock); +} + +static struct charlcd_ops charlcd_serial_ops = { + .write_cmd = lcd_write_cmd_s, + .write_data = lcd_write_data_s, + .clear_fast = lcd_clear_fast_s, + .backlight = lcd_backlight, +}; + +static struct charlcd_ops charlcd_parallel_ops = { + .write_cmd = lcd_write_cmd_p8, + .write_data = lcd_write_data_p8, + .clear_fast = lcd_clear_fast_p8, + .backlight = lcd_backlight, +}; + +static struct charlcd_ops charlcd_tilcd_ops = { + .write_cmd = lcd_write_cmd_tilcd, + .write_data = lcd_write_data_tilcd, + .clear_fast = lcd_clear_fast_tilcd, + .backlight = lcd_backlight, +}; + +/* initialize the LCD driver */ +static void lcd_init(void) +{ + struct charlcd *charlcd; + + charlcd = charlcd_alloc(0); + if (!charlcd) + return; + + /* + * Init lcd struct with load-time values to preserve exact + * current functionality (at least for now). + */ + charlcd->height = lcd_height; + charlcd->width = lcd_width; + charlcd->bwidth = lcd_bwidth; + charlcd->hwidth = lcd_hwidth; + + switch (selected_lcd_type) { + case LCD_TYPE_OLD: + /* parallel mode, 8 bits */ + lcd.proto = LCD_PROTO_PARALLEL; + lcd.charset = LCD_CHARSET_NORMAL; + lcd.pins.e = PIN_STROBE; + lcd.pins.rs = PIN_AUTOLF; + + charlcd->width = 40; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; + break; + case LCD_TYPE_KS0074: + /* serial mode, ks0074 */ + lcd.proto = LCD_PROTO_SERIAL; + lcd.charset = LCD_CHARSET_KS0074; + lcd.pins.bl = PIN_AUTOLF; + lcd.pins.cl = PIN_STROBE; + lcd.pins.da = PIN_D0; + + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 16; + charlcd->height = 2; + break; + case LCD_TYPE_NEXCOM: + /* parallel mode, 8 bits, generic */ + lcd.proto = LCD_PROTO_PARALLEL; + lcd.charset = LCD_CHARSET_NORMAL; + lcd.pins.e = PIN_AUTOLF; + lcd.pins.rs = PIN_SELECP; + lcd.pins.rw = PIN_INITP; + + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; + break; + case LCD_TYPE_CUSTOM: + /* customer-defined */ + lcd.proto = DEFAULT_LCD_PROTO; + lcd.charset = DEFAULT_LCD_CHARSET; + /* default geometry will be set later */ + break; + case LCD_TYPE_HANTRONIX: + /* parallel mode, 8 bits, hantronix-like */ + default: + lcd.proto = LCD_PROTO_PARALLEL; + lcd.charset = LCD_CHARSET_NORMAL; + lcd.pins.e = PIN_STROBE; + lcd.pins.rs = PIN_SELECP; + + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; + break; + } + + /* Overwrite with module params set on loading */ + if (lcd_height != NOT_SET) + charlcd->height = lcd_height; + if (lcd_width != NOT_SET) + charlcd->width = lcd_width; + if (lcd_bwidth != NOT_SET) + charlcd->bwidth = lcd_bwidth; + if (lcd_hwidth != NOT_SET) + charlcd->hwidth = lcd_hwidth; + if (lcd_charset != NOT_SET) + lcd.charset = lcd_charset; + if (lcd_proto != NOT_SET) + lcd.proto = lcd_proto; + if (lcd_e_pin != PIN_NOT_SET) + lcd.pins.e = lcd_e_pin; + if (lcd_rs_pin != PIN_NOT_SET) + lcd.pins.rs = lcd_rs_pin; + if (lcd_rw_pin != PIN_NOT_SET) + lcd.pins.rw = lcd_rw_pin; + if (lcd_cl_pin != PIN_NOT_SET) + lcd.pins.cl = lcd_cl_pin; + if (lcd_da_pin != PIN_NOT_SET) + lcd.pins.da = lcd_da_pin; + if (lcd_bl_pin != PIN_NOT_SET) + lcd.pins.bl = lcd_bl_pin; + + /* this is used to catch wrong and default values */ + if (charlcd->width <= 0) + charlcd->width = DEFAULT_LCD_WIDTH; + if (charlcd->bwidth <= 0) + charlcd->bwidth = DEFAULT_LCD_BWIDTH; + if (charlcd->hwidth <= 0) + charlcd->hwidth = DEFAULT_LCD_HWIDTH; + if (charlcd->height <= 0) + charlcd->height = DEFAULT_LCD_HEIGHT; + + if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */ + charlcd->ops = &charlcd_serial_ops; + + if (lcd.pins.cl == PIN_NOT_SET) + lcd.pins.cl = DEFAULT_LCD_PIN_SCL; + if (lcd.pins.da == PIN_NOT_SET) + lcd.pins.da = DEFAULT_LCD_PIN_SDA; + + } else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */ + charlcd->ops = &charlcd_parallel_ops; + + if (lcd.pins.e == PIN_NOT_SET) + lcd.pins.e = DEFAULT_LCD_PIN_E; + if (lcd.pins.rs == PIN_NOT_SET) + lcd.pins.rs = DEFAULT_LCD_PIN_RS; + if (lcd.pins.rw == PIN_NOT_SET) + lcd.pins.rw = DEFAULT_LCD_PIN_RW; + } else { + charlcd->ops = &charlcd_tilcd_ops; + } + + if (lcd.pins.bl == PIN_NOT_SET) + lcd.pins.bl = DEFAULT_LCD_PIN_BL; + + if (lcd.pins.e == PIN_NOT_SET) + lcd.pins.e = PIN_NONE; + if (lcd.pins.rs == PIN_NOT_SET) + lcd.pins.rs = PIN_NONE; + if (lcd.pins.rw == PIN_NOT_SET) + lcd.pins.rw = PIN_NONE; + if (lcd.pins.bl == PIN_NOT_SET) + lcd.pins.bl = PIN_NONE; + if (lcd.pins.cl == PIN_NOT_SET) + lcd.pins.cl = PIN_NONE; + if (lcd.pins.da == PIN_NOT_SET) + lcd.pins.da = PIN_NONE; + + if (lcd.charset == NOT_SET) + lcd.charset = DEFAULT_LCD_CHARSET; + + if (lcd.charset == LCD_CHARSET_KS0074) + charlcd->char_conv = lcd_char_conv_ks0074; + else + charlcd->char_conv = NULL; + + pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E], + lcd_bits[LCD_PORT_C][LCD_BIT_E]); + pin_to_bits(lcd.pins.rs, lcd_bits[LCD_PORT_D][LCD_BIT_RS], + lcd_bits[LCD_PORT_C][LCD_BIT_RS]); + pin_to_bits(lcd.pins.rw, lcd_bits[LCD_PORT_D][LCD_BIT_RW], + lcd_bits[LCD_PORT_C][LCD_BIT_RW]); + pin_to_bits(lcd.pins.bl, lcd_bits[LCD_PORT_D][LCD_BIT_BL], + lcd_bits[LCD_PORT_C][LCD_BIT_BL]); + pin_to_bits(lcd.pins.cl, lcd_bits[LCD_PORT_D][LCD_BIT_CL], + lcd_bits[LCD_PORT_C][LCD_BIT_CL]); + pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA], + lcd_bits[LCD_PORT_C][LCD_BIT_DA]); + + lcd.charlcd = charlcd; + lcd.initialized = true; +} + +/* + * These are the file operation function for user access to /dev/keypad + */ + +static ssize_t keypad_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + unsigned i = *ppos; + char __user *tmp = buf; + + if (keypad_buflen == 0) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(keypad_read_wait, + keypad_buflen != 0)) + return -EINTR; + } + + for (; count-- > 0 && (keypad_buflen > 0); + ++i, ++tmp, --keypad_buflen) { + put_user(keypad_buffer[keypad_start], tmp); + keypad_start = (keypad_start + 1) % KEYPAD_BUFFER; + } + *ppos = i; + + return tmp - buf; +} + +static int keypad_open(struct inode *inode, struct file *file) +{ + if (!atomic_dec_and_test(&keypad_available)) + return -EBUSY; /* open only once at a time */ + + if (file->f_mode & FMODE_WRITE) /* device is read-only */ + return -EPERM; + + keypad_buflen = 0; /* flush the buffer on opening */ + return 0; +} + +static int keypad_release(struct inode *inode, struct file *file) +{ + atomic_inc(&keypad_available); + return 0; +} + +static const struct file_operations keypad_fops = { + .read = keypad_read, /* read */ + .open = keypad_open, /* open */ + .release = keypad_release, /* close */ + .llseek = default_llseek, +}; + +static struct miscdevice keypad_dev = { + .minor = KEYPAD_MINOR, + .name = "keypad", + .fops = &keypad_fops, +}; + +static void keypad_send_key(const char *string, int max_len) +{ + /* send the key to the device only if a process is attached to it. */ + if (!atomic_read(&keypad_available)) { + while (max_len-- && keypad_buflen < KEYPAD_BUFFER && *string) { + keypad_buffer[(keypad_start + keypad_buflen++) % + KEYPAD_BUFFER] = *string++; + } + wake_up_interruptible(&keypad_read_wait); + } +} + +/* this function scans all the bits involving at least one logical signal, + * and puts the results in the bitfield "phys_read" (one bit per established + * contact), and sets "phys_read_prev" to "phys_read". + * + * Note: to debounce input signals, we will only consider as switched a signal + * which is stable across 2 measures. Signals which are different between two + * reads will be kept as they previously were in their logical form (phys_prev). + * A signal which has just switched will have a 1 in + * (phys_read ^ phys_read_prev). + */ +static void phys_scan_contacts(void) +{ + int bit, bitval; + char oldval; + char bitmask; + char gndmask; + + phys_prev = phys_curr; + phys_read_prev = phys_read; + phys_read = 0; /* flush all signals */ + + /* keep track of old value, with all outputs disabled */ + oldval = r_dtr(pprt) | scan_mask_o; + /* activate all keyboard outputs (active low) */ + w_dtr(pprt, oldval & ~scan_mask_o); + + /* will have a 1 for each bit set to gnd */ + bitmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; + /* disable all matrix signals */ + w_dtr(pprt, oldval); + + /* now that all outputs are cleared, the only active input bits are + * directly connected to the ground + */ + + /* 1 for each grounded input */ + gndmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; + + /* grounded inputs are signals 40-44 */ + phys_read |= (__u64)gndmask << 40; + + if (bitmask != gndmask) { + /* + * since clearing the outputs changed some inputs, we know + * that some input signals are currently tied to some outputs. + * So we'll scan them. + */ + for (bit = 0; bit < 8; bit++) { + bitval = BIT(bit); + + if (!(scan_mask_o & bitval)) /* skip unused bits */ + continue; + + w_dtr(pprt, oldval & ~bitval); /* enable this output */ + bitmask = PNL_PINPUT(r_str(pprt)) & ~gndmask; + phys_read |= (__u64)bitmask << (5 * bit); + } + w_dtr(pprt, oldval); /* disable all outputs */ + } + /* + * this is easy: use old bits when they are flapping, + * use new ones when stable + */ + phys_curr = (phys_prev & (phys_read ^ phys_read_prev)) | + (phys_read & ~(phys_read ^ phys_read_prev)); +} + +static inline int input_state_high(struct logical_input *input) +{ +#if 0 + /* FIXME: + * this is an invalid test. It tries to catch + * transitions from single-key to multiple-key, but + * doesn't take into account the contacts polarity. + * The only solution to the problem is to parse keys + * from the most complex to the simplest combinations, + * and mark them as 'caught' once a combination + * matches, then unmatch it for all other ones. + */ + + /* try to catch dangerous transitions cases : + * someone adds a bit, so this signal was a false + * positive resulting from a transition. We should + * invalidate the signal immediately and not call the + * release function. + * eg: 0 -(press A)-> A -(press B)-> AB : don't match A's release. + */ + if (((phys_prev & input->mask) == input->value) && + ((phys_curr & input->mask) > input->value)) { + input->state = INPUT_ST_LOW; /* invalidate */ + return 1; + } +#endif + + if ((phys_curr & input->mask) == input->value) { + if ((input->type == INPUT_TYPE_STD) && + (input->high_timer == 0)) { + input->high_timer++; + if (input->u.std.press_fct) + input->u.std.press_fct(input->u.std.press_data); + } else if (input->type == INPUT_TYPE_KBD) { + /* will turn on the light */ + keypressed = 1; + + if (input->high_timer == 0) { + char *press_str = input->u.kbd.press_str; + + if (press_str[0]) { + int s = sizeof(input->u.kbd.press_str); + + keypad_send_key(press_str, s); + } + } + + if (input->u.kbd.repeat_str[0]) { + char *repeat_str = input->u.kbd.repeat_str; + + if (input->high_timer >= KEYPAD_REP_START) { + int s = sizeof(input->u.kbd.repeat_str); + + input->high_timer -= KEYPAD_REP_DELAY; + keypad_send_key(repeat_str, s); + } + /* we will need to come back here soon */ + inputs_stable = 0; + } + + if (input->high_timer < 255) + input->high_timer++; + } + return 1; + } + + /* else signal falling down. Let's fall through. */ + input->state = INPUT_ST_FALLING; + input->fall_timer = 0; + + return 0; +} + +static inline void input_state_falling(struct logical_input *input) +{ +#if 0 + /* FIXME !!! same comment as in input_state_high */ + if (((phys_prev & input->mask) == input->value) && + ((phys_curr & input->mask) > input->value)) { + input->state = INPUT_ST_LOW; /* invalidate */ + return; + } +#endif + + if ((phys_curr & input->mask) == input->value) { + if (input->type == INPUT_TYPE_KBD) { + /* will turn on the light */ + keypressed = 1; + + if (input->u.kbd.repeat_str[0]) { + char *repeat_str = input->u.kbd.repeat_str; + + if (input->high_timer >= KEYPAD_REP_START) { + int s = sizeof(input->u.kbd.repeat_str); + + input->high_timer -= KEYPAD_REP_DELAY; + keypad_send_key(repeat_str, s); + } + /* we will need to come back here soon */ + inputs_stable = 0; + } + + if (input->high_timer < 255) + input->high_timer++; + } + input->state = INPUT_ST_HIGH; + } else if (input->fall_timer >= input->fall_time) { + /* call release event */ + if (input->type == INPUT_TYPE_STD) { + void (*release_fct)(int) = input->u.std.release_fct; + + if (release_fct) + release_fct(input->u.std.release_data); + } else if (input->type == INPUT_TYPE_KBD) { + char *release_str = input->u.kbd.release_str; + + if (release_str[0]) { + int s = sizeof(input->u.kbd.release_str); + + keypad_send_key(release_str, s); + } + } + + input->state = INPUT_ST_LOW; + } else { + input->fall_timer++; + inputs_stable = 0; + } +} + +static void panel_process_inputs(void) +{ + struct list_head *item; + struct logical_input *input; + + keypressed = 0; + inputs_stable = 1; + list_for_each(item, &logical_inputs) { + input = list_entry(item, struct logical_input, list); + + switch (input->state) { + case INPUT_ST_LOW: + if ((phys_curr & input->mask) != input->value) + break; + /* if all needed ones were already set previously, + * this means that this logical signal has been + * activated by the releasing of another combined + * signal, so we don't want to match. + * eg: AB -(release B)-> A -(release A)-> 0 : + * don't match A. + */ + if ((phys_prev & input->mask) == input->value) + break; + input->rise_timer = 0; + input->state = INPUT_ST_RISING; + /* no break here, fall through */ + case INPUT_ST_RISING: + if ((phys_curr & input->mask) != input->value) { + input->state = INPUT_ST_LOW; + break; + } + if (input->rise_timer < input->rise_time) { + inputs_stable = 0; + input->rise_timer++; + break; + } + input->high_timer = 0; + input->state = INPUT_ST_HIGH; + /* no break here, fall through */ + case INPUT_ST_HIGH: + if (input_state_high(input)) + break; + /* no break here, fall through */ + case INPUT_ST_FALLING: + input_state_falling(input); + } + } +} + +static void panel_scan_timer(void) +{ + if (keypad.enabled && keypad_initialized) { + if (spin_trylock_irq(&pprt_lock)) { + phys_scan_contacts(); + + /* no need for the parport anymore */ + spin_unlock_irq(&pprt_lock); + } + + if (!inputs_stable || phys_curr != phys_prev) + panel_process_inputs(); + } + + if (keypressed && lcd.enabled && lcd.initialized) + charlcd_poke(lcd.charlcd); + + mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); +} + +static void init_scan_timer(void) +{ + if (scan_timer.function) + return; /* already started */ + + setup_timer(&scan_timer, (void *)&panel_scan_timer, 0); + scan_timer.expires = jiffies + INPUT_POLL_TIME; + add_timer(&scan_timer); +} + +/* converts a name of the form "({BbAaPpSsEe}{01234567-})*" to a series of bits. + * if or are non-null, they will be or'ed with the bits + * corresponding to out and in bits respectively. + * returns 1 if ok, 0 if error (in which case, nothing is written). + */ +static u8 input_name2mask(const char *name, __u64 *mask, __u64 *value, + u8 *imask, u8 *omask) +{ + const char sigtab[] = "EeSsPpAaBb"; + u8 im, om; + __u64 m, v; + + om = 0; + im = 0; + m = 0ULL; + v = 0ULL; + while (*name) { + int in, out, bit, neg; + const char *idx; + + idx = strchr(sigtab, *name); + if (!idx) + return 0; /* input name not found */ + + in = idx - sigtab; + neg = (in & 1); /* odd (lower) names are negated */ + in >>= 1; + im |= BIT(in); + + name++; + if (*name >= '0' && *name <= '7') { + out = *name - '0'; + om |= BIT(out); + } else if (*name == '-') { + out = 8; + } else { + return 0; /* unknown bit name */ + } + + bit = (out * 5) + in; + + m |= 1ULL << bit; + if (!neg) + v |= 1ULL << bit; + name++; + } + *mask = m; + *value = v; + if (imask) + *imask |= im; + if (omask) + *omask |= om; + return 1; +} + +/* tries to bind a key to the signal name . The key will send the + * strings , , for these respective events. + * Returns the pointer to the new key if ok, NULL if the key could not be bound. + */ +static struct logical_input *panel_bind_key(const char *name, const char *press, + const char *repeat, + const char *release) +{ + struct logical_input *key; + + key = kzalloc(sizeof(*key), GFP_KERNEL); + if (!key) + return NULL; + + if (!input_name2mask(name, &key->mask, &key->value, &scan_mask_i, + &scan_mask_o)) { + kfree(key); + return NULL; + } + + key->type = INPUT_TYPE_KBD; + key->state = INPUT_ST_LOW; + key->rise_time = 1; + key->fall_time = 1; + + strncpy(key->u.kbd.press_str, press, sizeof(key->u.kbd.press_str)); + strncpy(key->u.kbd.repeat_str, repeat, sizeof(key->u.kbd.repeat_str)); + strncpy(key->u.kbd.release_str, release, + sizeof(key->u.kbd.release_str)); + list_add(&key->list, &logical_inputs); + return key; +} + +#if 0 +/* tries to bind a callback function to the signal name . The function + * will be called with the arg when the signal is + * activated, and so on for / + * Returns the pointer to the new signal if ok, NULL if the signal could not + * be bound. + */ +static struct logical_input *panel_bind_callback(char *name, + void (*press_fct)(int), + int press_data, + void (*release_fct)(int), + int release_data) +{ + struct logical_input *callback; + + callback = kmalloc(sizeof(*callback), GFP_KERNEL); + if (!callback) + return NULL; + + memset(callback, 0, sizeof(struct logical_input)); + if (!input_name2mask(name, &callback->mask, &callback->value, + &scan_mask_i, &scan_mask_o)) + return NULL; + + callback->type = INPUT_TYPE_STD; + callback->state = INPUT_ST_LOW; + callback->rise_time = 1; + callback->fall_time = 1; + callback->u.std.press_fct = press_fct; + callback->u.std.press_data = press_data; + callback->u.std.release_fct = release_fct; + callback->u.std.release_data = release_data; + list_add(&callback->list, &logical_inputs); + return callback; +} +#endif + +static void keypad_init(void) +{ + int keynum; + + init_waitqueue_head(&keypad_read_wait); + keypad_buflen = 0; /* flushes any eventual noisy keystroke */ + + /* Let's create all known keys */ + + for (keynum = 0; keypad_profile[keynum][0][0]; keynum++) { + panel_bind_key(keypad_profile[keynum][0], + keypad_profile[keynum][1], + keypad_profile[keynum][2], + keypad_profile[keynum][3]); + } + + init_scan_timer(); + keypad_initialized = 1; +} + +/**************************************************/ +/* device initialization */ +/**************************************************/ + +static void panel_attach(struct parport *port) +{ + struct pardev_cb panel_cb; + + if (port->number != parport) + return; + + if (pprt) { + pr_err("%s: port->number=%d parport=%d, already registered!\n", + __func__, port->number, parport); + return; + } + + memset(&panel_cb, 0, sizeof(panel_cb)); + panel_cb.private = &pprt; + /* panel_cb.flags = 0 should be PARPORT_DEV_EXCL? */ + + pprt = parport_register_dev_model(port, "panel", &panel_cb, 0); + if (!pprt) { + pr_err("%s: port->number=%d parport=%d, parport_register_device() failed\n", + __func__, port->number, parport); + return; + } + + if (parport_claim(pprt)) { + pr_err("could not claim access to parport%d. Aborting.\n", + parport); + goto err_unreg_device; + } + + /* must init LCD first, just in case an IRQ from the keypad is + * generated at keypad init + */ + if (lcd.enabled) { + lcd_init(); + if (!lcd.charlcd || charlcd_register(lcd.charlcd)) + goto err_unreg_device; + } + + if (keypad.enabled) { + keypad_init(); + if (misc_register(&keypad_dev)) + goto err_lcd_unreg; + } + return; + +err_lcd_unreg: + if (lcd.enabled) + charlcd_unregister(lcd.charlcd); +err_unreg_device: + kfree(lcd.charlcd); + lcd.charlcd = NULL; + parport_unregister_device(pprt); + pprt = NULL; +} + +static void panel_detach(struct parport *port) +{ + if (port->number != parport) + return; + + if (!pprt) { + pr_err("%s: port->number=%d parport=%d, nothing to unregister.\n", + __func__, port->number, parport); + return; + } + if (scan_timer.function) + del_timer_sync(&scan_timer); + + if (keypad.enabled) { + misc_deregister(&keypad_dev); + keypad_initialized = 0; + } + + if (lcd.enabled) { + charlcd_unregister(lcd.charlcd); + lcd.initialized = false; + kfree(lcd.charlcd); + lcd.charlcd = NULL; + } + + /* TODO: free all input signals */ + parport_release(pprt); + parport_unregister_device(pprt); + pprt = NULL; +} + +static struct parport_driver panel_driver = { + .name = "panel", + .match_port = panel_attach, + .detach = panel_detach, + .devmodel = true, +}; + +/* init function */ +static int __init panel_init_module(void) +{ + int selected_keypad_type = NOT_SET, err; + + /* take care of an eventual profile */ + switch (profile) { + case PANEL_PROFILE_CUSTOM: + /* custom profile */ + selected_keypad_type = DEFAULT_KEYPAD_TYPE; + selected_lcd_type = DEFAULT_LCD_TYPE; + break; + case PANEL_PROFILE_OLD: + /* 8 bits, 2*16, old keypad */ + selected_keypad_type = KEYPAD_TYPE_OLD; + selected_lcd_type = LCD_TYPE_OLD; + + /* TODO: This two are a little hacky, sort it out later */ + if (lcd_width == NOT_SET) + lcd_width = 16; + if (lcd_hwidth == NOT_SET) + lcd_hwidth = 16; + break; + case PANEL_PROFILE_NEW: + /* serial, 2*16, new keypad */ + selected_keypad_type = KEYPAD_TYPE_NEW; + selected_lcd_type = LCD_TYPE_KS0074; + break; + case PANEL_PROFILE_HANTRONIX: + /* 8 bits, 2*16 hantronix-like, no keypad */ + selected_keypad_type = KEYPAD_TYPE_NONE; + selected_lcd_type = LCD_TYPE_HANTRONIX; + break; + case PANEL_PROFILE_NEXCOM: + /* generic 8 bits, 2*16, nexcom keypad, eg. Nexcom. */ + selected_keypad_type = KEYPAD_TYPE_NEXCOM; + selected_lcd_type = LCD_TYPE_NEXCOM; + break; + case PANEL_PROFILE_LARGE: + /* 8 bits, 2*40, old keypad */ + selected_keypad_type = KEYPAD_TYPE_OLD; + selected_lcd_type = LCD_TYPE_OLD; + break; + } + + /* + * Overwrite selection with module param values (both keypad and lcd), + * where the deprecated params have lower prio. + */ + if (keypad_enabled != NOT_SET) + selected_keypad_type = keypad_enabled; + if (keypad_type != NOT_SET) + selected_keypad_type = keypad_type; + + keypad.enabled = (selected_keypad_type > 0); + + if (lcd_enabled != NOT_SET) + selected_lcd_type = lcd_enabled; + if (lcd_type != NOT_SET) + selected_lcd_type = lcd_type; + + lcd.enabled = (selected_lcd_type > 0); + + if (lcd.enabled) { + /* + * Init lcd struct with load-time values to preserve exact + * current functionality (at least for now). + */ + lcd.charset = lcd_charset; + lcd.proto = lcd_proto; + lcd.pins.e = lcd_e_pin; + lcd.pins.rs = lcd_rs_pin; + lcd.pins.rw = lcd_rw_pin; + lcd.pins.cl = lcd_cl_pin; + lcd.pins.da = lcd_da_pin; + lcd.pins.bl = lcd_bl_pin; + } + + switch (selected_keypad_type) { + case KEYPAD_TYPE_OLD: + keypad_profile = old_keypad_profile; + break; + case KEYPAD_TYPE_NEW: + keypad_profile = new_keypad_profile; + break; + case KEYPAD_TYPE_NEXCOM: + keypad_profile = nexcom_keypad_profile; + break; + default: + keypad_profile = NULL; + break; + } + + if (!lcd.enabled && !keypad.enabled) { + /* no device enabled, let's exit */ + pr_err("panel driver disabled.\n"); + return -ENODEV; + } + + err = parport_register_driver(&panel_driver); + if (err) { + pr_err("could not register with parport. Aborting.\n"); + return err; + } + + if (pprt) + pr_info("panel driver registered on parport%d (io=0x%lx).\n", + parport, pprt->port->base); + else + pr_info("panel driver not yet registered\n"); + return 0; +} + +static void __exit panel_cleanup_module(void) +{ + parport_unregister_driver(&panel_driver); +} + +module_init(panel_init_module); +module_exit(panel_cleanup_module); +MODULE_AUTHOR("Willy Tarreau"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-indent-level: 4 + * tab-width: 8 + * End: + */ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index fb933b0b9297..2438466c15ac 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -492,286 +492,6 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. -config PANEL - tristate "Parallel port LCD/Keypad Panel support" - depends on PARPORT - select CHARLCD - ---help--- - Say Y here if you have an HD44780 or KS-0074 LCD connected to your - parallel port. This driver also features 4 and 6-key keypads. The LCD - is accessible through the /dev/lcd char device (10, 156), and the - keypad through /dev/keypad (10, 185). This code can either be - compiled as a module, or linked into the kernel and started at boot. - If you don't understand what all this is about, say N. - -if PANEL - -config PANEL_PARPORT - int "Default parallel port number (0=LPT1)" - range 0 255 - default "0" - ---help--- - This is the index of the parallel port the panel is connected to. One - driver instance only supports one parallel port, so if your keypad - and LCD are connected to two separate ports, you have to start two - modules with different arguments. Numbering starts with '0' for LPT1, - and so on. - -config PANEL_PROFILE - int "Default panel profile (0-5, 0=custom)" - range 0 5 - default "5" - ---help--- - To ease configuration, the driver supports different configuration - profiles for past and recent wirings. These profiles can also be - used to define an approximative configuration, completed by a few - other options. Here are the profiles : - - 0 = custom (see further) - 1 = 2x16 parallel LCD, old keypad - 2 = 2x16 serial LCD (KS-0074), new keypad - 3 = 2x16 parallel LCD (Hantronix), no keypad - 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad - 5 = 2x40 parallel LCD (old one), with old keypad - - Custom configurations allow you to define how your display is - wired to the parallel port, and how it works. This is only intended - for experts. - -config PANEL_KEYPAD - depends on PANEL_PROFILE="0" - int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)" - range 0 3 - default 0 - ---help--- - This enables and configures a keypad connected to the parallel port. - The keys will be read from character device 10,185. Valid values are : - - 0 : do not enable this driver - 1 : old 6 keys keypad - 2 : new 6 keys keypad, as used on the server at www.ant-computing.com - 3 : Nexcom NSA1045's 4 keys keypad - - New profiles can be described in the driver source. The driver also - supports simultaneous keys pressed when the keypad supports them. - -config PANEL_LCD - depends on PANEL_PROFILE="0" - int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)" - range 0 5 - default 0 - ---help--- - This enables and configures an LCD connected to the parallel port. - The driver includes an interpreter for escape codes starting with - '\e[L' which are specific to the LCD, and a few ANSI codes. The - driver will be registered as character device 10,156, usually - under the name '/dev/lcd'. There are a total of 6 supported types : - - 0 : do not enable the driver - 1 : custom configuration and wiring (see further) - 2 : 2x16 & 2x40 parallel LCD (old wiring) - 3 : 2x16 serial LCD (KS-0074 based) - 4 : 2x16 parallel LCD (Hantronix wiring) - 5 : 2x16 parallel LCD (Nexcom wiring) - - When type '1' is specified, other options will appear to configure - more precise aspects (wiring, dimensions, protocol, ...). Please note - that those values changed from the 2.4 driver for better consistency. - -config PANEL_LCD_HEIGHT - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Number of lines on the LCD (1-2)" - range 1 2 - default 2 - ---help--- - This is the number of visible character lines on the LCD in custom profile. - It can either be 1 or 2. - -config PANEL_LCD_WIDTH - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Number of characters per line on the LCD (1-40)" - range 1 40 - default 40 - ---help--- - This is the number of characters per line on the LCD in custom profile. - Common values are 16,20,24,40. - -config PANEL_LCD_BWIDTH - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Internal LCD line width (1-40, 40 by default)" - range 1 40 - default 40 - ---help--- - Most LCDs use a standard controller which supports hardware lines of 40 - characters, although sometimes only 16, 20 or 24 of them are really wired - to the terminal. This results in some non-visible but addressable characters, - and is the case for most parallel LCDs. Other LCDs, and some serial ones, - however, use the same line width internally as what is visible. The KS0074 - for example, uses 16 characters per line for 16 visible characters per line. - - This option lets you configure the value used by your LCD in 'custom' profile. - If you don't know, put '40' here. - -config PANEL_LCD_HWIDTH - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Hardware LCD line width (1-64, 64 by default)" - range 1 64 - default 64 - ---help--- - Most LCDs use a single address bit to differentiate line 0 and line 1. Since - some of them need to be able to address 40 chars with the lower bits, they - often use the immediately superior power of 2, which is 64, to address the - next line. - - If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and - 64 here for a 2x40. - -config PANEL_LCD_CHARSET - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "LCD character set (0=normal, 1=KS0074)" - range 0 1 - default 0 - ---help--- - Some controllers such as the KS0074 use a somewhat strange character set - where many symbols are at unusual places. The driver knows how to map - 'standard' ASCII characters to the character sets used by these controllers. - Valid values are : - - 0 : normal (untranslated) character set - 1 : KS0074 character set - - If you don't know, use the normal one (0). - -config PANEL_LCD_PROTO - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "LCD communication mode (0=parallel 8 bits, 1=serial)" - range 0 1 - default 0 - ---help--- - This driver now supports any serial or parallel LCD wired to a parallel - port. But before assigning signals, the driver needs to know if it will - be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires - (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals - (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits - parallel LCD, and 1 for a serial LCD. - -config PANEL_LCD_PIN_E - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" - int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) " - range -17 17 - default 14 - ---help--- - This describes the number of the parallel port pin to which the LCD 'E' - signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'E' pin in custom profile is '14' (AUTOFEED). - -config PANEL_LCD_PIN_RS - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" - int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) " - range -17 17 - default 17 - ---help--- - This describes the number of the parallel port pin to which the LCD 'RS' - signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'RS' pin in custom profile is '17' (SELECT IN). - -config PANEL_LCD_PIN_RW - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" - int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) " - range -17 17 - default 16 - ---help--- - This describes the number of the parallel port pin to which the LCD 'RW' - signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'RW' pin in custom profile is '16' (INIT). - -config PANEL_LCD_PIN_SCL - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" - int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) " - range -17 17 - default 1 - ---help--- - This describes the number of the parallel port pin to which the serial - LCD 'SCL' signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'SCL' pin in custom profile is '1' (STROBE). - -config PANEL_LCD_PIN_SDA - depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" - int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) " - range -17 17 - default 2 - ---help--- - This describes the number of the parallel port pin to which the serial - LCD 'SDA' signal has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'SDA' pin in custom profile is '2' (D0). - -config PANEL_LCD_PIN_BL - depends on PANEL_PROFILE="0" && PANEL_LCD="1" - int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) " - range -17 17 - default 0 - ---help--- - This describes the number of the parallel port pin to which the LCD 'BL' signal - has been connected. It can be : - - 0 : no connection (eg: connected to ground) - 1..17 : directly connected to any of these pins on the DB25 plug - -1..-17 : connected to the same pin through an inverter (eg: transistor). - - Default for the 'BL' pin in custom profile is '0' (uncontrolled). - -config PANEL_CHANGE_MESSAGE - bool "Change LCD initialization message ?" - default "n" - ---help--- - This allows you to replace the boot message indicating the kernel version - and the driver version with a custom message. This is useful on appliances - where a simple 'Starting system' message can be enough to stop a customer - from worrying. - - If you say 'Y' here, you'll be able to choose a message yourself. Otherwise, - say 'N' and keep the default message with the version. - -config PANEL_BOOT_MESSAGE - depends on PANEL_CHANGE_MESSAGE="y" - string "New initialization message" - default "" - ---help--- - This allows you to replace the boot message indicating the kernel version - and the driver version with a custom message. This is useful on appliances - where a simple 'Starting system' message can be enough to stop a customer - from worrying. - - An empty message will only clear the display at driver init time. Any other - printf()-formatted message is valid with newline and escape codes. - -endif # PANEL - config ASPEED_LPC_CTRL depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 4925ea8e1952..9a9f7e72aa54 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -53,7 +53,6 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ -obj-$(CONFIG_PANEL) += panel.o obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o diff --git a/drivers/misc/panel.c b/drivers/misc/panel.c deleted file mode 100644 index e0c014c2356f..000000000000 --- a/drivers/misc/panel.c +++ /dev/null @@ -1,1796 +0,0 @@ -/* - * Front panel driver for Linux - * Copyright (C) 2000-2008, Willy Tarreau - * Copyright (C) 2016-2017 Glider bvba - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * This code drives an LCD module (/dev/lcd), and a keypad (/dev/keypad) - * connected to a parallel printer port. - * - * The LCD module may either be an HD44780-like 8-bit parallel LCD, or a 1-bit - * serial module compatible with Samsung's KS0074. The pins may be connected in - * any combination, everything is programmable. - * - * The keypad consists in a matrix of push buttons connecting input pins to - * data output pins or to the ground. The combinations have to be hard-coded - * in the driver, though several profiles exist and adding new ones is easy. - * - * Several profiles are provided for commonly found LCD+keypad modules on the - * market, such as those found in Nexcom's appliances. - * - * FIXME: - * - the initialization/deinitialization process is very dirty and should - * be rewritten. It may even be buggy. - * - * TODO: - * - document 24 keys keyboard (3 rows of 8 cols, 32 diodes + 2 inputs) - * - make the LCD a part of a virtual screen of Vx*Vy - * - make the inputs list smp-safe - * - change the keyboard to a double mapping : signals -> key_id -> values - * so that applications can change values without knowing signals - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#define KEYPAD_MINOR 185 - -#define LCD_MAXBYTES 256 /* max burst write */ - -#define KEYPAD_BUFFER 64 - -/* poll the keyboard this every second */ -#define INPUT_POLL_TIME (HZ / 50) -/* a key starts to repeat after this times INPUT_POLL_TIME */ -#define KEYPAD_REP_START (10) -/* a key repeats this times INPUT_POLL_TIME */ -#define KEYPAD_REP_DELAY (2) - -/* converts an r_str() input to an active high, bits string : 000BAOSE */ -#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3) - -#define PNL_PBUSY 0x80 /* inverted input, active low */ -#define PNL_PACK 0x40 /* direct input, active low */ -#define PNL_POUTPA 0x20 /* direct input, active high */ -#define PNL_PSELECD 0x10 /* direct input, active high */ -#define PNL_PERRORP 0x08 /* direct input, active low */ - -#define PNL_PBIDIR 0x20 /* bi-directional ports */ -/* high to read data in or-ed with data out */ -#define PNL_PINTEN 0x10 -#define PNL_PSELECP 0x08 /* inverted output, active low */ -#define PNL_PINITP 0x04 /* direct output, active low */ -#define PNL_PAUTOLF 0x02 /* inverted output, active low */ -#define PNL_PSTROBE 0x01 /* inverted output */ - -#define PNL_PD0 0x01 -#define PNL_PD1 0x02 -#define PNL_PD2 0x04 -#define PNL_PD3 0x08 -#define PNL_PD4 0x10 -#define PNL_PD5 0x20 -#define PNL_PD6 0x40 -#define PNL_PD7 0x80 - -#define PIN_NONE 0 -#define PIN_STROBE 1 -#define PIN_D0 2 -#define PIN_D1 3 -#define PIN_D2 4 -#define PIN_D3 5 -#define PIN_D4 6 -#define PIN_D5 7 -#define PIN_D6 8 -#define PIN_D7 9 -#define PIN_AUTOLF 14 -#define PIN_INITP 16 -#define PIN_SELECP 17 -#define PIN_NOT_SET 127 - -#define NOT_SET -1 - -/* macros to simplify use of the parallel port */ -#define r_ctr(x) (parport_read_control((x)->port)) -#define r_dtr(x) (parport_read_data((x)->port)) -#define r_str(x) (parport_read_status((x)->port)) -#define w_ctr(x, y) (parport_write_control((x)->port, (y))) -#define w_dtr(x, y) (parport_write_data((x)->port, (y))) - -/* this defines which bits are to be used and which ones to be ignored */ -/* logical or of the output bits involved in the scan matrix */ -static __u8 scan_mask_o; -/* logical or of the input bits involved in the scan matrix */ -static __u8 scan_mask_i; - -enum input_type { - INPUT_TYPE_STD, - INPUT_TYPE_KBD, -}; - -enum input_state { - INPUT_ST_LOW, - INPUT_ST_RISING, - INPUT_ST_HIGH, - INPUT_ST_FALLING, -}; - -struct logical_input { - struct list_head list; - __u64 mask; - __u64 value; - enum input_type type; - enum input_state state; - __u8 rise_time, fall_time; - __u8 rise_timer, fall_timer, high_timer; - - union { - struct { /* valid when type == INPUT_TYPE_STD */ - void (*press_fct)(int); - void (*release_fct)(int); - int press_data; - int release_data; - } std; - struct { /* valid when type == INPUT_TYPE_KBD */ - /* strings can be non null-terminated */ - char press_str[sizeof(void *) + sizeof(int)]; - char repeat_str[sizeof(void *) + sizeof(int)]; - char release_str[sizeof(void *) + sizeof(int)]; - } kbd; - } u; -}; - -static LIST_HEAD(logical_inputs); /* list of all defined logical inputs */ - -/* physical contacts history - * Physical contacts are a 45 bits string of 9 groups of 5 bits each. - * The 8 lower groups correspond to output bits 0 to 7, and the 9th group - * corresponds to the ground. - * Within each group, bits are stored in the same order as read on the port : - * BAPSE (busy=4, ack=3, paper empty=2, select=1, error=0). - * So, each __u64 is represented like this : - * 0000000000000000000BAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSE - * <-----unused------> - */ - -/* what has just been read from the I/O ports */ -static __u64 phys_read; -/* previous phys_read */ -static __u64 phys_read_prev; -/* stabilized phys_read (phys_read|phys_read_prev) */ -static __u64 phys_curr; -/* previous phys_curr */ -static __u64 phys_prev; -/* 0 means that at least one logical signal needs be computed */ -static char inputs_stable; - -/* these variables are specific to the keypad */ -static struct { - bool enabled; -} keypad; - -static char keypad_buffer[KEYPAD_BUFFER]; -static int keypad_buflen; -static int keypad_start; -static char keypressed; -static wait_queue_head_t keypad_read_wait; - -/* lcd-specific variables */ -static struct { - bool enabled; - bool initialized; - - int charset; - int proto; - - /* TODO: use union here? */ - struct { - int e; - int rs; - int rw; - int cl; - int da; - int bl; - } pins; - - struct charlcd *charlcd; -} lcd; - -/* Needed only for init */ -static int selected_lcd_type = NOT_SET; - -/* - * Bit masks to convert LCD signals to parallel port outputs. - * _d_ are values for data port, _c_ are for control port. - * [0] = signal OFF, [1] = signal ON, [2] = mask - */ -#define BIT_CLR 0 -#define BIT_SET 1 -#define BIT_MSK 2 -#define BIT_STATES 3 -/* - * one entry for each bit on the LCD - */ -#define LCD_BIT_E 0 -#define LCD_BIT_RS 1 -#define LCD_BIT_RW 2 -#define LCD_BIT_BL 3 -#define LCD_BIT_CL 4 -#define LCD_BIT_DA 5 -#define LCD_BITS 6 - -/* - * each bit can be either connected to a DATA or CTRL port - */ -#define LCD_PORT_C 0 -#define LCD_PORT_D 1 -#define LCD_PORTS 2 - -static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES]; - -/* - * LCD protocols - */ -#define LCD_PROTO_PARALLEL 0 -#define LCD_PROTO_SERIAL 1 -#define LCD_PROTO_TI_DA8XX_LCD 2 - -/* - * LCD character sets - */ -#define LCD_CHARSET_NORMAL 0 -#define LCD_CHARSET_KS0074 1 - -/* - * LCD types - */ -#define LCD_TYPE_NONE 0 -#define LCD_TYPE_CUSTOM 1 -#define LCD_TYPE_OLD 2 -#define LCD_TYPE_KS0074 3 -#define LCD_TYPE_HANTRONIX 4 -#define LCD_TYPE_NEXCOM 5 - -/* - * keypad types - */ -#define KEYPAD_TYPE_NONE 0 -#define KEYPAD_TYPE_OLD 1 -#define KEYPAD_TYPE_NEW 2 -#define KEYPAD_TYPE_NEXCOM 3 - -/* - * panel profiles - */ -#define PANEL_PROFILE_CUSTOM 0 -#define PANEL_PROFILE_OLD 1 -#define PANEL_PROFILE_NEW 2 -#define PANEL_PROFILE_HANTRONIX 3 -#define PANEL_PROFILE_NEXCOM 4 -#define PANEL_PROFILE_LARGE 5 - -/* - * Construct custom config from the kernel's configuration - */ -#define DEFAULT_PARPORT 0 -#define DEFAULT_PROFILE PANEL_PROFILE_LARGE -#define DEFAULT_KEYPAD_TYPE KEYPAD_TYPE_OLD -#define DEFAULT_LCD_TYPE LCD_TYPE_OLD -#define DEFAULT_LCD_HEIGHT 2 -#define DEFAULT_LCD_WIDTH 40 -#define DEFAULT_LCD_BWIDTH 40 -#define DEFAULT_LCD_HWIDTH 64 -#define DEFAULT_LCD_CHARSET LCD_CHARSET_NORMAL -#define DEFAULT_LCD_PROTO LCD_PROTO_PARALLEL - -#define DEFAULT_LCD_PIN_E PIN_AUTOLF -#define DEFAULT_LCD_PIN_RS PIN_SELECP -#define DEFAULT_LCD_PIN_RW PIN_INITP -#define DEFAULT_LCD_PIN_SCL PIN_STROBE -#define DEFAULT_LCD_PIN_SDA PIN_D0 -#define DEFAULT_LCD_PIN_BL PIN_NOT_SET - -#ifdef CONFIG_PANEL_PARPORT -#undef DEFAULT_PARPORT -#define DEFAULT_PARPORT CONFIG_PANEL_PARPORT -#endif - -#ifdef CONFIG_PANEL_PROFILE -#undef DEFAULT_PROFILE -#define DEFAULT_PROFILE CONFIG_PANEL_PROFILE -#endif - -#if DEFAULT_PROFILE == 0 /* custom */ -#ifdef CONFIG_PANEL_KEYPAD -#undef DEFAULT_KEYPAD_TYPE -#define DEFAULT_KEYPAD_TYPE CONFIG_PANEL_KEYPAD -#endif - -#ifdef CONFIG_PANEL_LCD -#undef DEFAULT_LCD_TYPE -#define DEFAULT_LCD_TYPE CONFIG_PANEL_LCD -#endif - -#ifdef CONFIG_PANEL_LCD_HEIGHT -#undef DEFAULT_LCD_HEIGHT -#define DEFAULT_LCD_HEIGHT CONFIG_PANEL_LCD_HEIGHT -#endif - -#ifdef CONFIG_PANEL_LCD_WIDTH -#undef DEFAULT_LCD_WIDTH -#define DEFAULT_LCD_WIDTH CONFIG_PANEL_LCD_WIDTH -#endif - -#ifdef CONFIG_PANEL_LCD_BWIDTH -#undef DEFAULT_LCD_BWIDTH -#define DEFAULT_LCD_BWIDTH CONFIG_PANEL_LCD_BWIDTH -#endif - -#ifdef CONFIG_PANEL_LCD_HWIDTH -#undef DEFAULT_LCD_HWIDTH -#define DEFAULT_LCD_HWIDTH CONFIG_PANEL_LCD_HWIDTH -#endif - -#ifdef CONFIG_PANEL_LCD_CHARSET -#undef DEFAULT_LCD_CHARSET -#define DEFAULT_LCD_CHARSET CONFIG_PANEL_LCD_CHARSET -#endif - -#ifdef CONFIG_PANEL_LCD_PROTO -#undef DEFAULT_LCD_PROTO -#define DEFAULT_LCD_PROTO CONFIG_PANEL_LCD_PROTO -#endif - -#ifdef CONFIG_PANEL_LCD_PIN_E -#undef DEFAULT_LCD_PIN_E -#define DEFAULT_LCD_PIN_E CONFIG_PANEL_LCD_PIN_E -#endif - -#ifdef CONFIG_PANEL_LCD_PIN_RS -#undef DEFAULT_LCD_PIN_RS -#define DEFAULT_LCD_PIN_RS CONFIG_PANEL_LCD_PIN_RS -#endif - -#ifdef CONFIG_PANEL_LCD_PIN_RW -#undef DEFAULT_LCD_PIN_RW -#define DEFAULT_LCD_PIN_RW CONFIG_PANEL_LCD_PIN_RW -#endif - -#ifdef CONFIG_PANEL_LCD_PIN_SCL -#undef DEFAULT_LCD_PIN_SCL -#define DEFAULT_LCD_PIN_SCL CONFIG_PANEL_LCD_PIN_SCL -#endif - -#ifdef CONFIG_PANEL_LCD_PIN_SDA -#undef DEFAULT_LCD_PIN_SDA -#define DEFAULT_LCD_PIN_SDA CONFIG_PANEL_LCD_PIN_SDA -#endif - -#ifdef CONFIG_PANEL_LCD_PIN_BL -#undef DEFAULT_LCD_PIN_BL -#define DEFAULT_LCD_PIN_BL CONFIG_PANEL_LCD_PIN_BL -#endif - -#endif /* DEFAULT_PROFILE == 0 */ - -/* global variables */ - -/* Device single-open policy control */ -static atomic_t keypad_available = ATOMIC_INIT(1); - -static struct pardevice *pprt; - -static int keypad_initialized; - -static DEFINE_SPINLOCK(pprt_lock); -static struct timer_list scan_timer; - -MODULE_DESCRIPTION("Generic parallel port LCD/Keypad driver"); - -static int parport = DEFAULT_PARPORT; -module_param(parport, int, 0000); -MODULE_PARM_DESC(parport, "Parallel port index (0=lpt1, 1=lpt2, ...)"); - -static int profile = DEFAULT_PROFILE; -module_param(profile, int, 0000); -MODULE_PARM_DESC(profile, - "1=16x2 old kp; 2=serial 16x2, new kp; 3=16x2 hantronix; " - "4=16x2 nexcom; default=40x2, old kp"); - -static int keypad_type = NOT_SET; -module_param(keypad_type, int, 0000); -MODULE_PARM_DESC(keypad_type, - "Keypad type: 0=none, 1=old 6 keys, 2=new 6+1 keys, 3=nexcom 4 keys"); - -static int lcd_type = NOT_SET; -module_param(lcd_type, int, 0000); -MODULE_PARM_DESC(lcd_type, - "LCD type: 0=none, 1=compiled-in, 2=old, 3=serial ks0074, 4=hantronix, 5=nexcom"); - -static int lcd_height = NOT_SET; -module_param(lcd_height, int, 0000); -MODULE_PARM_DESC(lcd_height, "Number of lines on the LCD"); - -static int lcd_width = NOT_SET; -module_param(lcd_width, int, 0000); -MODULE_PARM_DESC(lcd_width, "Number of columns on the LCD"); - -static int lcd_bwidth = NOT_SET; /* internal buffer width (usually 40) */ -module_param(lcd_bwidth, int, 0000); -MODULE_PARM_DESC(lcd_bwidth, "Internal LCD line width (40)"); - -static int lcd_hwidth = NOT_SET; /* hardware buffer width (usually 64) */ -module_param(lcd_hwidth, int, 0000); -MODULE_PARM_DESC(lcd_hwidth, "LCD line hardware address (64)"); - -static int lcd_charset = NOT_SET; -module_param(lcd_charset, int, 0000); -MODULE_PARM_DESC(lcd_charset, "LCD character set: 0=standard, 1=KS0074"); - -static int lcd_proto = NOT_SET; -module_param(lcd_proto, int, 0000); -MODULE_PARM_DESC(lcd_proto, - "LCD communication: 0=parallel (//), 1=serial, 2=TI LCD Interface"); - -/* - * These are the parallel port pins the LCD control signals are connected to. - * Set this to 0 if the signal is not used. Set it to its opposite value - * (negative) if the signal is negated. -MAXINT is used to indicate that the - * pin has not been explicitly specified. - * - * WARNING! no check will be performed about collisions with keypad ! - */ - -static int lcd_e_pin = PIN_NOT_SET; -module_param(lcd_e_pin, int, 0000); -MODULE_PARM_DESC(lcd_e_pin, - "# of the // port pin connected to LCD 'E' signal, with polarity (-17..17)"); - -static int lcd_rs_pin = PIN_NOT_SET; -module_param(lcd_rs_pin, int, 0000); -MODULE_PARM_DESC(lcd_rs_pin, - "# of the // port pin connected to LCD 'RS' signal, with polarity (-17..17)"); - -static int lcd_rw_pin = PIN_NOT_SET; -module_param(lcd_rw_pin, int, 0000); -MODULE_PARM_DESC(lcd_rw_pin, - "# of the // port pin connected to LCD 'RW' signal, with polarity (-17..17)"); - -static int lcd_cl_pin = PIN_NOT_SET; -module_param(lcd_cl_pin, int, 0000); -MODULE_PARM_DESC(lcd_cl_pin, - "# of the // port pin connected to serial LCD 'SCL' signal, with polarity (-17..17)"); - -static int lcd_da_pin = PIN_NOT_SET; -module_param(lcd_da_pin, int, 0000); -MODULE_PARM_DESC(lcd_da_pin, - "# of the // port pin connected to serial LCD 'SDA' signal, with polarity (-17..17)"); - -static int lcd_bl_pin = PIN_NOT_SET; -module_param(lcd_bl_pin, int, 0000); -MODULE_PARM_DESC(lcd_bl_pin, - "# of the // port pin connected to LCD backlight, with polarity (-17..17)"); - -/* Deprecated module parameters - consider not using them anymore */ - -static int lcd_enabled = NOT_SET; -module_param(lcd_enabled, int, 0000); -MODULE_PARM_DESC(lcd_enabled, "Deprecated option, use lcd_type instead"); - -static int keypad_enabled = NOT_SET; -module_param(keypad_enabled, int, 0000); -MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead"); - -/* for some LCD drivers (ks0074) we need a charset conversion table. */ -static const unsigned char lcd_char_conv_ks0074[256] = { - /* 0|8 1|9 2|A 3|B 4|C 5|D 6|E 7|F */ - /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - /* 0x08 */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - /* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - /* 0x18 */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - /* 0x20 */ 0x20, 0x21, 0x22, 0x23, 0xa2, 0x25, 0x26, 0x27, - /* 0x28 */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, - /* 0x30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - /* 0x38 */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - /* 0x40 */ 0xa0, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, - /* 0x48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - /* 0x50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, - /* 0x58 */ 0x58, 0x59, 0x5a, 0xfa, 0xfb, 0xfc, 0x1d, 0xc4, - /* 0x60 */ 0x96, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, - /* 0x68 */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, - /* 0x70 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, - /* 0x78 */ 0x78, 0x79, 0x7a, 0xfd, 0xfe, 0xff, 0xce, 0x20, - /* 0x80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - /* 0x88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - /* 0x90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, - /* 0x98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, - /* 0xA0 */ 0x20, 0x40, 0xb1, 0xa1, 0x24, 0xa3, 0xfe, 0x5f, - /* 0xA8 */ 0x22, 0xc8, 0x61, 0x14, 0x97, 0x2d, 0xad, 0x96, - /* 0xB0 */ 0x80, 0x8c, 0x82, 0x83, 0x27, 0x8f, 0x86, 0xdd, - /* 0xB8 */ 0x2c, 0x81, 0x6f, 0x15, 0x8b, 0x8a, 0x84, 0x60, - /* 0xC0 */ 0xe2, 0xe2, 0xe2, 0x5b, 0x5b, 0xae, 0xbc, 0xa9, - /* 0xC8 */ 0xc5, 0xbf, 0xc6, 0xf1, 0xe3, 0xe3, 0xe3, 0xe3, - /* 0xD0 */ 0x44, 0x5d, 0xa8, 0xe4, 0xec, 0xec, 0x5c, 0x78, - /* 0xD8 */ 0xab, 0xa6, 0xe5, 0x5e, 0x5e, 0xe6, 0xaa, 0xbe, - /* 0xE0 */ 0x7f, 0xe7, 0xaf, 0x7b, 0x7b, 0xaf, 0xbd, 0xc8, - /* 0xE8 */ 0xa4, 0xa5, 0xc7, 0xf6, 0xa7, 0xe8, 0x69, 0x69, - /* 0xF0 */ 0xed, 0x7d, 0xa8, 0xe4, 0xec, 0x5c, 0x5c, 0x25, - /* 0xF8 */ 0xac, 0xa6, 0xea, 0xef, 0x7e, 0xeb, 0xb2, 0x79, -}; - -static const char old_keypad_profile[][4][9] = { - {"S0", "Left\n", "Left\n", ""}, - {"S1", "Down\n", "Down\n", ""}, - {"S2", "Up\n", "Up\n", ""}, - {"S3", "Right\n", "Right\n", ""}, - {"S4", "Esc\n", "Esc\n", ""}, - {"S5", "Ret\n", "Ret\n", ""}, - {"", "", "", ""} -}; - -/* signals, press, repeat, release */ -static const char new_keypad_profile[][4][9] = { - {"S0", "Left\n", "Left\n", ""}, - {"S1", "Down\n", "Down\n", ""}, - {"S2", "Up\n", "Up\n", ""}, - {"S3", "Right\n", "Right\n", ""}, - {"S4s5", "", "Esc\n", "Esc\n"}, - {"s4S5", "", "Ret\n", "Ret\n"}, - {"S4S5", "Help\n", "", ""}, - /* add new signals above this line */ - {"", "", "", ""} -}; - -/* signals, press, repeat, release */ -static const char nexcom_keypad_profile[][4][9] = { - {"a-p-e-", "Down\n", "Down\n", ""}, - {"a-p-E-", "Ret\n", "Ret\n", ""}, - {"a-P-E-", "Esc\n", "Esc\n", ""}, - {"a-P-e-", "Up\n", "Up\n", ""}, - /* add new signals above this line */ - {"", "", "", ""} -}; - -static const char (*keypad_profile)[4][9] = old_keypad_profile; - -static DECLARE_BITMAP(bits, LCD_BITS); - -static void lcd_get_bits(unsigned int port, int *val) -{ - unsigned int bit, state; - - for (bit = 0; bit < LCD_BITS; bit++) { - state = test_bit(bit, bits) ? BIT_SET : BIT_CLR; - *val &= lcd_bits[port][bit][BIT_MSK]; - *val |= lcd_bits[port][bit][state]; - } -} - -/* sets data port bits according to current signals values */ -static int set_data_bits(void) -{ - int val; - - val = r_dtr(pprt); - lcd_get_bits(LCD_PORT_D, &val); - w_dtr(pprt, val); - return val; -} - -/* sets ctrl port bits according to current signals values */ -static int set_ctrl_bits(void) -{ - int val; - - val = r_ctr(pprt); - lcd_get_bits(LCD_PORT_C, &val); - w_ctr(pprt, val); - return val; -} - -/* sets ctrl & data port bits according to current signals values */ -static void panel_set_bits(void) -{ - set_data_bits(); - set_ctrl_bits(); -} - -/* - * Converts a parallel port pin (from -25 to 25) to data and control ports - * masks, and data and control port bits. The signal will be considered - * unconnected if it's on pin 0 or an invalid pin (<-25 or >25). - * - * Result will be used this way : - * out(dport, in(dport) & d_val[2] | d_val[signal_state]) - * out(cport, in(cport) & c_val[2] | c_val[signal_state]) - */ -static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val) -{ - int d_bit, c_bit, inv; - - d_val[0] = 0; - c_val[0] = 0; - d_val[1] = 0; - c_val[1] = 0; - d_val[2] = 0xFF; - c_val[2] = 0xFF; - - if (pin == 0) - return; - - inv = (pin < 0); - if (inv) - pin = -pin; - - d_bit = 0; - c_bit = 0; - - switch (pin) { - case PIN_STROBE: /* strobe, inverted */ - c_bit = PNL_PSTROBE; - inv = !inv; - break; - case PIN_D0...PIN_D7: /* D0 - D7 = 2 - 9 */ - d_bit = 1 << (pin - 2); - break; - case PIN_AUTOLF: /* autofeed, inverted */ - c_bit = PNL_PAUTOLF; - inv = !inv; - break; - case PIN_INITP: /* init, direct */ - c_bit = PNL_PINITP; - break; - case PIN_SELECP: /* select_in, inverted */ - c_bit = PNL_PSELECP; - inv = !inv; - break; - default: /* unknown pin, ignore */ - break; - } - - if (c_bit) { - c_val[2] &= ~c_bit; - c_val[!inv] = c_bit; - } else if (d_bit) { - d_val[2] &= ~d_bit; - d_val[!inv] = d_bit; - } -} - -/* - * send a serial byte to the LCD panel. The caller is responsible for locking - * if needed. - */ -static void lcd_send_serial(int byte) -{ - int bit; - - /* - * the data bit is set on D0, and the clock on STROBE. - * LCD reads D0 on STROBE's rising edge. - */ - for (bit = 0; bit < 8; bit++) { - clear_bit(LCD_BIT_CL, bits); /* CLK low */ - panel_set_bits(); - if (byte & 1) { - set_bit(LCD_BIT_DA, bits); - } else { - clear_bit(LCD_BIT_DA, bits); - } - - panel_set_bits(); - udelay(2); /* maintain the data during 2 us before CLK up */ - set_bit(LCD_BIT_CL, bits); /* CLK high */ - panel_set_bits(); - udelay(1); /* maintain the strobe during 1 us */ - byte >>= 1; - } -} - -/* turn the backlight on or off */ -static void lcd_backlight(struct charlcd *charlcd, int on) -{ - if (lcd.pins.bl == PIN_NONE) - return; - - /* The backlight is activated by setting the AUTOFEED line to +5V */ - spin_lock_irq(&pprt_lock); - if (on) - set_bit(LCD_BIT_BL, bits); - else - clear_bit(LCD_BIT_BL, bits); - panel_set_bits(); - spin_unlock_irq(&pprt_lock); -} - -/* send a command to the LCD panel in serial mode */ -static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd) -{ - spin_lock_irq(&pprt_lock); - lcd_send_serial(0x1F); /* R/W=W, RS=0 */ - lcd_send_serial(cmd & 0x0F); - lcd_send_serial((cmd >> 4) & 0x0F); - udelay(40); /* the shortest command takes at least 40 us */ - spin_unlock_irq(&pprt_lock); -} - -/* send data to the LCD panel in serial mode */ -static void lcd_write_data_s(struct charlcd *charlcd, int data) -{ - spin_lock_irq(&pprt_lock); - lcd_send_serial(0x5F); /* R/W=W, RS=1 */ - lcd_send_serial(data & 0x0F); - lcd_send_serial((data >> 4) & 0x0F); - udelay(40); /* the shortest data takes at least 40 us */ - spin_unlock_irq(&pprt_lock); -} - -/* send a command to the LCD panel in 8 bits parallel mode */ -static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd) -{ - spin_lock_irq(&pprt_lock); - /* present the data to the data port */ - w_dtr(pprt, cmd); - udelay(20); /* maintain the data during 20 us before the strobe */ - - set_bit(LCD_BIT_E, bits); - clear_bit(LCD_BIT_RS, bits); - clear_bit(LCD_BIT_RW, bits); - set_ctrl_bits(); - - udelay(40); /* maintain the strobe during 40 us */ - - clear_bit(LCD_BIT_E, bits); - set_ctrl_bits(); - - udelay(120); /* the shortest command takes at least 120 us */ - spin_unlock_irq(&pprt_lock); -} - -/* send data to the LCD panel in 8 bits parallel mode */ -static void lcd_write_data_p8(struct charlcd *charlcd, int data) -{ - spin_lock_irq(&pprt_lock); - /* present the data to the data port */ - w_dtr(pprt, data); - udelay(20); /* maintain the data during 20 us before the strobe */ - - set_bit(LCD_BIT_E, bits); - set_bit(LCD_BIT_RS, bits); - clear_bit(LCD_BIT_RW, bits); - set_ctrl_bits(); - - udelay(40); /* maintain the strobe during 40 us */ - - clear_bit(LCD_BIT_E, bits); - set_ctrl_bits(); - - udelay(45); /* the shortest data takes at least 45 us */ - spin_unlock_irq(&pprt_lock); -} - -/* send a command to the TI LCD panel */ -static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd) -{ - spin_lock_irq(&pprt_lock); - /* present the data to the control port */ - w_ctr(pprt, cmd); - udelay(60); - spin_unlock_irq(&pprt_lock); -} - -/* send data to the TI LCD panel */ -static void lcd_write_data_tilcd(struct charlcd *charlcd, int data) -{ - spin_lock_irq(&pprt_lock); - /* present the data to the data port */ - w_dtr(pprt, data); - udelay(60); - spin_unlock_irq(&pprt_lock); -} - -/* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_s(struct charlcd *charlcd) -{ - int pos; - - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { - lcd_send_serial(0x5F); /* R/W=W, RS=1 */ - lcd_send_serial(' ' & 0x0F); - lcd_send_serial((' ' >> 4) & 0x0F); - /* the shortest data takes at least 40 us */ - udelay(40); - } - spin_unlock_irq(&pprt_lock); -} - -/* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_p8(struct charlcd *charlcd) -{ - int pos; - - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { - /* present the data to the data port */ - w_dtr(pprt, ' '); - - /* maintain the data during 20 us before the strobe */ - udelay(20); - - set_bit(LCD_BIT_E, bits); - set_bit(LCD_BIT_RS, bits); - clear_bit(LCD_BIT_RW, bits); - set_ctrl_bits(); - - /* maintain the strobe during 40 us */ - udelay(40); - - clear_bit(LCD_BIT_E, bits); - set_ctrl_bits(); - - /* the shortest data takes at least 45 us */ - udelay(45); - } - spin_unlock_irq(&pprt_lock); -} - -/* fills the display with spaces and resets X/Y */ -static void lcd_clear_fast_tilcd(struct charlcd *charlcd) -{ - int pos; - - spin_lock_irq(&pprt_lock); - for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) { - /* present the data to the data port */ - w_dtr(pprt, ' '); - udelay(60); - } - - spin_unlock_irq(&pprt_lock); -} - -static struct charlcd_ops charlcd_serial_ops = { - .write_cmd = lcd_write_cmd_s, - .write_data = lcd_write_data_s, - .clear_fast = lcd_clear_fast_s, - .backlight = lcd_backlight, -}; - -static struct charlcd_ops charlcd_parallel_ops = { - .write_cmd = lcd_write_cmd_p8, - .write_data = lcd_write_data_p8, - .clear_fast = lcd_clear_fast_p8, - .backlight = lcd_backlight, -}; - -static struct charlcd_ops charlcd_tilcd_ops = { - .write_cmd = lcd_write_cmd_tilcd, - .write_data = lcd_write_data_tilcd, - .clear_fast = lcd_clear_fast_tilcd, - .backlight = lcd_backlight, -}; - -/* initialize the LCD driver */ -static void lcd_init(void) -{ - struct charlcd *charlcd; - - charlcd = charlcd_alloc(0); - if (!charlcd) - return; - - /* - * Init lcd struct with load-time values to preserve exact - * current functionality (at least for now). - */ - charlcd->height = lcd_height; - charlcd->width = lcd_width; - charlcd->bwidth = lcd_bwidth; - charlcd->hwidth = lcd_hwidth; - - switch (selected_lcd_type) { - case LCD_TYPE_OLD: - /* parallel mode, 8 bits */ - lcd.proto = LCD_PROTO_PARALLEL; - lcd.charset = LCD_CHARSET_NORMAL; - lcd.pins.e = PIN_STROBE; - lcd.pins.rs = PIN_AUTOLF; - - charlcd->width = 40; - charlcd->bwidth = 40; - charlcd->hwidth = 64; - charlcd->height = 2; - break; - case LCD_TYPE_KS0074: - /* serial mode, ks0074 */ - lcd.proto = LCD_PROTO_SERIAL; - lcd.charset = LCD_CHARSET_KS0074; - lcd.pins.bl = PIN_AUTOLF; - lcd.pins.cl = PIN_STROBE; - lcd.pins.da = PIN_D0; - - charlcd->width = 16; - charlcd->bwidth = 40; - charlcd->hwidth = 16; - charlcd->height = 2; - break; - case LCD_TYPE_NEXCOM: - /* parallel mode, 8 bits, generic */ - lcd.proto = LCD_PROTO_PARALLEL; - lcd.charset = LCD_CHARSET_NORMAL; - lcd.pins.e = PIN_AUTOLF; - lcd.pins.rs = PIN_SELECP; - lcd.pins.rw = PIN_INITP; - - charlcd->width = 16; - charlcd->bwidth = 40; - charlcd->hwidth = 64; - charlcd->height = 2; - break; - case LCD_TYPE_CUSTOM: - /* customer-defined */ - lcd.proto = DEFAULT_LCD_PROTO; - lcd.charset = DEFAULT_LCD_CHARSET; - /* default geometry will be set later */ - break; - case LCD_TYPE_HANTRONIX: - /* parallel mode, 8 bits, hantronix-like */ - default: - lcd.proto = LCD_PROTO_PARALLEL; - lcd.charset = LCD_CHARSET_NORMAL; - lcd.pins.e = PIN_STROBE; - lcd.pins.rs = PIN_SELECP; - - charlcd->width = 16; - charlcd->bwidth = 40; - charlcd->hwidth = 64; - charlcd->height = 2; - break; - } - - /* Overwrite with module params set on loading */ - if (lcd_height != NOT_SET) - charlcd->height = lcd_height; - if (lcd_width != NOT_SET) - charlcd->width = lcd_width; - if (lcd_bwidth != NOT_SET) - charlcd->bwidth = lcd_bwidth; - if (lcd_hwidth != NOT_SET) - charlcd->hwidth = lcd_hwidth; - if (lcd_charset != NOT_SET) - lcd.charset = lcd_charset; - if (lcd_proto != NOT_SET) - lcd.proto = lcd_proto; - if (lcd_e_pin != PIN_NOT_SET) - lcd.pins.e = lcd_e_pin; - if (lcd_rs_pin != PIN_NOT_SET) - lcd.pins.rs = lcd_rs_pin; - if (lcd_rw_pin != PIN_NOT_SET) - lcd.pins.rw = lcd_rw_pin; - if (lcd_cl_pin != PIN_NOT_SET) - lcd.pins.cl = lcd_cl_pin; - if (lcd_da_pin != PIN_NOT_SET) - lcd.pins.da = lcd_da_pin; - if (lcd_bl_pin != PIN_NOT_SET) - lcd.pins.bl = lcd_bl_pin; - - /* this is used to catch wrong and default values */ - if (charlcd->width <= 0) - charlcd->width = DEFAULT_LCD_WIDTH; - if (charlcd->bwidth <= 0) - charlcd->bwidth = DEFAULT_LCD_BWIDTH; - if (charlcd->hwidth <= 0) - charlcd->hwidth = DEFAULT_LCD_HWIDTH; - if (charlcd->height <= 0) - charlcd->height = DEFAULT_LCD_HEIGHT; - - if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */ - charlcd->ops = &charlcd_serial_ops; - - if (lcd.pins.cl == PIN_NOT_SET) - lcd.pins.cl = DEFAULT_LCD_PIN_SCL; - if (lcd.pins.da == PIN_NOT_SET) - lcd.pins.da = DEFAULT_LCD_PIN_SDA; - - } else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */ - charlcd->ops = &charlcd_parallel_ops; - - if (lcd.pins.e == PIN_NOT_SET) - lcd.pins.e = DEFAULT_LCD_PIN_E; - if (lcd.pins.rs == PIN_NOT_SET) - lcd.pins.rs = DEFAULT_LCD_PIN_RS; - if (lcd.pins.rw == PIN_NOT_SET) - lcd.pins.rw = DEFAULT_LCD_PIN_RW; - } else { - charlcd->ops = &charlcd_tilcd_ops; - } - - if (lcd.pins.bl == PIN_NOT_SET) - lcd.pins.bl = DEFAULT_LCD_PIN_BL; - - if (lcd.pins.e == PIN_NOT_SET) - lcd.pins.e = PIN_NONE; - if (lcd.pins.rs == PIN_NOT_SET) - lcd.pins.rs = PIN_NONE; - if (lcd.pins.rw == PIN_NOT_SET) - lcd.pins.rw = PIN_NONE; - if (lcd.pins.bl == PIN_NOT_SET) - lcd.pins.bl = PIN_NONE; - if (lcd.pins.cl == PIN_NOT_SET) - lcd.pins.cl = PIN_NONE; - if (lcd.pins.da == PIN_NOT_SET) - lcd.pins.da = PIN_NONE; - - if (lcd.charset == NOT_SET) - lcd.charset = DEFAULT_LCD_CHARSET; - - if (lcd.charset == LCD_CHARSET_KS0074) - charlcd->char_conv = lcd_char_conv_ks0074; - else - charlcd->char_conv = NULL; - - pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E], - lcd_bits[LCD_PORT_C][LCD_BIT_E]); - pin_to_bits(lcd.pins.rs, lcd_bits[LCD_PORT_D][LCD_BIT_RS], - lcd_bits[LCD_PORT_C][LCD_BIT_RS]); - pin_to_bits(lcd.pins.rw, lcd_bits[LCD_PORT_D][LCD_BIT_RW], - lcd_bits[LCD_PORT_C][LCD_BIT_RW]); - pin_to_bits(lcd.pins.bl, lcd_bits[LCD_PORT_D][LCD_BIT_BL], - lcd_bits[LCD_PORT_C][LCD_BIT_BL]); - pin_to_bits(lcd.pins.cl, lcd_bits[LCD_PORT_D][LCD_BIT_CL], - lcd_bits[LCD_PORT_C][LCD_BIT_CL]); - pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA], - lcd_bits[LCD_PORT_C][LCD_BIT_DA]); - - lcd.charlcd = charlcd; - lcd.initialized = true; -} - -/* - * These are the file operation function for user access to /dev/keypad - */ - -static ssize_t keypad_read(struct file *file, - char __user *buf, size_t count, loff_t *ppos) -{ - unsigned i = *ppos; - char __user *tmp = buf; - - if (keypad_buflen == 0) { - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; - - if (wait_event_interruptible(keypad_read_wait, - keypad_buflen != 0)) - return -EINTR; - } - - for (; count-- > 0 && (keypad_buflen > 0); - ++i, ++tmp, --keypad_buflen) { - put_user(keypad_buffer[keypad_start], tmp); - keypad_start = (keypad_start + 1) % KEYPAD_BUFFER; - } - *ppos = i; - - return tmp - buf; -} - -static int keypad_open(struct inode *inode, struct file *file) -{ - if (!atomic_dec_and_test(&keypad_available)) - return -EBUSY; /* open only once at a time */ - - if (file->f_mode & FMODE_WRITE) /* device is read-only */ - return -EPERM; - - keypad_buflen = 0; /* flush the buffer on opening */ - return 0; -} - -static int keypad_release(struct inode *inode, struct file *file) -{ - atomic_inc(&keypad_available); - return 0; -} - -static const struct file_operations keypad_fops = { - .read = keypad_read, /* read */ - .open = keypad_open, /* open */ - .release = keypad_release, /* close */ - .llseek = default_llseek, -}; - -static struct miscdevice keypad_dev = { - .minor = KEYPAD_MINOR, - .name = "keypad", - .fops = &keypad_fops, -}; - -static void keypad_send_key(const char *string, int max_len) -{ - /* send the key to the device only if a process is attached to it. */ - if (!atomic_read(&keypad_available)) { - while (max_len-- && keypad_buflen < KEYPAD_BUFFER && *string) { - keypad_buffer[(keypad_start + keypad_buflen++) % - KEYPAD_BUFFER] = *string++; - } - wake_up_interruptible(&keypad_read_wait); - } -} - -/* this function scans all the bits involving at least one logical signal, - * and puts the results in the bitfield "phys_read" (one bit per established - * contact), and sets "phys_read_prev" to "phys_read". - * - * Note: to debounce input signals, we will only consider as switched a signal - * which is stable across 2 measures. Signals which are different between two - * reads will be kept as they previously were in their logical form (phys_prev). - * A signal which has just switched will have a 1 in - * (phys_read ^ phys_read_prev). - */ -static void phys_scan_contacts(void) -{ - int bit, bitval; - char oldval; - char bitmask; - char gndmask; - - phys_prev = phys_curr; - phys_read_prev = phys_read; - phys_read = 0; /* flush all signals */ - - /* keep track of old value, with all outputs disabled */ - oldval = r_dtr(pprt) | scan_mask_o; - /* activate all keyboard outputs (active low) */ - w_dtr(pprt, oldval & ~scan_mask_o); - - /* will have a 1 for each bit set to gnd */ - bitmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; - /* disable all matrix signals */ - w_dtr(pprt, oldval); - - /* now that all outputs are cleared, the only active input bits are - * directly connected to the ground - */ - - /* 1 for each grounded input */ - gndmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; - - /* grounded inputs are signals 40-44 */ - phys_read |= (__u64)gndmask << 40; - - if (bitmask != gndmask) { - /* - * since clearing the outputs changed some inputs, we know - * that some input signals are currently tied to some outputs. - * So we'll scan them. - */ - for (bit = 0; bit < 8; bit++) { - bitval = BIT(bit); - - if (!(scan_mask_o & bitval)) /* skip unused bits */ - continue; - - w_dtr(pprt, oldval & ~bitval); /* enable this output */ - bitmask = PNL_PINPUT(r_str(pprt)) & ~gndmask; - phys_read |= (__u64)bitmask << (5 * bit); - } - w_dtr(pprt, oldval); /* disable all outputs */ - } - /* - * this is easy: use old bits when they are flapping, - * use new ones when stable - */ - phys_curr = (phys_prev & (phys_read ^ phys_read_prev)) | - (phys_read & ~(phys_read ^ phys_read_prev)); -} - -static inline int input_state_high(struct logical_input *input) -{ -#if 0 - /* FIXME: - * this is an invalid test. It tries to catch - * transitions from single-key to multiple-key, but - * doesn't take into account the contacts polarity. - * The only solution to the problem is to parse keys - * from the most complex to the simplest combinations, - * and mark them as 'caught' once a combination - * matches, then unmatch it for all other ones. - */ - - /* try to catch dangerous transitions cases : - * someone adds a bit, so this signal was a false - * positive resulting from a transition. We should - * invalidate the signal immediately and not call the - * release function. - * eg: 0 -(press A)-> A -(press B)-> AB : don't match A's release. - */ - if (((phys_prev & input->mask) == input->value) && - ((phys_curr & input->mask) > input->value)) { - input->state = INPUT_ST_LOW; /* invalidate */ - return 1; - } -#endif - - if ((phys_curr & input->mask) == input->value) { - if ((input->type == INPUT_TYPE_STD) && - (input->high_timer == 0)) { - input->high_timer++; - if (input->u.std.press_fct) - input->u.std.press_fct(input->u.std.press_data); - } else if (input->type == INPUT_TYPE_KBD) { - /* will turn on the light */ - keypressed = 1; - - if (input->high_timer == 0) { - char *press_str = input->u.kbd.press_str; - - if (press_str[0]) { - int s = sizeof(input->u.kbd.press_str); - - keypad_send_key(press_str, s); - } - } - - if (input->u.kbd.repeat_str[0]) { - char *repeat_str = input->u.kbd.repeat_str; - - if (input->high_timer >= KEYPAD_REP_START) { - int s = sizeof(input->u.kbd.repeat_str); - - input->high_timer -= KEYPAD_REP_DELAY; - keypad_send_key(repeat_str, s); - } - /* we will need to come back here soon */ - inputs_stable = 0; - } - - if (input->high_timer < 255) - input->high_timer++; - } - return 1; - } - - /* else signal falling down. Let's fall through. */ - input->state = INPUT_ST_FALLING; - input->fall_timer = 0; - - return 0; -} - -static inline void input_state_falling(struct logical_input *input) -{ -#if 0 - /* FIXME !!! same comment as in input_state_high */ - if (((phys_prev & input->mask) == input->value) && - ((phys_curr & input->mask) > input->value)) { - input->state = INPUT_ST_LOW; /* invalidate */ - return; - } -#endif - - if ((phys_curr & input->mask) == input->value) { - if (input->type == INPUT_TYPE_KBD) { - /* will turn on the light */ - keypressed = 1; - - if (input->u.kbd.repeat_str[0]) { - char *repeat_str = input->u.kbd.repeat_str; - - if (input->high_timer >= KEYPAD_REP_START) { - int s = sizeof(input->u.kbd.repeat_str); - - input->high_timer -= KEYPAD_REP_DELAY; - keypad_send_key(repeat_str, s); - } - /* we will need to come back here soon */ - inputs_stable = 0; - } - - if (input->high_timer < 255) - input->high_timer++; - } - input->state = INPUT_ST_HIGH; - } else if (input->fall_timer >= input->fall_time) { - /* call release event */ - if (input->type == INPUT_TYPE_STD) { - void (*release_fct)(int) = input->u.std.release_fct; - - if (release_fct) - release_fct(input->u.std.release_data); - } else if (input->type == INPUT_TYPE_KBD) { - char *release_str = input->u.kbd.release_str; - - if (release_str[0]) { - int s = sizeof(input->u.kbd.release_str); - - keypad_send_key(release_str, s); - } - } - - input->state = INPUT_ST_LOW; - } else { - input->fall_timer++; - inputs_stable = 0; - } -} - -static void panel_process_inputs(void) -{ - struct list_head *item; - struct logical_input *input; - - keypressed = 0; - inputs_stable = 1; - list_for_each(item, &logical_inputs) { - input = list_entry(item, struct logical_input, list); - - switch (input->state) { - case INPUT_ST_LOW: - if ((phys_curr & input->mask) != input->value) - break; - /* if all needed ones were already set previously, - * this means that this logical signal has been - * activated by the releasing of another combined - * signal, so we don't want to match. - * eg: AB -(release B)-> A -(release A)-> 0 : - * don't match A. - */ - if ((phys_prev & input->mask) == input->value) - break; - input->rise_timer = 0; - input->state = INPUT_ST_RISING; - /* no break here, fall through */ - case INPUT_ST_RISING: - if ((phys_curr & input->mask) != input->value) { - input->state = INPUT_ST_LOW; - break; - } - if (input->rise_timer < input->rise_time) { - inputs_stable = 0; - input->rise_timer++; - break; - } - input->high_timer = 0; - input->state = INPUT_ST_HIGH; - /* no break here, fall through */ - case INPUT_ST_HIGH: - if (input_state_high(input)) - break; - /* no break here, fall through */ - case INPUT_ST_FALLING: - input_state_falling(input); - } - } -} - -static void panel_scan_timer(void) -{ - if (keypad.enabled && keypad_initialized) { - if (spin_trylock_irq(&pprt_lock)) { - phys_scan_contacts(); - - /* no need for the parport anymore */ - spin_unlock_irq(&pprt_lock); - } - - if (!inputs_stable || phys_curr != phys_prev) - panel_process_inputs(); - } - - if (keypressed && lcd.enabled && lcd.initialized) - charlcd_poke(lcd.charlcd); - - mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); -} - -static void init_scan_timer(void) -{ - if (scan_timer.function) - return; /* already started */ - - setup_timer(&scan_timer, (void *)&panel_scan_timer, 0); - scan_timer.expires = jiffies + INPUT_POLL_TIME; - add_timer(&scan_timer); -} - -/* converts a name of the form "({BbAaPpSsEe}{01234567-})*" to a series of bits. - * if or are non-null, they will be or'ed with the bits - * corresponding to out and in bits respectively. - * returns 1 if ok, 0 if error (in which case, nothing is written). - */ -static u8 input_name2mask(const char *name, __u64 *mask, __u64 *value, - u8 *imask, u8 *omask) -{ - const char sigtab[] = "EeSsPpAaBb"; - u8 im, om; - __u64 m, v; - - om = 0; - im = 0; - m = 0ULL; - v = 0ULL; - while (*name) { - int in, out, bit, neg; - const char *idx; - - idx = strchr(sigtab, *name); - if (!idx) - return 0; /* input name not found */ - - in = idx - sigtab; - neg = (in & 1); /* odd (lower) names are negated */ - in >>= 1; - im |= BIT(in); - - name++; - if (*name >= '0' && *name <= '7') { - out = *name - '0'; - om |= BIT(out); - } else if (*name == '-') { - out = 8; - } else { - return 0; /* unknown bit name */ - } - - bit = (out * 5) + in; - - m |= 1ULL << bit; - if (!neg) - v |= 1ULL << bit; - name++; - } - *mask = m; - *value = v; - if (imask) - *imask |= im; - if (omask) - *omask |= om; - return 1; -} - -/* tries to bind a key to the signal name . The key will send the - * strings , , for these respective events. - * Returns the pointer to the new key if ok, NULL if the key could not be bound. - */ -static struct logical_input *panel_bind_key(const char *name, const char *press, - const char *repeat, - const char *release) -{ - struct logical_input *key; - - key = kzalloc(sizeof(*key), GFP_KERNEL); - if (!key) - return NULL; - - if (!input_name2mask(name, &key->mask, &key->value, &scan_mask_i, - &scan_mask_o)) { - kfree(key); - return NULL; - } - - key->type = INPUT_TYPE_KBD; - key->state = INPUT_ST_LOW; - key->rise_time = 1; - key->fall_time = 1; - - strncpy(key->u.kbd.press_str, press, sizeof(key->u.kbd.press_str)); - strncpy(key->u.kbd.repeat_str, repeat, sizeof(key->u.kbd.repeat_str)); - strncpy(key->u.kbd.release_str, release, - sizeof(key->u.kbd.release_str)); - list_add(&key->list, &logical_inputs); - return key; -} - -#if 0 -/* tries to bind a callback function to the signal name . The function - * will be called with the arg when the signal is - * activated, and so on for / - * Returns the pointer to the new signal if ok, NULL if the signal could not - * be bound. - */ -static struct logical_input *panel_bind_callback(char *name, - void (*press_fct)(int), - int press_data, - void (*release_fct)(int), - int release_data) -{ - struct logical_input *callback; - - callback = kmalloc(sizeof(*callback), GFP_KERNEL); - if (!callback) - return NULL; - - memset(callback, 0, sizeof(struct logical_input)); - if (!input_name2mask(name, &callback->mask, &callback->value, - &scan_mask_i, &scan_mask_o)) - return NULL; - - callback->type = INPUT_TYPE_STD; - callback->state = INPUT_ST_LOW; - callback->rise_time = 1; - callback->fall_time = 1; - callback->u.std.press_fct = press_fct; - callback->u.std.press_data = press_data; - callback->u.std.release_fct = release_fct; - callback->u.std.release_data = release_data; - list_add(&callback->list, &logical_inputs); - return callback; -} -#endif - -static void keypad_init(void) -{ - int keynum; - - init_waitqueue_head(&keypad_read_wait); - keypad_buflen = 0; /* flushes any eventual noisy keystroke */ - - /* Let's create all known keys */ - - for (keynum = 0; keypad_profile[keynum][0][0]; keynum++) { - panel_bind_key(keypad_profile[keynum][0], - keypad_profile[keynum][1], - keypad_profile[keynum][2], - keypad_profile[keynum][3]); - } - - init_scan_timer(); - keypad_initialized = 1; -} - -/**************************************************/ -/* device initialization */ -/**************************************************/ - -static void panel_attach(struct parport *port) -{ - struct pardev_cb panel_cb; - - if (port->number != parport) - return; - - if (pprt) { - pr_err("%s: port->number=%d parport=%d, already registered!\n", - __func__, port->number, parport); - return; - } - - memset(&panel_cb, 0, sizeof(panel_cb)); - panel_cb.private = &pprt; - /* panel_cb.flags = 0 should be PARPORT_DEV_EXCL? */ - - pprt = parport_register_dev_model(port, "panel", &panel_cb, 0); - if (!pprt) { - pr_err("%s: port->number=%d parport=%d, parport_register_device() failed\n", - __func__, port->number, parport); - return; - } - - if (parport_claim(pprt)) { - pr_err("could not claim access to parport%d. Aborting.\n", - parport); - goto err_unreg_device; - } - - /* must init LCD first, just in case an IRQ from the keypad is - * generated at keypad init - */ - if (lcd.enabled) { - lcd_init(); - if (!lcd.charlcd || charlcd_register(lcd.charlcd)) - goto err_unreg_device; - } - - if (keypad.enabled) { - keypad_init(); - if (misc_register(&keypad_dev)) - goto err_lcd_unreg; - } - return; - -err_lcd_unreg: - if (lcd.enabled) - charlcd_unregister(lcd.charlcd); -err_unreg_device: - kfree(lcd.charlcd); - lcd.charlcd = NULL; - parport_unregister_device(pprt); - pprt = NULL; -} - -static void panel_detach(struct parport *port) -{ - if (port->number != parport) - return; - - if (!pprt) { - pr_err("%s: port->number=%d parport=%d, nothing to unregister.\n", - __func__, port->number, parport); - return; - } - if (scan_timer.function) - del_timer_sync(&scan_timer); - - if (keypad.enabled) { - misc_deregister(&keypad_dev); - keypad_initialized = 0; - } - - if (lcd.enabled) { - charlcd_unregister(lcd.charlcd); - lcd.initialized = false; - kfree(lcd.charlcd); - lcd.charlcd = NULL; - } - - /* TODO: free all input signals */ - parport_release(pprt); - parport_unregister_device(pprt); - pprt = NULL; -} - -static struct parport_driver panel_driver = { - .name = "panel", - .match_port = panel_attach, - .detach = panel_detach, - .devmodel = true, -}; - -/* init function */ -static int __init panel_init_module(void) -{ - int selected_keypad_type = NOT_SET, err; - - /* take care of an eventual profile */ - switch (profile) { - case PANEL_PROFILE_CUSTOM: - /* custom profile */ - selected_keypad_type = DEFAULT_KEYPAD_TYPE; - selected_lcd_type = DEFAULT_LCD_TYPE; - break; - case PANEL_PROFILE_OLD: - /* 8 bits, 2*16, old keypad */ - selected_keypad_type = KEYPAD_TYPE_OLD; - selected_lcd_type = LCD_TYPE_OLD; - - /* TODO: This two are a little hacky, sort it out later */ - if (lcd_width == NOT_SET) - lcd_width = 16; - if (lcd_hwidth == NOT_SET) - lcd_hwidth = 16; - break; - case PANEL_PROFILE_NEW: - /* serial, 2*16, new keypad */ - selected_keypad_type = KEYPAD_TYPE_NEW; - selected_lcd_type = LCD_TYPE_KS0074; - break; - case PANEL_PROFILE_HANTRONIX: - /* 8 bits, 2*16 hantronix-like, no keypad */ - selected_keypad_type = KEYPAD_TYPE_NONE; - selected_lcd_type = LCD_TYPE_HANTRONIX; - break; - case PANEL_PROFILE_NEXCOM: - /* generic 8 bits, 2*16, nexcom keypad, eg. Nexcom. */ - selected_keypad_type = KEYPAD_TYPE_NEXCOM; - selected_lcd_type = LCD_TYPE_NEXCOM; - break; - case PANEL_PROFILE_LARGE: - /* 8 bits, 2*40, old keypad */ - selected_keypad_type = KEYPAD_TYPE_OLD; - selected_lcd_type = LCD_TYPE_OLD; - break; - } - - /* - * Overwrite selection with module param values (both keypad and lcd), - * where the deprecated params have lower prio. - */ - if (keypad_enabled != NOT_SET) - selected_keypad_type = keypad_enabled; - if (keypad_type != NOT_SET) - selected_keypad_type = keypad_type; - - keypad.enabled = (selected_keypad_type > 0); - - if (lcd_enabled != NOT_SET) - selected_lcd_type = lcd_enabled; - if (lcd_type != NOT_SET) - selected_lcd_type = lcd_type; - - lcd.enabled = (selected_lcd_type > 0); - - if (lcd.enabled) { - /* - * Init lcd struct with load-time values to preserve exact - * current functionality (at least for now). - */ - lcd.charset = lcd_charset; - lcd.proto = lcd_proto; - lcd.pins.e = lcd_e_pin; - lcd.pins.rs = lcd_rs_pin; - lcd.pins.rw = lcd_rw_pin; - lcd.pins.cl = lcd_cl_pin; - lcd.pins.da = lcd_da_pin; - lcd.pins.bl = lcd_bl_pin; - } - - switch (selected_keypad_type) { - case KEYPAD_TYPE_OLD: - keypad_profile = old_keypad_profile; - break; - case KEYPAD_TYPE_NEW: - keypad_profile = new_keypad_profile; - break; - case KEYPAD_TYPE_NEXCOM: - keypad_profile = nexcom_keypad_profile; - break; - default: - keypad_profile = NULL; - break; - } - - if (!lcd.enabled && !keypad.enabled) { - /* no device enabled, let's exit */ - pr_err("panel driver disabled.\n"); - return -ENODEV; - } - - err = parport_register_driver(&panel_driver); - if (err) { - pr_err("could not register with parport. Aborting.\n"); - return err; - } - - if (pprt) - pr_info("panel driver registered on parport%d (io=0x%lx).\n", - parport, pprt->port->base); - else - pr_info("panel driver not yet registered\n"); - return 0; -} - -static void __exit panel_cleanup_module(void) -{ - parport_unregister_driver(&panel_driver); -} - -module_init(panel_init_module); -module_exit(panel_cleanup_module); -MODULE_AUTHOR("Willy Tarreau"); -MODULE_LICENSE("GPL"); - -/* - * Local variables: - * c-indent-level: 4 - * tab-width: 8 - * End: - */ -- cgit v1.2.3-70-g09d2 From 00846a4425d373761c70cbe82cceb98d3d4da179 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 4 Apr 2017 20:25:22 +0300 Subject: auxdisplay: Move arm-charlcd.c to drivers/auxdisplay folder It looks like arm-charlcd.c belongs to auxdisplay subsystem. Move it to drivers/auxdisplay folder. No functional changes intended. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman --- drivers/auxdisplay/Kconfig | 10 ++ drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/arm-charlcd.c | 368 +++++++++++++++++++++++++++++++++++++++ drivers/misc/Kconfig | 10 -- drivers/misc/Makefile | 1 - drivers/misc/arm-charlcd.c | 368 --------------------------------------- 6 files changed, 379 insertions(+), 379 deletions(-) create mode 100644 drivers/auxdisplay/arm-charlcd.c delete mode 100644 drivers/misc/arm-charlcd.c diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index ad83a0598c67..9ae6681c90ad 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -157,6 +157,16 @@ config HT16K33 endif # AUXDISPLAY +config ARM_CHARLCD + bool "ARM Ltd. Character LCD Driver" + depends on PLAT_VERSATILE + help + This is a driver for the character LCD found on the ARM Ltd. + Versatile and RealView Platform Baseboards. It doesn't do + very much more than display the text "ARM Linux" on the first + line and the Linux version on the second line, but that's + still useful. + config PANEL tristate "Parallel port LCD/Keypad Panel support" depends on PARPORT diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index 58c25e5ece84..2b8af3dc5e42 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_CHARLCD) += charlcd.o +obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_KS0108) += ks0108.o obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o diff --git a/drivers/auxdisplay/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c new file mode 100644 index 000000000000..b3176ee92b90 --- /dev/null +++ b/drivers/auxdisplay/arm-charlcd.c @@ -0,0 +1,368 @@ +/* + * Driver for the on-board character LCD found on some ARM reference boards + * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it + * http://en.wikipedia.org/wiki/HD44780_Character_LCD + * Currently it will just display the text "ARM Linux" and the linux version + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Linus Walleij + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVERNAME "arm-charlcd" +#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000)) + +/* Offsets to registers */ +#define CHAR_COM 0x00U +#define CHAR_DAT 0x04U +#define CHAR_RD 0x08U +#define CHAR_RAW 0x0CU +#define CHAR_MASK 0x10U +#define CHAR_STAT 0x14U + +#define CHAR_RAW_CLEAR 0x00000000U +#define CHAR_RAW_VALID 0x00000100U + +/* Hitachi HD44780 display commands */ +#define HD_CLEAR 0x01U +#define HD_HOME 0x02U +#define HD_ENTRYMODE 0x04U +#define HD_ENTRYMODE_INCREMENT 0x02U +#define HD_ENTRYMODE_SHIFT 0x01U +#define HD_DISPCTRL 0x08U +#define HD_DISPCTRL_ON 0x04U +#define HD_DISPCTRL_CURSOR_ON 0x02U +#define HD_DISPCTRL_CURSOR_BLINK 0x01U +#define HD_CRSR_SHIFT 0x10U +#define HD_CRSR_SHIFT_DISPLAY 0x08U +#define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U +#define HD_FUNCSET 0x20U +#define HD_FUNCSET_8BIT 0x10U +#define HD_FUNCSET_2_LINES 0x08U +#define HD_FUNCSET_FONT_5X10 0x04U +#define HD_SET_CGRAM 0x40U +#define HD_SET_DDRAM 0x80U +#define HD_BUSY_FLAG 0x80U + +/** + * @dev: a pointer back to containing device + * @phybase: the offset to the controller in physical memory + * @physize: the size of the physical page + * @virtbase: the offset to the controller in virtual memory + * @irq: reserved interrupt number + * @complete: completion structure for the last LCD command + */ +struct charlcd { + struct device *dev; + u32 phybase; + u32 physize; + void __iomem *virtbase; + int irq; + struct completion complete; + struct delayed_work init_work; +}; + +static irqreturn_t charlcd_interrupt(int irq, void *data) +{ + struct charlcd *lcd = data; + u8 status; + + status = readl(lcd->virtbase + CHAR_STAT) & 0x01; + /* Clear IRQ */ + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + if (status) + complete(&lcd->complete); + else + dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status); + return IRQ_HANDLED; +} + + +static void charlcd_wait_complete_irq(struct charlcd *lcd) +{ + int ret; + + ret = wait_for_completion_interruptible_timeout(&lcd->complete, + CHARLCD_TIMEOUT); + /* Disable IRQ after completion */ + writel(0x00, lcd->virtbase + CHAR_MASK); + + if (ret < 0) { + dev_err(lcd->dev, + "wait_for_completion_interruptible_timeout() " + "returned %d waiting for ready\n", ret); + return; + } + + if (ret == 0) { + dev_err(lcd->dev, "charlcd controller timed out " + "waiting for ready\n"); + return; + } +} + +static u8 charlcd_4bit_read_char(struct charlcd *lcd) +{ + u8 data; + u32 val; + int i; + + /* If we can, use an IRQ to wait for the data, else poll */ + if (lcd->irq >= 0) + charlcd_wait_complete_irq(lcd); + else { + i = 0; + val = 0; + while (!(val & CHAR_RAW_VALID) && i < 10) { + udelay(100); + val = readl(lcd->virtbase + CHAR_RAW); + i++; + } + + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + } + msleep(1); + + /* Read the 4 high bits of the data */ + data = readl(lcd->virtbase + CHAR_RD) & 0xf0; + + /* + * The second read for the low bits does not trigger an IRQ + * so in this case we have to poll for the 4 lower bits + */ + i = 0; + val = 0; + while (!(val & CHAR_RAW_VALID) && i < 10) { + udelay(100); + val = readl(lcd->virtbase + CHAR_RAW); + i++; + } + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + msleep(1); + + /* Read the 4 low bits of the data */ + data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f; + + return data; +} + +static bool charlcd_4bit_read_bf(struct charlcd *lcd) +{ + if (lcd->irq >= 0) { + /* + * If we'll use IRQs to wait for the busyflag, clear any + * pending flag and enable IRQ + */ + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + init_completion(&lcd->complete); + writel(0x01, lcd->virtbase + CHAR_MASK); + } + readl(lcd->virtbase + CHAR_COM); + return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false; +} + +static void charlcd_4bit_wait_busy(struct charlcd *lcd) +{ + int retries = 50; + + udelay(100); + while (charlcd_4bit_read_bf(lcd) && retries) + retries--; + if (!retries) + dev_err(lcd->dev, "timeout waiting for busyflag\n"); +} + +static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd) +{ + u32 cmdlo = (cmd << 4) & 0xf0; + u32 cmdhi = (cmd & 0xf0); + + writel(cmdhi, lcd->virtbase + CHAR_COM); + udelay(10); + writel(cmdlo, lcd->virtbase + CHAR_COM); + charlcd_4bit_wait_busy(lcd); +} + +static void charlcd_4bit_char(struct charlcd *lcd, u8 ch) +{ + u32 chlo = (ch << 4) & 0xf0; + u32 chhi = (ch & 0xf0); + + writel(chhi, lcd->virtbase + CHAR_DAT); + udelay(10); + writel(chlo, lcd->virtbase + CHAR_DAT); + charlcd_4bit_wait_busy(lcd); +} + +static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str) +{ + u8 offset; + int i; + + /* + * We support line 0, 1 + * Line 1 runs from 0x00..0x27 + * Line 2 runs from 0x28..0x4f + */ + if (line == 0) + offset = 0; + else if (line == 1) + offset = 0x28; + else + return; + + /* Set offset */ + charlcd_4bit_command(lcd, HD_SET_DDRAM | offset); + + /* Send string */ + for (i = 0; i < strlen(str) && i < 0x28; i++) + charlcd_4bit_char(lcd, str[i]); +} + +static void charlcd_4bit_init(struct charlcd *lcd) +{ + /* These commands cannot be checked with the busy flag */ + writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); + msleep(5); + writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); + udelay(100); + writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); + udelay(100); + /* Go to 4bit mode */ + writel(HD_FUNCSET, lcd->virtbase + CHAR_COM); + udelay(100); + /* + * 4bit mode, 2 lines, 5x8 font, after this the number of lines + * and the font cannot be changed until the next initialization sequence + */ + charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES); + charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); + charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT); + charlcd_4bit_command(lcd, HD_CLEAR); + charlcd_4bit_command(lcd, HD_HOME); + /* Put something useful in the display */ + charlcd_4bit_print(lcd, 0, "ARM Linux"); + charlcd_4bit_print(lcd, 1, UTS_RELEASE); +} + +static void charlcd_init_work(struct work_struct *work) +{ + struct charlcd *lcd = + container_of(work, struct charlcd, init_work.work); + + charlcd_4bit_init(lcd); +} + +static int __init charlcd_probe(struct platform_device *pdev) +{ + int ret; + struct charlcd *lcd; + struct resource *res; + + lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + lcd->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENOENT; + goto out_no_resource; + } + lcd->phybase = res->start; + lcd->physize = resource_size(res); + + if (request_mem_region(lcd->phybase, lcd->physize, + DRIVERNAME) == NULL) { + ret = -EBUSY; + goto out_no_memregion; + } + + lcd->virtbase = ioremap(lcd->phybase, lcd->physize); + if (!lcd->virtbase) { + ret = -ENOMEM; + goto out_no_memregion; + } + + lcd->irq = platform_get_irq(pdev, 0); + /* If no IRQ is supplied, we'll survive without it */ + if (lcd->irq >= 0) { + if (request_irq(lcd->irq, charlcd_interrupt, 0, + DRIVERNAME, lcd)) { + ret = -EIO; + goto out_no_irq; + } + } + + platform_set_drvdata(pdev, lcd); + + /* + * Initialize the display in a delayed work, because + * it is VERY slow and would slow down the boot of the system. + */ + INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work); + schedule_delayed_work(&lcd->init_work, 0); + + dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n", + lcd->phybase); + + return 0; + +out_no_irq: + iounmap(lcd->virtbase); +out_no_memregion: + release_mem_region(lcd->phybase, SZ_4K); +out_no_resource: + kfree(lcd); + return ret; +} + +static int charlcd_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct charlcd *lcd = platform_get_drvdata(pdev); + + /* Power the display off */ + charlcd_4bit_command(lcd, HD_DISPCTRL); + return 0; +} + +static int charlcd_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct charlcd *lcd = platform_get_drvdata(pdev); + + /* Turn the display back on */ + charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); + return 0; +} + +static const struct dev_pm_ops charlcd_pm_ops = { + .suspend = charlcd_suspend, + .resume = charlcd_resume, +}; + +static const struct of_device_id charlcd_match[] = { + { .compatible = "arm,versatile-lcd", }, + {} +}; + +static struct platform_driver charlcd_driver = { + .driver = { + .name = DRIVERNAME, + .pm = &charlcd_pm_ops, + .suppress_bind_attrs = true, + .of_match_table = of_match_ptr(charlcd_match), + }, +}; +builtin_platform_driver_probe(charlcd_driver, charlcd_probe); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 2438466c15ac..39d1acb27452 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -419,16 +419,6 @@ config VMWARE_BALLOON To compile this driver as a module, choose M here: the module will be called vmw_balloon. -config ARM_CHARLCD - bool "ARM Ltd. Character LCD Driver" - depends on PLAT_VERSATILE - help - This is a driver for the character LCD found on the ARM Ltd. - Versatile and RealView Platform Baseboards. It doesn't do - very much more than display the text "ARM Linux" on the first - line and the Linux version on the second line, but that's - still useful. - config PCH_PHUB tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB" select GENERIC_NET_UTILS diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 9a9f7e72aa54..4c4d6dc03181 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -37,7 +37,6 @@ obj-y += eeprom/ obj-y += cb710/ obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o -obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ diff --git a/drivers/misc/arm-charlcd.c b/drivers/misc/arm-charlcd.c deleted file mode 100644 index b3176ee92b90..000000000000 --- a/drivers/misc/arm-charlcd.c +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Driver for the on-board character LCD found on some ARM reference boards - * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it - * http://en.wikipedia.org/wiki/HD44780_Character_LCD - * Currently it will just display the text "ARM Linux" and the linux version - * - * License terms: GNU General Public License (GPL) version 2 - * Author: Linus Walleij - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVERNAME "arm-charlcd" -#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000)) - -/* Offsets to registers */ -#define CHAR_COM 0x00U -#define CHAR_DAT 0x04U -#define CHAR_RD 0x08U -#define CHAR_RAW 0x0CU -#define CHAR_MASK 0x10U -#define CHAR_STAT 0x14U - -#define CHAR_RAW_CLEAR 0x00000000U -#define CHAR_RAW_VALID 0x00000100U - -/* Hitachi HD44780 display commands */ -#define HD_CLEAR 0x01U -#define HD_HOME 0x02U -#define HD_ENTRYMODE 0x04U -#define HD_ENTRYMODE_INCREMENT 0x02U -#define HD_ENTRYMODE_SHIFT 0x01U -#define HD_DISPCTRL 0x08U -#define HD_DISPCTRL_ON 0x04U -#define HD_DISPCTRL_CURSOR_ON 0x02U -#define HD_DISPCTRL_CURSOR_BLINK 0x01U -#define HD_CRSR_SHIFT 0x10U -#define HD_CRSR_SHIFT_DISPLAY 0x08U -#define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U -#define HD_FUNCSET 0x20U -#define HD_FUNCSET_8BIT 0x10U -#define HD_FUNCSET_2_LINES 0x08U -#define HD_FUNCSET_FONT_5X10 0x04U -#define HD_SET_CGRAM 0x40U -#define HD_SET_DDRAM 0x80U -#define HD_BUSY_FLAG 0x80U - -/** - * @dev: a pointer back to containing device - * @phybase: the offset to the controller in physical memory - * @physize: the size of the physical page - * @virtbase: the offset to the controller in virtual memory - * @irq: reserved interrupt number - * @complete: completion structure for the last LCD command - */ -struct charlcd { - struct device *dev; - u32 phybase; - u32 physize; - void __iomem *virtbase; - int irq; - struct completion complete; - struct delayed_work init_work; -}; - -static irqreturn_t charlcd_interrupt(int irq, void *data) -{ - struct charlcd *lcd = data; - u8 status; - - status = readl(lcd->virtbase + CHAR_STAT) & 0x01; - /* Clear IRQ */ - writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); - if (status) - complete(&lcd->complete); - else - dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status); - return IRQ_HANDLED; -} - - -static void charlcd_wait_complete_irq(struct charlcd *lcd) -{ - int ret; - - ret = wait_for_completion_interruptible_timeout(&lcd->complete, - CHARLCD_TIMEOUT); - /* Disable IRQ after completion */ - writel(0x00, lcd->virtbase + CHAR_MASK); - - if (ret < 0) { - dev_err(lcd->dev, - "wait_for_completion_interruptible_timeout() " - "returned %d waiting for ready\n", ret); - return; - } - - if (ret == 0) { - dev_err(lcd->dev, "charlcd controller timed out " - "waiting for ready\n"); - return; - } -} - -static u8 charlcd_4bit_read_char(struct charlcd *lcd) -{ - u8 data; - u32 val; - int i; - - /* If we can, use an IRQ to wait for the data, else poll */ - if (lcd->irq >= 0) - charlcd_wait_complete_irq(lcd); - else { - i = 0; - val = 0; - while (!(val & CHAR_RAW_VALID) && i < 10) { - udelay(100); - val = readl(lcd->virtbase + CHAR_RAW); - i++; - } - - writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); - } - msleep(1); - - /* Read the 4 high bits of the data */ - data = readl(lcd->virtbase + CHAR_RD) & 0xf0; - - /* - * The second read for the low bits does not trigger an IRQ - * so in this case we have to poll for the 4 lower bits - */ - i = 0; - val = 0; - while (!(val & CHAR_RAW_VALID) && i < 10) { - udelay(100); - val = readl(lcd->virtbase + CHAR_RAW); - i++; - } - writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); - msleep(1); - - /* Read the 4 low bits of the data */ - data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f; - - return data; -} - -static bool charlcd_4bit_read_bf(struct charlcd *lcd) -{ - if (lcd->irq >= 0) { - /* - * If we'll use IRQs to wait for the busyflag, clear any - * pending flag and enable IRQ - */ - writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); - init_completion(&lcd->complete); - writel(0x01, lcd->virtbase + CHAR_MASK); - } - readl(lcd->virtbase + CHAR_COM); - return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false; -} - -static void charlcd_4bit_wait_busy(struct charlcd *lcd) -{ - int retries = 50; - - udelay(100); - while (charlcd_4bit_read_bf(lcd) && retries) - retries--; - if (!retries) - dev_err(lcd->dev, "timeout waiting for busyflag\n"); -} - -static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd) -{ - u32 cmdlo = (cmd << 4) & 0xf0; - u32 cmdhi = (cmd & 0xf0); - - writel(cmdhi, lcd->virtbase + CHAR_COM); - udelay(10); - writel(cmdlo, lcd->virtbase + CHAR_COM); - charlcd_4bit_wait_busy(lcd); -} - -static void charlcd_4bit_char(struct charlcd *lcd, u8 ch) -{ - u32 chlo = (ch << 4) & 0xf0; - u32 chhi = (ch & 0xf0); - - writel(chhi, lcd->virtbase + CHAR_DAT); - udelay(10); - writel(chlo, lcd->virtbase + CHAR_DAT); - charlcd_4bit_wait_busy(lcd); -} - -static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str) -{ - u8 offset; - int i; - - /* - * We support line 0, 1 - * Line 1 runs from 0x00..0x27 - * Line 2 runs from 0x28..0x4f - */ - if (line == 0) - offset = 0; - else if (line == 1) - offset = 0x28; - else - return; - - /* Set offset */ - charlcd_4bit_command(lcd, HD_SET_DDRAM | offset); - - /* Send string */ - for (i = 0; i < strlen(str) && i < 0x28; i++) - charlcd_4bit_char(lcd, str[i]); -} - -static void charlcd_4bit_init(struct charlcd *lcd) -{ - /* These commands cannot be checked with the busy flag */ - writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); - msleep(5); - writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); - udelay(100); - writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); - udelay(100); - /* Go to 4bit mode */ - writel(HD_FUNCSET, lcd->virtbase + CHAR_COM); - udelay(100); - /* - * 4bit mode, 2 lines, 5x8 font, after this the number of lines - * and the font cannot be changed until the next initialization sequence - */ - charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES); - charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); - charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT); - charlcd_4bit_command(lcd, HD_CLEAR); - charlcd_4bit_command(lcd, HD_HOME); - /* Put something useful in the display */ - charlcd_4bit_print(lcd, 0, "ARM Linux"); - charlcd_4bit_print(lcd, 1, UTS_RELEASE); -} - -static void charlcd_init_work(struct work_struct *work) -{ - struct charlcd *lcd = - container_of(work, struct charlcd, init_work.work); - - charlcd_4bit_init(lcd); -} - -static int __init charlcd_probe(struct platform_device *pdev) -{ - int ret; - struct charlcd *lcd; - struct resource *res; - - lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL); - if (!lcd) - return -ENOMEM; - - lcd->dev = &pdev->dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENOENT; - goto out_no_resource; - } - lcd->phybase = res->start; - lcd->physize = resource_size(res); - - if (request_mem_region(lcd->phybase, lcd->physize, - DRIVERNAME) == NULL) { - ret = -EBUSY; - goto out_no_memregion; - } - - lcd->virtbase = ioremap(lcd->phybase, lcd->physize); - if (!lcd->virtbase) { - ret = -ENOMEM; - goto out_no_memregion; - } - - lcd->irq = platform_get_irq(pdev, 0); - /* If no IRQ is supplied, we'll survive without it */ - if (lcd->irq >= 0) { - if (request_irq(lcd->irq, charlcd_interrupt, 0, - DRIVERNAME, lcd)) { - ret = -EIO; - goto out_no_irq; - } - } - - platform_set_drvdata(pdev, lcd); - - /* - * Initialize the display in a delayed work, because - * it is VERY slow and would slow down the boot of the system. - */ - INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work); - schedule_delayed_work(&lcd->init_work, 0); - - dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n", - lcd->phybase); - - return 0; - -out_no_irq: - iounmap(lcd->virtbase); -out_no_memregion: - release_mem_region(lcd->phybase, SZ_4K); -out_no_resource: - kfree(lcd); - return ret; -} - -static int charlcd_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct charlcd *lcd = platform_get_drvdata(pdev); - - /* Power the display off */ - charlcd_4bit_command(lcd, HD_DISPCTRL); - return 0; -} - -static int charlcd_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct charlcd *lcd = platform_get_drvdata(pdev); - - /* Turn the display back on */ - charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); - return 0; -} - -static const struct dev_pm_ops charlcd_pm_ops = { - .suspend = charlcd_suspend, - .resume = charlcd_resume, -}; - -static const struct of_device_id charlcd_match[] = { - { .compatible = "arm,versatile-lcd", }, - {} -}; - -static struct platform_driver charlcd_driver = { - .driver = { - .name = DRIVERNAME, - .pm = &charlcd_pm_ops, - .suppress_bind_attrs = true, - .of_match_table = of_match_ptr(charlcd_match), - }, -}; -builtin_platform_driver_probe(charlcd_driver, charlcd_probe); -- cgit v1.2.3-70-g09d2 From 7e961c12be424c6c1e355d469cc1b82dbf3af718 Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Fri, 24 Mar 2017 10:33:21 -0500 Subject: fpga: Add support for Xilinx LogiCORE PR Decoupler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the Xilinx LogiCORE PR Decoupler soft-ip that does decoupling of PR regions in the FPGA fabric during partial reconfiguration. Signed-off-by: Moritz Fischer Signed-off-by: Michal Simek Cc: Sören Brinkmann Cc: linux-kernel@vger.kernel.org Cc: devicetree@vger.kernel.org Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 10 +++ drivers/fpga/Makefile | 1 + drivers/fpga/xilinx-pr-decoupler.c | 161 +++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 drivers/fpga/xilinx-pr-decoupler.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 116ee92fe034..161ba9dccede 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -95,6 +95,16 @@ config ALTERA_PR_IP_CORE_PLAT Platform driver support for Altera Partial Reconfiguration IP component +config XILINX_PR_DECOUPLER + tristate "Xilinx LogiCORE PR Decoupler" + depends on FPGA_BRIDGE + depends on HAS_IOMEM + help + Say Y to enable drivers for Xilinx LogiCORE PR Decoupler. + The PR Decoupler exists in the FPGA fabric to isolate one + region of the FPGA from the busses while that region is + being reprogrammed during partial reconfig. + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 530cf9410dde..2a4f0218145c 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o obj-$(CONFIG_ALTERA_FREEZE_BRIDGE) += altera-freeze-bridge.o +obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o # High Level Interfaces obj-$(CONFIG_FPGA_REGION) += fpga-region.o diff --git a/drivers/fpga/xilinx-pr-decoupler.c b/drivers/fpga/xilinx-pr-decoupler.c new file mode 100644 index 000000000000..e359930bebc8 --- /dev/null +++ b/drivers/fpga/xilinx-pr-decoupler.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017, National Instruments Corp. + * Copyright (c) 2017, Xilix Inc + * + * FPGA Bridge Driver for the Xilinx LogiCORE Partial Reconfiguration + * Decoupler IP Core. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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 +#include + +#define CTRL_CMD_DECOUPLE BIT(0) +#define CTRL_CMD_COUPLE 0 +#define CTRL_OFFSET 0 + +struct xlnx_pr_decoupler_data { + void __iomem *io_base; + struct clk *clk; +}; + +static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d, + u32 offset, u32 val) +{ + writel(val, d->io_base + offset); +} + +static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d, + u32 offset) +{ + return readl(d->io_base + offset); +} + +static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable) +{ + int err; + struct xlnx_pr_decoupler_data *priv = bridge->priv; + + err = clk_enable(priv->clk); + if (err) + return err; + + if (enable) + xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE); + else + xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE); + + clk_disable(priv->clk); + + return 0; +} + +static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge) +{ + const struct xlnx_pr_decoupler_data *priv = bridge->priv; + u32 status; + int err; + + err = clk_enable(priv->clk); + if (err) + return err; + + status = readl(priv->io_base); + + clk_disable(priv->clk); + + return !status; +} + +static struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = { + .enable_set = xlnx_pr_decoupler_enable_set, + .enable_show = xlnx_pr_decoupler_enable_show, +}; + +static const struct of_device_id xlnx_pr_decoupler_of_match[] = { + { .compatible = "xlnx,pr-decoupler-1.00", }, + { .compatible = "xlnx,pr-decoupler", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match); + +static int xlnx_pr_decoupler_probe(struct platform_device *pdev) +{ + struct xlnx_pr_decoupler_data *priv; + int err; + struct resource *res; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->io_base)) + return PTR_ERR(priv->io_base); + + priv->clk = devm_clk_get(&pdev->dev, "aclk"); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "input clock not found\n"); + return PTR_ERR(priv->clk); + } + + err = clk_prepare_enable(priv->clk); + if (err) { + dev_err(&pdev->dev, "unable to enable clock\n"); + return err; + } + + clk_disable(priv->clk); + + err = fpga_bridge_register(&pdev->dev, "Xilinx PR Decoupler", + &xlnx_pr_decoupler_br_ops, priv); + + if (err) { + dev_err(&pdev->dev, "unable to register Xilinx PR Decoupler"); + clk_unprepare(priv->clk); + return err; + } + + return 0; +} + +static int xlnx_pr_decoupler_remove(struct platform_device *pdev) +{ + struct fpga_bridge *bridge = platform_get_drvdata(pdev); + struct xlnx_pr_decoupler_data *p = bridge->priv; + + fpga_bridge_unregister(&pdev->dev); + + clk_unprepare(p->clk); + + return 0; +} + +static struct platform_driver xlnx_pr_decoupler_driver = { + .probe = xlnx_pr_decoupler_probe, + .remove = xlnx_pr_decoupler_remove, + .driver = { + .name = "xlnx_pr_decoupler", + .of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match), + }, +}; + +module_platform_driver(xlnx_pr_decoupler_driver); + +MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler"); +MODULE_AUTHOR("Moritz Fischer "); +MODULE_AUTHOR("Michal Simek "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 1b595505bf68bf6a821e944f87766af76d515f96 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 31 Mar 2017 13:44:45 +0100 Subject: MAINTAINERS: nvmem: Remove myself from maintainers I've never been really been maintaining nvmem, so make that official. Signed-off-by: Maxime Ripard Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 216823d0471d..ef0d61c04d28 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9022,7 +9022,6 @@ F: drivers/nvme/target/fcloop.c NVMEM FRAMEWORK M: Srinivas Kandagatla -M: Maxime Ripard S: Maintained F: drivers/nvmem/ F: Documentation/devicetree/bindings/nvmem/ -- cgit v1.2.3-70-g09d2 From 5253193d5479006b14dbe475740d031ce3e01570 Mon Sep 17 00:00:00 2001 From: Aban Bedel Date: Fri, 31 Mar 2017 13:44:46 +0100 Subject: nvmem: core: Allow allocating several anonymous nvmem devices Currently the nvmem core expect the config to provide a name and ID that are then used to create the device name. When no device name is given 'nvmem' is used. However if there is several such anonymous devices they all get named 'nvmem0', which doesn't work. To fix this problem use the ID from the config only when the config also provides a name. When no name is provided take the uinque ID of the nvmem device instead. Signed-off-by: Aban Bedel Reviewed-by: Moritz Fischer Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 408b521ee520..8c830a80a648 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -468,7 +468,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) np = config->dev->of_node; nvmem->dev.of_node = np; dev_set_name(&nvmem->dev, "%s%d", - config->name ? : "nvmem", config->id); + config->name ? : "nvmem", + config->name ? config->id : nvmem->id); nvmem->read_only = of_property_read_bool(np, "read-only") | config->read_only; -- cgit v1.2.3-70-g09d2 From 4a72cda530f87ee0d96c4b79b9e1a865f7761659 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Fri, 31 Mar 2017 13:44:47 +0100 Subject: nvmem: sunxi-sid: read NVMEM size from device compatible Sometimes the SID device have more memory address space than the real NVMEM size (for the registers used to read/write the SID). Fetch the NVMEM size from device compatible, rather than the memory address space's length, in order to prepare for adding some registers-based read support. Signed-off-by: Icenowy Zheng Acked-by: Maxime Ripard Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/sunxi_sid.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c index 1567ccca8de3..69524b67007f 100644 --- a/drivers/nvmem/sunxi_sid.c +++ b/drivers/nvmem/sunxi_sid.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,10 @@ static struct nvmem_config econfig = { .owner = THIS_MODULE, }; +struct sunxi_sid_cfg { + u32 size; +}; + struct sunxi_sid { void __iomem *base; }; @@ -72,18 +77,24 @@ static int sunxi_sid_probe(struct platform_device *pdev) struct sunxi_sid *sid; int ret, i, size; char *randomness; + const struct sunxi_sid_cfg *cfg; sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); if (!sid) return -ENOMEM; + cfg = of_device_get_match_data(dev); + if (!cfg) + return -EINVAL; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); sid->base = devm_ioremap_resource(dev, res); if (IS_ERR(sid->base)) return PTR_ERR(sid->base); - size = resource_size(res) - 1; - econfig.size = resource_size(res); + size = cfg->size; + + econfig.size = size; econfig.dev = dev; econfig.reg_read = sunxi_sid_read; econfig.priv = sid; @@ -119,9 +130,17 @@ static int sunxi_sid_remove(struct platform_device *pdev) return nvmem_unregister(nvmem); } +static const struct sunxi_sid_cfg sun4i_a10_cfg = { + .size = 0x10, +}; + +static const struct sunxi_sid_cfg sun7i_a20_cfg = { + .size = 0x200, +}; + static const struct of_device_id sunxi_sid_of_match[] = { - { .compatible = "allwinner,sun4i-a10-sid" }, - { .compatible = "allwinner,sun7i-a20-sid" }, + { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, {/* sentinel */}, }; MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); -- cgit v1.2.3-70-g09d2 From 1a9636426b9f994d57dfe5a64e3f4644747a2e95 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Fri, 31 Mar 2017 13:44:48 +0100 Subject: nvmem: sunxi-sid: add support for H3's SID controller The H3 SoC have a bigger SID controller, which has its direct read address at 0x200 position in the SID block, not 0x0. Also, H3 SID controller has some silicon bug that makes the direct read value wrong at cold boot, add code to workaround the bug. (This bug has already been fixed on A64 and later SoCs) Signed-off-by: Icenowy Zheng Acked-by: Maxime Ripard Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- .../bindings/nvmem/allwinner,sunxi-sid.txt | 6 ++- drivers/nvmem/sunxi_sid.c | 62 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt b/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt index d543ed3f5363..ef06d061913c 100644 --- a/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt +++ b/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt @@ -1,7 +1,11 @@ Allwinner sunxi-sid Required properties: -- compatible: "allwinner,sun4i-a10-sid" or "allwinner,sun7i-a20-sid" +- compatible: Should be one of the following: + "allwinner,sun4i-a10-sid" + "allwinner,sun7i-a20-sid" + "allwinner,sun8i-h3-sid" + - reg: Should contain registers location and length = Data cells = diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c index 69524b67007f..0d6648be93b8 100644 --- a/drivers/nvmem/sunxi_sid.c +++ b/drivers/nvmem/sunxi_sid.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,15 @@ #include #include +/* Registers and special values for doing register-based SID readout on H3 */ +#define SUN8I_SID_PRCTL 0x40 +#define SUN8I_SID_RDKEY 0x60 + +#define SUN8I_SID_OFFSET_MASK 0x1FF +#define SUN8I_SID_OFFSET_SHIFT 16 +#define SUN8I_SID_OP_LOCK (0xAC << 8) +#define SUN8I_SID_READ BIT(1) + static struct nvmem_config econfig = { .name = "sunxi-sid", .read_only = true, @@ -34,11 +44,14 @@ static struct nvmem_config econfig = { }; struct sunxi_sid_cfg { + u32 value_offset; u32 size; + bool need_register_readout; }; struct sunxi_sid { void __iomem *base; + u32 value_offset; }; /* We read the entire key, due to a 32 bit read alignment requirement. Since we @@ -63,12 +76,36 @@ static int sunxi_sid_read(void *context, unsigned int offset, struct sunxi_sid *sid = context; u8 *buf = val; + /* Offset the read operation to the real position of SID */ + offset += sid->value_offset; + while (bytes--) *buf++ = sunxi_sid_read_byte(sid, offset++); return 0; } +static int sun8i_sid_register_readout(const struct sunxi_sid *sid, + const unsigned int word) +{ + u32 reg_val; + int ret; + + /* Set word, lock access, and set read command */ + reg_val = (word & SUN8I_SID_OFFSET_MASK) + << SUN8I_SID_OFFSET_SHIFT; + reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; + writel(reg_val, sid->base + SUN8I_SID_PRCTL); + + ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, + !(reg_val & SUN8I_SID_READ), 100, 250000); + if (ret) + return ret; + + writel(0, sid->base + SUN8I_SID_PRCTL); + return 0; +} + static int sunxi_sid_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -86,6 +123,7 @@ static int sunxi_sid_probe(struct platform_device *pdev) cfg = of_device_get_match_data(dev); if (!cfg) return -EINVAL; + sid->value_offset = cfg->value_offset; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); sid->base = devm_ioremap_resource(dev, res); @@ -94,6 +132,23 @@ static int sunxi_sid_probe(struct platform_device *pdev) size = cfg->size; + if (cfg->need_register_readout) { + /* + * H3's SID controller have a bug that the value at 0x200 + * offset is not the correct value when the hardware is reseted. + * However, after doing a register-based read operation, the + * value become right. + * Do a full read operation here, but ignore its value + * (as it's more fast to read by direct MMIO value than + * with registers) + */ + for (i = 0; i < (size >> 2); i++) { + ret = sun8i_sid_register_readout(sid, i); + if (ret) + return ret; + } + } + econfig.size = size; econfig.dev = dev; econfig.reg_read = sunxi_sid_read; @@ -138,9 +193,16 @@ static const struct sunxi_sid_cfg sun7i_a20_cfg = { .size = 0x200, }; +static const struct sunxi_sid_cfg sun8i_h3_cfg = { + .value_offset = 0x200, + .size = 0x100, + .need_register_readout = true, +}; + static const struct of_device_id sunxi_sid_of_match[] = { { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, {/* sentinel */}, }; MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); -- cgit v1.2.3-70-g09d2 From 4cefb74ada57c479e4f8b92d28008d35c1527e87 Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Fri, 31 Mar 2017 13:44:49 +0100 Subject: nvmem: imx-ocotp: fix usage of "dev" pointers Assign the correct dev pointer to struct ocotp_priv during probe. This is needed to display dev_* messages correctly. Furthermore harmonize the usage of dev (instead of &pdev->dev) in the probe function. Signed-off-by: Richard Leitner Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/imx-ocotp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index b8ca1e677b01..549177dde41a 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -90,12 +90,14 @@ static int imx_ocotp_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(dev, res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); - priv->clk = devm_clk_get(&pdev->dev, NULL); + priv->clk = devm_clk_get(dev, NULL); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); -- cgit v1.2.3-70-g09d2 From c066c1c0e43f6914d34a0c574c0110b523820567 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 31 Mar 2017 13:44:50 +0100 Subject: nvmem: Add driver for the i.MX IIM This adds a readonly nvmem driver for the i.MX IC Identification Module (IIM). The IIM is found on the older i.MX SoCs like the i.MX25, i.MX27, i.MX31, i.MX35, i.MX51 and the i.MX53. The IIM can control up to 8 fuse banks with 256 bit each. Not all of the banks are equipped on the different SoCs. The actual number of fuses differ from 512 on the i.MX27 and 1152 on the i.MX53. The fuses are one time writable, but writing is currently not supported in the driver. Signed-off-by: Michael Grzeschik Signed-off-by: Sascha Hauer Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 11 +++ drivers/nvmem/Makefile | 2 + drivers/nvmem/imx-iim.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 drivers/nvmem/imx-iim.c diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 650f1b1797ad..101ced4c84be 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -13,6 +13,17 @@ menuconfig NVMEM if NVMEM +config NVMEM_IMX_IIM + tristate "i.MX IC Identification Module support" + depends on ARCH_MXC || COMPILE_TEST + help + This is a driver for the IC Identification Module (IIM) available on + i.MX SoCs, providing access to 4 Kbits of programmable + eFuses. + + This driver can also be built as a module. If so, the module + will be called nvmem-imx-iim. + config NVMEM_IMX_OCOTP tristate "i.MX6 On-Chip OTP Controller support" depends on SOC_IMX6 || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 86e45995fdad..173140658693 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -8,6 +8,8 @@ nvmem_core-y := core.o # Devices obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o nvmem-bcm-ocotp-y := bcm-ocotp.o +obj-$(CONFIG_NVMEM_IMX_IIM) += nvmem-imx-iim.o +nvmem-imx-iim-y := imx-iim.o obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o nvmem-imx-ocotp-y := imx-ocotp.o obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o diff --git a/drivers/nvmem/imx-iim.c b/drivers/nvmem/imx-iim.c new file mode 100644 index 000000000000..52ff65e0673f --- /dev/null +++ b/drivers/nvmem/imx-iim.c @@ -0,0 +1,173 @@ +/* + * i.MX IIM driver + * + * Copyright (c) 2017 Pengutronix, Michael Grzeschik + * + * Based on the barebox iim driver, + * Copyright (c) 2010 Baruch Siach , + * Orex Computed Radiography + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IIM_BANK_BASE(n) (0x800 + 0x400 * (n)) + +struct imx_iim_drvdata { + unsigned int nregs; +}; + +struct iim_priv { + void __iomem *base; + struct clk *clk; + struct nvmem_config nvmem; +}; + +static int imx_iim_read(void *context, unsigned int offset, + void *buf, size_t bytes) +{ + struct iim_priv *iim = context; + int i, ret; + u8 *buf8 = buf; + + ret = clk_prepare_enable(iim->clk); + if (ret) + return ret; + + for (i = offset; i < offset + bytes; i++) { + int bank = i >> 5; + int reg = i & 0x1f; + + *buf8++ = readl(iim->base + IIM_BANK_BASE(bank) + reg * 4); + } + + clk_disable_unprepare(iim->clk); + + return 0; +} + +static struct imx_iim_drvdata imx27_drvdata = { + .nregs = 2 * 32, +}; + +static struct imx_iim_drvdata imx25_imx31_imx35_drvdata = { + .nregs = 3 * 32, +}; + +static struct imx_iim_drvdata imx51_drvdata = { + .nregs = 4 * 32, +}; + +static struct imx_iim_drvdata imx53_drvdata = { + .nregs = 4 * 32 + 16, +}; + +static const struct of_device_id imx_iim_dt_ids[] = { + { + .compatible = "fsl,imx25-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx27-iim", + .data = &imx27_drvdata, + }, { + .compatible = "fsl,imx31-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx35-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx51-iim", + .data = &imx51_drvdata, + }, { + .compatible = "fsl,imx53-iim", + .data = &imx53_drvdata, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, imx_iim_dt_ids); + +static int imx_iim_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct device *dev = &pdev->dev; + struct resource *res; + struct iim_priv *iim; + struct nvmem_device *nvmem; + struct nvmem_config *cfg; + const struct imx_iim_drvdata *drvdata = NULL; + + iim = devm_kzalloc(dev, sizeof(*iim), GFP_KERNEL); + if (!iim) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iim->base = devm_ioremap_resource(dev, res); + if (IS_ERR(iim->base)) + return PTR_ERR(iim->base); + + of_id = of_match_device(imx_iim_dt_ids, dev); + if (!of_id) + return -ENODEV; + + drvdata = of_id->data; + + iim->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(iim->clk)) + return PTR_ERR(iim->clk); + + cfg = &iim->nvmem; + + cfg->name = "imx-iim", + cfg->read_only = true, + cfg->word_size = 1, + cfg->stride = 1, + cfg->owner = THIS_MODULE, + cfg->reg_read = imx_iim_read, + cfg->dev = dev; + cfg->size = drvdata->nregs; + cfg->priv = iim; + + nvmem = nvmem_register(cfg); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int imx_iim_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static struct platform_driver imx_iim_driver = { + .probe = imx_iim_probe, + .remove = imx_iim_remove, + .driver = { + .name = "imx-iim", + .of_match_table = imx_iim_dt_ids, + }, +}; +module_platform_driver(imx_iim_driver); + +MODULE_AUTHOR("Michael Grzeschik "); +MODULE_DESCRIPTION("i.MX IIM driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 40bbb60cecc0ff145f56b69af08b340acd8fba9e Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 31 Mar 2017 13:44:51 +0100 Subject: dt-bindings: nvmem: Add i.MX IIM binding doc The IIM is part of the i.MX device trees for long already, add a binding document for it. Signed-off-by: Sascha Hauer Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/nvmem/imx-iim.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Documentation/devicetree/bindings/nvmem/imx-iim.txt diff --git a/Documentation/devicetree/bindings/nvmem/imx-iim.txt b/Documentation/devicetree/bindings/nvmem/imx-iim.txt new file mode 100644 index 000000000000..1978c5bcd96d --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/imx-iim.txt @@ -0,0 +1,22 @@ +Freescale i.MX IC Identification Module (IIM) device tree bindings + +This binding represents the IC Identification Module (IIM) found on +i.MX25, i.MX27, i.MX31, i.MX35, i.MX51 and i.MX53 SoCs. + +Required properties: +- compatible: should be one of + "fsl,imx25-iim", "fsl,imx27-iim", + "fsl,imx31-iim", "fsl,imx35-iim", + "fsl,imx51-iim", "fsl,imx53-iim", +- reg: Should contain the register base and length. +- interrupts: Should contain the interrupt for the IIM +- clocks: Should contain a phandle pointing to the gated peripheral clock. + +Example: + + iim: iim@63f98000 { + compatible = "fsl,imx53-iim", "fsl,imx27-iim"; + reg = <0x63f98000 0x4000>; + interrupts = <69>; + clocks = <&clks IMX5_CLK_IIM_GATE>; + }; -- cgit v1.2.3-70-g09d2 From 8bb79e85cc4ad859bda3cd1dda0abc505bafcc29 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 31 Mar 2017 13:44:52 +0100 Subject: dt-bindings: imx-ocotp: add compatible string for i.MX7D/S Add compatible string for i.MX7D/S Signed-off-by: Peng Fan Cc: Shawn Guo Cc: Srinivas Kandagatla Cc: Maxime Ripard Cc: Mark Rutland Signed-off-by: Srinivas Kandagatla Acked-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/imx-ocotp.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt index 966a72ecc6bd..bbde2e43ef86 100644 --- a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt +++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt @@ -9,6 +9,7 @@ Required properties: "fsl,imx6sl-ocotp" (i.MX6SL), or "fsl,imx6sx-ocotp" (i.MX6SX), "fsl,imx6ul-ocotp" (i.MX6UL), + "fsl,imx7d-ocotp" (i.MX7D/S), followed by "syscon". - reg: Should contain the register base and length. - clocks: Should contain a phandle pointing to the gated peripheral clock. -- cgit v1.2.3-70-g09d2 From 711d4547793176fa0773c9cdf7af838417f14ef9 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 31 Mar 2017 13:44:53 +0100 Subject: nvmem: octop: Add i.MX7D support Add i.MX7D support. There is 16 banks, each bank 4 words. Signed-off-by: Peng Fan Cc: Srinivas Kandagatla Cc: Maxime Ripard Cc: Shawn Guo Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/imx-ocotp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index 549177dde41a..0d337054845f 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -74,6 +74,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6sl-ocotp", (void *)64 }, { .compatible = "fsl,imx6sx-ocotp", (void *)128 }, { .compatible = "fsl,imx6ul-ocotp", (void *)128 }, + { .compatible = "fsl,imx7d-ocotp", (void *)64 }, { }, }; MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); -- cgit v1.2.3-70-g09d2 From 9b66587ee259800d278e84af64755083ea09926b Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Fri, 31 Mar 2017 13:44:54 +0100 Subject: nvmem: imx-ocotp: clear error bit after reading locked values When reading a "read locked" value from the OCOTP controller on i.MX6 SoC's an error bit is set. This bit has to be cleared by software before any new write, read or reload access can be issued. Therefore clear it after we detect such an "locked read". Signed-off-by: Richard Leitner Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/imx-ocotp.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index 0d337054845f..0bb8d0dd56f9 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -25,6 +25,19 @@ #include #include +#define IMX_OCOTP_OFFSET_B0W0 0x400 /* Offset from base address of the + * OTP Bank0 Word0 + */ +#define IMX_OCOTP_OFFSET_PER_WORD 0x10 /* Offset between the start addr + * of two consecutive OTP words. + */ +#define IMX_OCOTP_ADDR_CTRL 0x0000 +#define IMX_OCOTP_ADDR_CTRL_CLR 0x0008 + +#define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 + +#define IMX_OCOTP_READ_LOCKED_VAL 0xBADABADA + struct ocotp_priv { struct device *dev; struct clk *clk; @@ -32,6 +45,17 @@ struct ocotp_priv { unsigned int nregs; }; +static void imx_ocotp_clr_err_if_set(void __iomem *base) +{ + u32 c; + + c = readl(base + IMX_OCOTP_ADDR_CTRL); + if (!(c & IMX_OCOTP_BM_CTRL_ERROR)) + return; + + writel(IMX_OCOTP_BM_CTRL_ERROR, base + IMX_OCOTP_ADDR_CTRL_CLR); +} + static int imx_ocotp_read(void *context, unsigned int offset, void *val, size_t bytes) { @@ -52,11 +76,22 @@ static int imx_ocotp_read(void *context, unsigned int offset, dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); return ret; } - for (i = index; i < (index + count); i++) - *buf++ = readl(priv->base + 0x400 + i * 0x10); - clk_disable_unprepare(priv->clk); + for (i = index; i < (index + count); i++) { + *buf++ = readl(priv->base + IMX_OCOTP_OFFSET_B0W0 + + i * IMX_OCOTP_OFFSET_PER_WORD); + + /* 47.3.1.2 + * For "read locked" registers 0xBADABADA will be returned and + * HW_OCOTP_CTRL[ERROR] will be set. It must be cleared by + * software before any new write, read or reload access can be + * issued + */ + if (*(buf - 1) == IMX_OCOTP_READ_LOCKED_VAL) + imx_ocotp_clr_err_if_set(priv->base); + } + clk_disable_unprepare(priv->clk); return 0; } -- cgit v1.2.3-70-g09d2 From 0642bac7da4240912c3e923a7d1c47af3dd89b9d Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Fri, 31 Mar 2017 13:44:55 +0100 Subject: nvmem: imx-ocotp: add write support Implement write routine for OCOTP controller found in i.MX6 SoC's. Furthermore add locking to the read function to prevent race conditions. The write routine code is based on the fsl_otp driver from Freescale. Signed-off-by: Richard Leitner Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/nvmem/imx-ocotp.txt | 4 + drivers/nvmem/imx-ocotp.c | 210 ++++++++++++++++++++- 2 files changed, 212 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt index bbde2e43ef86..70d791b03ea1 100644 --- a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt +++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt @@ -14,10 +14,14 @@ Required properties: - reg: Should contain the register base and length. - clocks: Should contain a phandle pointing to the gated peripheral clock. +Optional properties: +- read-only: disable write access + Example: ocotp: ocotp@021bc000 { compatible = "fsl,imx6q-ocotp", "syscon"; reg = <0x021bc000 0x4000>; clocks = <&clks IMX6QDL_CLK_IIM>; + read-only; }; diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index 0bb8d0dd56f9..193ca8fd350a 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -7,6 +7,9 @@ * Copyright (c) 2010 Baruch Siach , * Orex Computed Radiography * + * Write support based on the fsl_otp driver, + * Copyright (C) 2010-2013 Freescale Semiconductor, Inc + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. @@ -24,6 +27,7 @@ #include #include #include +#include #define IMX_OCOTP_OFFSET_B0W0 0x400 /* Offset from base address of the * OTP Bank0 Word0 @@ -31,20 +35,69 @@ #define IMX_OCOTP_OFFSET_PER_WORD 0x10 /* Offset between the start addr * of two consecutive OTP words. */ + #define IMX_OCOTP_ADDR_CTRL 0x0000 +#define IMX_OCOTP_ADDR_CTRL_SET 0x0004 #define IMX_OCOTP_ADDR_CTRL_CLR 0x0008 +#define IMX_OCOTP_ADDR_TIMING 0x0010 +#define IMX_OCOTP_ADDR_DATA 0x0020 +#define IMX_OCOTP_BM_CTRL_ADDR 0x0000007F +#define IMX_OCOTP_BM_CTRL_BUSY 0x00000100 #define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 +#define IMX_OCOTP_BM_CTRL_REL_SHADOWS 0x00000400 +#define DEF_RELAX 20 /* > 16.5ns */ +#define IMX_OCOTP_WR_UNLOCK 0x3E770000 #define IMX_OCOTP_READ_LOCKED_VAL 0xBADABADA +static DEFINE_MUTEX(ocotp_mutex); + struct ocotp_priv { struct device *dev; struct clk *clk; void __iomem *base; unsigned int nregs; + struct nvmem_config *config; }; +static int imx_ocotp_wait_for_busy(void __iomem *base, u32 flags) +{ + int count; + u32 c, mask; + + mask = IMX_OCOTP_BM_CTRL_BUSY | IMX_OCOTP_BM_CTRL_ERROR | flags; + + for (count = 10000; count >= 0; count--) { + c = readl(base + IMX_OCOTP_ADDR_CTRL); + if (!(c & mask)) + break; + cpu_relax(); + } + + if (count < 0) { + /* HW_OCOTP_CTRL[ERROR] will be set under the following + * conditions: + * - A write is performed to a shadow register during a shadow + * reload (essentially, while HW_OCOTP_CTRL[RELOAD_SHADOWS] is + * set. In addition, the contents of the shadow register shall + * not be updated. + * - A write is performed to a shadow register which has been + * locked. + * - A read is performed to from a shadow register which has + * been read locked. + * - A program is performed to a fuse word which has been locked + * - A read is performed to from a fuse word which has been read + * locked. + */ + if (c & IMX_OCOTP_BM_CTRL_ERROR) + return -EPERM; + return -ETIMEDOUT; + } + + return 0; +} + static void imx_ocotp_clr_err_if_set(void __iomem *base) { u32 c; @@ -71,12 +124,21 @@ static int imx_ocotp_read(void *context, unsigned int offset, if (count > (priv->nregs - index)) count = priv->nregs - index; + mutex_lock(&ocotp_mutex); + ret = clk_prepare_enable(priv->clk); if (ret < 0) { + mutex_unlock(&ocotp_mutex); dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); return ret; } + ret = imx_ocotp_wait_for_busy(priv->base, 0); + if (ret < 0) { + dev_err(priv->dev, "timeout during read setup\n"); + goto read_end; + } + for (i = index; i < (index + count); i++) { *buf++ = readl(priv->base + IMX_OCOTP_OFFSET_B0W0 + i * IMX_OCOTP_OFFSET_PER_WORD); @@ -90,18 +152,160 @@ static int imx_ocotp_read(void *context, unsigned int offset, if (*(buf - 1) == IMX_OCOTP_READ_LOCKED_VAL) imx_ocotp_clr_err_if_set(priv->base); } + ret = 0; +read_end: clk_disable_unprepare(priv->clk); - return 0; + mutex_unlock(&ocotp_mutex); + return ret; +} + +static int imx_ocotp_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct ocotp_priv *priv = context; + u32 *buf = val; + int ret; + + unsigned long clk_rate = 0; + unsigned long strobe_read, relax, strobe_prog; + u32 timing = 0; + u32 ctrl; + u8 waddr; + + /* allow only writing one complete OTP word at a time */ + if ((bytes != priv->config->word_size) || + (offset % priv->config->word_size)) + return -EINVAL; + + mutex_lock(&ocotp_mutex); + + ret = clk_prepare_enable(priv->clk); + if (ret < 0) { + mutex_unlock(&ocotp_mutex); + dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); + return ret; + } + + /* 47.3.1.3.1 + * Program HW_OCOTP_TIMING[STROBE_PROG] and HW_OCOTP_TIMING[RELAX] + * fields with timing values to match the current frequency of the + * ipg_clk. OTP writes will work at maximum bus frequencies as long + * as the HW_OCOTP_TIMING parameters are set correctly. + */ + clk_rate = clk_get_rate(priv->clk); + + relax = clk_rate / (1000000000 / DEF_RELAX) - 1; + strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1; + strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1; + + timing = strobe_prog & 0x00000FFF; + timing |= (relax << 12) & 0x0000F000; + timing |= (strobe_read << 16) & 0x003F0000; + + writel(timing, priv->base + IMX_OCOTP_ADDR_TIMING); + + /* 47.3.1.3.2 + * Check that HW_OCOTP_CTRL[BUSY] and HW_OCOTP_CTRL[ERROR] are clear. + * Overlapped accesses are not supported by the controller. Any pending + * write or reload must be completed before a write access can be + * requested. + */ + ret = imx_ocotp_wait_for_busy(priv->base, 0); + if (ret < 0) { + dev_err(priv->dev, "timeout during timing setup\n"); + goto write_end; + } + + /* 47.3.1.3.3 + * Write the requested address to HW_OCOTP_CTRL[ADDR] and program the + * unlock code into HW_OCOTP_CTRL[WR_UNLOCK]. This must be programmed + * for each write access. The lock code is documented in the register + * description. Both the unlock code and address can be written in the + * same operation. + */ + /* OTP write/read address specifies one of 128 word address locations */ + waddr = offset / 4; + + ctrl = readl(priv->base + IMX_OCOTP_ADDR_CTRL); + ctrl &= ~IMX_OCOTP_BM_CTRL_ADDR; + ctrl |= waddr & IMX_OCOTP_BM_CTRL_ADDR; + ctrl |= IMX_OCOTP_WR_UNLOCK; + + writel(ctrl, priv->base + IMX_OCOTP_ADDR_CTRL); + + /* 47.3.1.3.4 + * Write the data to the HW_OCOTP_DATA register. This will automatically + * set HW_OCOTP_CTRL[BUSY] and clear HW_OCOTP_CTRL[WR_UNLOCK]. To + * protect programming same OTP bit twice, before program OCOTP will + * automatically read fuse value in OTP and use read value to mask + * program data. The controller will use masked program data to program + * a 32-bit word in the OTP per the address in HW_OCOTP_CTRL[ADDR]. Bit + * fields with 1's will result in that OTP bit being programmed. Bit + * fields with 0's will be ignored. At the same time that the write is + * accepted, the controller makes an internal copy of + * HW_OCOTP_CTRL[ADDR] which cannot be updated until the next write + * sequence is initiated. This copy guarantees that erroneous writes to + * HW_OCOTP_CTRL[ADDR] will not affect an active write operation. It + * should also be noted that during the programming HW_OCOTP_DATA will + * shift right (with zero fill). This shifting is required to program + * the OTP serially. During the write operation, HW_OCOTP_DATA cannot be + * modified. + */ + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA); + + /* 47.4.1.4.5 + * Once complete, the controller will clear BUSY. A write request to a + * protected or locked region will result in no OTP access and no + * setting of HW_OCOTP_CTRL[BUSY]. In addition HW_OCOTP_CTRL[ERROR] will + * be set. It must be cleared by software before any new write access + * can be issued. + */ + ret = imx_ocotp_wait_for_busy(priv->base, 0); + if (ret < 0) { + if (ret == -EPERM) { + dev_err(priv->dev, "failed write to locked region"); + imx_ocotp_clr_err_if_set(priv->base); + } else { + dev_err(priv->dev, "timeout during data write\n"); + } + goto write_end; + } + + /* 47.3.1.4 + * Write Postamble: Due to internal electrical characteristics of the + * OTP during writes, all OTP operations following a write must be + * separated by 2 us after the clearing of HW_OCOTP_CTRL_BUSY following + * the write. + */ + udelay(2); + + /* reload all shadow registers */ + writel(IMX_OCOTP_BM_CTRL_REL_SHADOWS, + priv->base + IMX_OCOTP_ADDR_CTRL_SET); + ret = imx_ocotp_wait_for_busy(priv->base, + IMX_OCOTP_BM_CTRL_REL_SHADOWS); + if (ret < 0) { + dev_err(priv->dev, "timeout during shadow register reload\n"); + goto write_end; + } + +write_end: + clk_disable_unprepare(priv->clk); + mutex_unlock(&ocotp_mutex); + if (ret < 0) + return ret; + return bytes; } static struct nvmem_config imx_ocotp_nvmem_config = { .name = "imx-ocotp", - .read_only = true, + .read_only = false, .word_size = 4, .stride = 4, .owner = THIS_MODULE, .reg_read = imx_ocotp_read, + .reg_write = imx_ocotp_write, }; static const struct of_device_id imx_ocotp_dt_ids[] = { @@ -142,7 +346,9 @@ static int imx_ocotp_probe(struct platform_device *pdev) imx_ocotp_nvmem_config.size = 4 * priv->nregs; imx_ocotp_nvmem_config.dev = dev; imx_ocotp_nvmem_config.priv = priv; + priv->config = &imx_ocotp_nvmem_config; nvmem = nvmem_register(&imx_ocotp_nvmem_config); + if (IS_ERR(nvmem)) return PTR_ERR(nvmem); -- cgit v1.2.3-70-g09d2 From 65ebd3dfe79553b294f893ad2b6f54f8b8680e44 Mon Sep 17 00:00:00 2001 From: Varsha Rao Date: Fri, 7 Apr 2017 16:58:54 +0530 Subject: drivers: char: misc: Replace "foo * bar" with "foo *bar". Remove space after * in pointer type, to follow linux coding style. This patch fixes the following checkpatch issue: ERROR: "foo * bar" should be "foo *bar" Signed-off-by: Varsha Rao Signed-off-by: Greg Kroah-Hartman --- drivers/char/misc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 8069b361b8dd..ed8f79c630e7 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -109,7 +109,7 @@ static const struct file_operations misc_proc_fops = { }; #endif -static int misc_open(struct inode * inode, struct file * file) +static int misc_open(struct inode *inode, struct file *file) { int minor = iminor(inode); struct miscdevice *c; @@ -182,7 +182,7 @@ static const struct file_operations misc_fops = { * failure. */ -int misc_register(struct miscdevice * misc) +int misc_register(struct miscdevice *misc) { dev_t dev; int err = 0; -- cgit v1.2.3-70-g09d2 From 50a5e314792a4edbd64b1156ad4112480189a8dd Mon Sep 17 00:00:00 2001 From: Varsha Rao Date: Fri, 7 Apr 2017 17:01:31 +0530 Subject: drivers: char: misc: Add space after ','. Add space which is required after ',' to follow linux coding style. This patch fixes the checkpatch issue. Signed-off-by: Varsha Rao Signed-off-by: Greg Kroah-Hartman --- drivers/char/misc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/char/misc.c b/drivers/char/misc.c index ed8f79c630e7..3a19fa3269ff 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -150,7 +150,7 @@ static int misc_open(struct inode *inode, struct file *file) err = 0; replace_fops(file, new_fops); if (file->f_op->open) - err = file->f_op->open(inode,file); + err = file->f_op->open(inode, file); fail: mutex_unlock(&misc_mtx); return err; @@ -287,7 +287,7 @@ static int __init misc_init(void) goto fail_remove; err = -EIO; - if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) + if (register_chrdev(MISC_MAJOR, "misc", &misc_fops)) goto fail_printk; misc_class->devnode = misc_devnode; return 0; -- cgit v1.2.3-70-g09d2 From 5b884a95a5561c9ccea7b5cdf6a2fe9b48b589f5 Mon Sep 17 00:00:00 2001 From: Varsha Rao Date: Fri, 7 Apr 2017 17:02:32 +0530 Subject: drivers: char: misc: Add blank line after declaration. Add a blank line after declaration, to fix the checkpatch issue. Signed-off-by: Varsha Rao Signed-off-by: Greg Kroah-Hartman --- drivers/char/misc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 3a19fa3269ff..1312e29deb00 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -194,6 +194,7 @@ int misc_register(struct miscdevice *misc) if (is_dynamic) { int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); + if (i >= DYNAMIC_MINORS) { err = -EBUSY; goto out; -- cgit v1.2.3-70-g09d2 From 8ab44b4003381cf4bae7ccdfe81059aa9ce76033 Mon Sep 17 00:00:00 2001 From: Varsha Rao Date: Fri, 7 Apr 2017 17:04:06 +0530 Subject: drivers: char: misc: Replace printk with pr_err. Replace printk with pr_err to fix the checkpatch issue. Signed-off-by: Varsha Rao Signed-off-by: Greg Kroah-Hartman --- drivers/char/misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 1312e29deb00..c9cd1ea6844a 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -294,7 +294,7 @@ static int __init misc_init(void) return 0; fail_printk: - printk("unable to get major %d for misc devices\n", MISC_MAJOR); + pr_err("unable to get major %d for misc devices\n", MISC_MAJOR); class_destroy(misc_class); fail_remove: if (ret) -- cgit v1.2.3-70-g09d2 From e22aa9d781a27a961581c57442911309fb86a48e Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 24 Mar 2017 10:51:25 -0700 Subject: lkdtm: add bad USER_DS test This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return (via __VERIFY_PRE_USERMODE_STATE) still sees USER_DS. Since trying to deal with values other than USER_DS and KERNEL_DS across all architectures in a safe way is not sensible, this sets KERNEL_DS, but since that could be extremely dangerous if the protection is not present, it also raises SIGKILL for current, so that no matter what, the process will die. A successful test will be visible with a BUG(), like all the other LKDTM tests. Signed-off-by: Kees Cook Signed-off-by: Greg Kroah-Hartman --- drivers/misc/lkdtm.h | 1 + drivers/misc/lkdtm_bugs.c | 11 +++++++++++ drivers/misc/lkdtm_core.c | 1 + 3 files changed, 13 insertions(+) diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h index 67d27be60405..3b4976396ec4 100644 --- a/drivers/misc/lkdtm.h +++ b/drivers/misc/lkdtm.h @@ -27,6 +27,7 @@ void lkdtm_REFCOUNT_ZERO_SUB(void); void lkdtm_REFCOUNT_ZERO_ADD(void); void lkdtm_CORRUPT_LIST_ADD(void); void lkdtm_CORRUPT_LIST_DEL(void); +void lkdtm_CORRUPT_USER_DS(void); /* lkdtm_heap.c */ void lkdtm_OVERWRITE_ALLOCATION(void); diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c index e3f4cd8876b5..ed4f4c56c796 100644 --- a/drivers/misc/lkdtm_bugs.c +++ b/drivers/misc/lkdtm_bugs.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include struct lkdtm_list { struct list_head node; @@ -279,3 +281,12 @@ void lkdtm_CORRUPT_LIST_DEL(void) else pr_err("list_del() corruption not detected!\n"); } + +void lkdtm_CORRUPT_USER_DS(void) +{ + pr_info("setting bad task size limit\n"); + set_fs(KERNEL_DS); + + /* Make sure we do not keep running with a KERNEL_DS! */ + force_sig(SIGKILL, current); +} diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c index b9a4cd4a9b68..42d2b8e31e6b 100644 --- a/drivers/misc/lkdtm_core.c +++ b/drivers/misc/lkdtm_core.c @@ -199,6 +199,7 @@ struct crashtype crashtypes[] = { CRASHTYPE(OVERFLOW), CRASHTYPE(CORRUPT_LIST_ADD), CRASHTYPE(CORRUPT_LIST_DEL), + CRASHTYPE(CORRUPT_USER_DS), CRASHTYPE(CORRUPT_STACK), CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE), CRASHTYPE(OVERWRITE_ALLOCATION), -- cgit v1.2.3-70-g09d2 From 7064dc7fc13b2994d33ae540ffb7a3a05ac463bf Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 28 Mar 2017 11:57:27 +0200 Subject: lkdtm: turn off kcov for lkdtm_rodata_do_nothing: I ran into a link error on ARM64 for lkdtm_rodata_do_nothing: drivers/misc/built-in.o: In function `lkdtm_rodata_do_nothing': :(.rodata+0x68c8): relocation truncated to fit: R_AARCH64_CALL26 against symbol `__sanitizer_cov_trace_pc' defined in .text section in kernel/built-in.o I did not analyze this further, but my theory is that we would need a trampoline to call __sanitizer_cov_trace_pc(), but the linker (correctly) only adds trampolines for callers in executable sections. Disabling KCOV for this one file avoids the build failure with no other practical downsides I can think of. The problem can only happen on kernels that contain both kcov and lkdtm, so if we want to backport this, it should be in the earliest version that has both (v4.8). Fixes: 5c9a8750a640 ("kernel: add kcov code coverage") Fixes: 9a49a528dcf3 ("lkdtm: add function for testing .rodata section") Signed-off-by: Arnd Bergmann Acked-by: Dmitry Vyukov Acked-by: Kees Cook Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 4c4d6dc03181..4fb10af2ea1c 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -61,6 +61,8 @@ lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o +KCOV_INSTRUMENT_lkdtm_rodata.o := n + OBJCOPYFLAGS := OBJCOPYFLAGS_lkdtm_rodata_objcopy.o := \ --set-section-flags .text=alloc,readonly \ -- cgit v1.2.3-70-g09d2 From 8af11c1cdd8fa08217e702b57cf96e9030db52b2 Mon Sep 17 00:00:00 2001 From: Saul Wold Date: Tue, 28 Mar 2017 15:06:08 -0700 Subject: ver_linux: Use /usr/bin/awk instead of /bin/awk Most Linux distributions contain awk in /usr/bin by default, not in /bin. This script's suggested use is for creating version information for bug reporting. This has been tested on a number of different distributions, including Fedora, Ubuntu, OpenSUSE, Debian, Centos, Arch Linuxi, and Poky! Signed-off-by: Saul Wold Reviewed-by: Alexander Kapshuk Reviewed-by: Mark Gross Signed-off-by: Greg Kroah-Hartman --- scripts/ver_linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ver_linux b/scripts/ver_linux index 430b201f3e25..b51de8a7e2a3 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -1,4 +1,4 @@ -#!/bin/awk -f +#!/usr/bin/awk -f # Before running this script please ensure that your PATH is # typical as you use for compilation/installation. I use # /bin /sbin /usr/bin /usr/sbin /usr/local/bin, but it may -- cgit v1.2.3-70-g09d2 From f952a401ac7f566e9a577f3460dbb037cdcc56f1 Mon Sep 17 00:00:00 2001 From: Thierry Escande Date: Tue, 28 Mar 2017 18:11:25 +0200 Subject: firmware: google memconsole: Remove useless submenu in Kconfig This patch removes the "Google Firmware Drivers" menu containing a menuconfig entry with the exact same name. The menuconfig is now directly under the "Firmware Drivers" entry. Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/Kconfig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 29c8cdda82a1..475bd0122b9e 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -1,4 +1,4 @@ -config GOOGLE_FIRMWARE +menuconfig GOOGLE_FIRMWARE bool "Google Firmware Drivers" depends on X86 default n @@ -7,8 +7,7 @@ config GOOGLE_FIRMWARE only useful if you are working directly on one of their proprietary servers. If in doubt, say "N". -menu "Google Firmware Drivers" - depends on GOOGLE_FIRMWARE +if GOOGLE_FIRMWARE config GOOGLE_SMI tristate "SMI interface for Google platforms" @@ -28,4 +27,4 @@ config GOOGLE_MEMCONSOLE the EBDA on Google servers. If found, this log is exported to userland in the file /sys/firmware/log. -endmenu +endif # GOOGLE_FIRMWARE -- cgit v1.2.3-70-g09d2 From afe9dba4f9aedae417243a1782d085ff23e97b77 Mon Sep 17 00:00:00 2001 From: Thierry Escande Date: Tue, 28 Mar 2017 18:11:26 +0200 Subject: firmware: google memconsole: Move specific EBDA parts This patch splits memconsole.c in 2 parts. One containing the architecture-independent part and the other one containing the EBDA specific part. This prepares the integration of coreboot support for the memconsole. The memconsole driver is now named as memconsole-x86-legacy. Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/Kconfig | 9 +- drivers/firmware/google/Makefile | 3 +- drivers/firmware/google/memconsole-x86-legacy.c | 154 +++++++++++++++++++++++ drivers/firmware/google/memconsole.c | 155 ++++-------------------- drivers/firmware/google/memconsole.h | 43 +++++++ 5 files changed, 229 insertions(+), 135 deletions(-) create mode 100644 drivers/firmware/google/memconsole-x86-legacy.c create mode 100644 drivers/firmware/google/memconsole.h diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 475bd0122b9e..27a0658b8523 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -20,8 +20,13 @@ config GOOGLE_SMI variables. config GOOGLE_MEMCONSOLE - tristate "Firmware Memory Console" - depends on DMI + tristate + depends on GOOGLE_MEMCONSOLE_X86_LEGACY + +config GOOGLE_MEMCONSOLE_X86_LEGACY + tristate "Firmware Memory Console - X86 Legacy support" + depends on X86 && ACPI && DMI + select GOOGLE_MEMCONSOLE help This option enables the kernel to search for a firmware log in the EBDA on Google servers. If found, this log is exported to diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index 54a294e3cb61..10683ef8d2cd 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o -obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c new file mode 100644 index 000000000000..529078c62488 --- /dev/null +++ b/drivers/firmware/google/memconsole-x86-legacy.c @@ -0,0 +1,154 @@ +/* + * memconsole-x86-legacy.c + * + * EBDA specific parts of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 +#include +#include + +#include "memconsole.h" + +#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE +#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) + +struct biosmemcon_ebda { + u32 signature; + union { + struct { + u8 enabled; + u32 buffer_addr; + u16 start; + u16 end; + u16 num_chars; + u8 wrapped; + } __packed v1; + struct { + u32 buffer_addr; + /* Misdocumented as number of pages! */ + u16 num_bytes; + u16 start; + u16 end; + } __packed v2; + }; +} __packed; + +static void found_v1_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n", + hdr->v1.buffer_addr, hdr->v1.start, + hdr->v1.end, hdr->v1.num_chars); + + memconsole_setup(phys_to_virt(hdr->v1.buffer_addr), hdr->v1.num_chars); +} + +static void found_v2_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n", + hdr->v2.buffer_addr, hdr->v2.start, + hdr->v2.end, hdr->v2.num_bytes); + + memconsole_setup(phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start), + hdr->v2.end - hdr->v2.start); +} + +/* + * Search through the EBDA for the BIOS Memory Console, and + * set the global variables to point to it. Return true if found. + */ +static bool memconsole_ebda_init(void) +{ + unsigned int address; + size_t length, cur; + + address = get_bios_ebda(); + if (!address) { + pr_info("memconsole: BIOS EBDA non-existent.\n"); + return false; + } + + /* EBDA length is byte 0 of EBDA (in KB) */ + length = *(u8 *)phys_to_virt(address); + length <<= 10; /* convert to bytes */ + + /* + * Search through EBDA for BIOS memory console structure + * note: signature is not necessarily dword-aligned + */ + for (cur = 0; cur < length; cur++) { + struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); + + /* memconsole v1 */ + if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { + found_v1_header(hdr); + return true; + } + + /* memconsole v2 */ + if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { + found_v2_header(hdr); + return true; + } + } + + pr_info("memconsole: BIOS console EBDA structure not found!\n"); + return false; +} + +static struct dmi_system_id memconsole_dmi_table[] __initdata = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); + +static bool __init memconsole_find(void) +{ + if (!dmi_check_system(memconsole_dmi_table)) + return false; + + return memconsole_ebda_init(); +} + +static int __init memconsole_x86_init(void) +{ + if (!memconsole_find()) + return -ENODEV; + + return memconsole_sysfs_init(); +} + +static void __exit memconsole_x86_exit(void) +{ + memconsole_exit(); +} + +module_init(memconsole_x86_init); +module_exit(memconsole_x86_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c index 2f569aaed4c7..94e200ddb4fa 100644 --- a/drivers/firmware/google/memconsole.c +++ b/drivers/firmware/google/memconsole.c @@ -1,66 +1,36 @@ /* * memconsole.c * - * Infrastructure for importing the BIOS memory based console - * into the kernel log ringbuffer. + * Architecture-independent parts of the memory based BIOS console. * - * Copyright 2010 Google Inc. All rights reserved. + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 #include #include -#include -#include -#include -#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE -#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) +#include "memconsole.h" -struct biosmemcon_ebda { - u32 signature; - union { - struct { - u8 enabled; - u32 buffer_addr; - u16 start; - u16 end; - u16 num_chars; - u8 wrapped; - } __packed v1; - struct { - u32 buffer_addr; - /* Misdocumented as number of pages! */ - u16 num_bytes; - u16 start; - u16 end; - } __packed v2; - }; -} __packed; - -static u32 memconsole_baseaddr; +static char *memconsole_baseaddr; static size_t memconsole_length; static ssize_t memconsole_read(struct file *filp, struct kobject *kobp, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { - char *memconsole; - ssize_t ret; - - memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length); - if (!memconsole) { - pr_err("memconsole: ioremap_cache failed\n"); - return -ENOMEM; - } - ret = memory_read_from_buffer(buf, count, &pos, memconsole, - memconsole_length); - iounmap(memconsole); - return ret; + return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, + memconsole_length); } static struct bin_attribute memconsole_bin_attr = { @@ -68,104 +38,25 @@ static struct bin_attribute memconsole_bin_attr = { .read = memconsole_read, }; - -static void __init found_v1_header(struct biosmemcon_ebda *hdr) -{ - pr_info("BIOS console v1 EBDA structure found at %p\n", hdr); - pr_info("BIOS console buffer at 0x%.8x, " - "start = %d, end = %d, num = %d\n", - hdr->v1.buffer_addr, hdr->v1.start, - hdr->v1.end, hdr->v1.num_chars); - - memconsole_length = hdr->v1.num_chars; - memconsole_baseaddr = hdr->v1.buffer_addr; -} - -static void __init found_v2_header(struct biosmemcon_ebda *hdr) -{ - pr_info("BIOS console v2 EBDA structure found at %p\n", hdr); - pr_info("BIOS console buffer at 0x%.8x, " - "start = %d, end = %d, num_bytes = %d\n", - hdr->v2.buffer_addr, hdr->v2.start, - hdr->v2.end, hdr->v2.num_bytes); - - memconsole_length = hdr->v2.end - hdr->v2.start; - memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start; -} - -/* - * Search through the EBDA for the BIOS Memory Console, and - * set the global variables to point to it. Return true if found. - */ -static bool __init found_memconsole(void) +void memconsole_setup(void *baseaddr, size_t length) { - unsigned int address; - size_t length, cur; - - address = get_bios_ebda(); - if (!address) { - pr_info("BIOS EBDA non-existent.\n"); - return false; - } - - /* EBDA length is byte 0 of EBDA (in KB) */ - length = *(u8 *)phys_to_virt(address); - length <<= 10; /* convert to bytes */ - - /* - * Search through EBDA for BIOS memory console structure - * note: signature is not necessarily dword-aligned - */ - for (cur = 0; cur < length; cur++) { - struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); - - /* memconsole v1 */ - if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { - found_v1_header(hdr); - return true; - } - - /* memconsole v2 */ - if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { - found_v2_header(hdr); - return true; - } - } - - pr_info("BIOS console EBDA structure not found!\n"); - return false; + memconsole_baseaddr = baseaddr; + memconsole_length = length; } +EXPORT_SYMBOL(memconsole_setup); -static struct dmi_system_id memconsole_dmi_table[] __initdata = { - { - .ident = "Google Board", - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), - }, - }, - {} -}; -MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); - -static int __init memconsole_init(void) +int memconsole_sysfs_init(void) { - if (!dmi_check_system(memconsole_dmi_table)) - return -ENODEV; - - if (!found_memconsole()) - return -ENODEV; - memconsole_bin_attr.size = memconsole_length; return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); } +EXPORT_SYMBOL(memconsole_sysfs_init); -static void __exit memconsole_exit(void) +void memconsole_exit(void) { sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr); } - -module_init(memconsole_init); -module_exit(memconsole_exit); +EXPORT_SYMBOL(memconsole_exit); MODULE_AUTHOR("Google, Inc."); MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h new file mode 100644 index 000000000000..190fc03a51ae --- /dev/null +++ b/drivers/firmware/google/memconsole.h @@ -0,0 +1,43 @@ +/* + * memconsole.h + * + * Internal headers of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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. + */ + +#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H +#define __FIRMWARE_GOOGLE_MEMCONSOLE_H + +/* + * memconsole_setup + * + * Initialize the memory console from raw (virtual) base + * address and length. + */ +void memconsole_setup(void *baseaddr, size_t length); + +/* + * memconsole_sysfs_init + * + * Update memory console length and create binary file + * for firmware object. + */ +int memconsole_sysfs_init(void); + +/* memconsole_exit + * + * Unmap the console buffer. + */ +void memconsole_exit(void); + +#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */ -- cgit v1.2.3-70-g09d2 From d384d6f43d1ec3f1225ab0275fd592c5980bd830 Mon Sep 17 00:00:00 2001 From: Thierry Escande Date: Tue, 28 Mar 2017 18:11:27 +0200 Subject: firmware: google memconsole: Add coreboot support Coreboot (http://www.coreboot.org) allows to save the firmware console output in a memory buffer. With this patch, the address of this memory buffer is obtained from coreboot tables on x86 chromebook devices declaring an ACPI device with name matching GOOGCB00 or BOOT0000. If the memconsole-coreboot driver is able to find the coreboot table, the memconsole driver sets the cbmem_console address and initializes the memconsole sysfs entries. The coreboot_table-acpi driver is responsible for setting the address of the coreboot table header when probed. If this address is not yet set when memconsole-coreboot is probed, then the probe is deferred by returning -EPROBE_DEFER. This patch is a rework/split/merge of patches from the chromeos v4.4 kernel tree originally authored by: Vadim Bendebury Wei-Ning Huang Yuji Sasaki Duncan Laurie Julius Werner Brian Norris Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/Kconfig | 25 +++++- drivers/firmware/google/Makefile | 3 + drivers/firmware/google/coreboot_table-acpi.c | 88 +++++++++++++++++++++ drivers/firmware/google/coreboot_table.c | 94 ++++++++++++++++++++++ drivers/firmware/google/coreboot_table.h | 50 ++++++++++++ drivers/firmware/google/memconsole-coreboot.c | 109 ++++++++++++++++++++++++++ 6 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/google/coreboot_table-acpi.c create mode 100644 drivers/firmware/google/coreboot_table.c create mode 100644 drivers/firmware/google/coreboot_table.h create mode 100644 drivers/firmware/google/memconsole-coreboot.c diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 27a0658b8523..840bd62e3b1c 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -19,9 +19,23 @@ config GOOGLE_SMI clearing the EFI event log and reading and writing NVRAM variables. +config GOOGLE_COREBOOT_TABLE + tristate + depends on GOOGLE_COREBOOT_TABLE_ACPI + +config GOOGLE_COREBOOT_TABLE_ACPI + tristate "Coreboot Table Access - ACPI" + depends on ACPI + select GOOGLE_COREBOOT_TABLE + help + This option enables the coreboot_table module, which provides other + firmware modules to access to the coreboot table. The coreboot table + pointer is accessed through the ACPI "GOOGCB00" object. + If unsure say N. + config GOOGLE_MEMCONSOLE tristate - depends on GOOGLE_MEMCONSOLE_X86_LEGACY + depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT config GOOGLE_MEMCONSOLE_X86_LEGACY tristate "Firmware Memory Console - X86 Legacy support" @@ -32,4 +46,13 @@ config GOOGLE_MEMCONSOLE_X86_LEGACY the EBDA on Google servers. If found, this log is exported to userland in the file /sys/firmware/log. +config GOOGLE_MEMCONSOLE_COREBOOT + tristate "Firmware Memory Console" + depends on GOOGLE_COREBOOT_TABLE + select GOOGLE_MEMCONSOLE + help + This option enables the kernel to search for a firmware log in + the coreboot table. If found, this log is exported to userland + in the file /sys/firmware/log. + endif # GOOGLE_FIRMWARE diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index 10683ef8d2cd..662a83edb03c 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -1,4 +1,7 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o diff --git a/drivers/firmware/google/coreboot_table-acpi.c b/drivers/firmware/google/coreboot_table-acpi.c new file mode 100644 index 000000000000..fb98db2d20e2 --- /dev/null +++ b/drivers/firmware/google/coreboot_table-acpi.c @@ -0,0 +1,88 @@ +/* + * coreboot_table-acpi.c + * + * Using ACPI to locate Coreboot table and provide coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 +#include +#include +#include + +#include "coreboot_table.h" + +static int coreboot_table_acpi_probe(struct platform_device *pdev) +{ + phys_addr_t phyaddr; + resource_size_t len; + struct coreboot_table_header __iomem *header = NULL; + struct resource *res; + void __iomem *ptr = NULL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + len = resource_size(res); + if (!res->start || !len) + return -EINVAL; + + phyaddr = res->start; + header = ioremap_cache(phyaddr, sizeof(*header)); + if (header == NULL) + return -ENOMEM; + + ptr = ioremap_cache(phyaddr, + header->header_bytes + header->table_bytes); + iounmap(header); + if (!ptr) + return -ENOMEM; + + return coreboot_table_init(ptr); +} + +static int coreboot_table_acpi_remove(struct platform_device *pdev) +{ + return coreboot_table_exit(); +} + +static const struct acpi_device_id cros_coreboot_acpi_match[] = { + { "GOOGCB00", 0 }, + { "BOOT0000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match); + +static struct platform_driver coreboot_table_acpi_driver = { + .probe = coreboot_table_acpi_probe, + .remove = coreboot_table_acpi_remove, + .driver = { + .name = "coreboot_table_acpi", + .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match), + }, +}; + +static int __init coreboot_table_acpi_init(void) +{ + return platform_driver_register(&coreboot_table_acpi_driver); +} + +module_init(coreboot_table_acpi_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c new file mode 100644 index 000000000000..0019d3ec18dd --- /dev/null +++ b/drivers/firmware/google/coreboot_table.c @@ -0,0 +1,94 @@ +/* + * coreboot_table.c + * + * Module providing coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 + +#include "coreboot_table.h" + +struct coreboot_table_entry { + u32 tag; + u32 size; +}; + +static struct coreboot_table_header __iomem *ptr_header; + +/* + * This function parses the coreboot table for an entry that contains the base + * address of the given entry tag. The coreboot table consists of a header + * directly followed by a number of small, variable-sized entries, which each + * contain an identifying tag and their length as the first two fields. + */ +int coreboot_table_find(int tag, void *data, size_t data_size) +{ + struct coreboot_table_header header; + struct coreboot_table_entry entry; + void *ptr_entry; + int i; + + if (!ptr_header) + return -EPROBE_DEFER; + + memcpy_fromio(&header, ptr_header, sizeof(header)); + + if (strncmp(header.signature, "LBIO", sizeof(header.signature))) { + pr_warn("coreboot_table: coreboot table missing or corrupt!\n"); + return -ENODEV; + } + + ptr_entry = (void *)ptr_header + header.header_bytes; + + for (i = 0; i < header.table_entries; i++) { + memcpy_fromio(&entry, ptr_entry, sizeof(entry)); + if (entry.tag == tag) { + if (data_size < entry.size) + return -EINVAL; + + memcpy_fromio(data, ptr_entry, entry.size); + + return 0; + } + + ptr_entry += entry.size; + } + + return -ENOENT; +} +EXPORT_SYMBOL(coreboot_table_find); + +int coreboot_table_init(void __iomem *ptr) +{ + ptr_header = ptr; + + return 0; +} +EXPORT_SYMBOL(coreboot_table_init); + +int coreboot_table_exit(void) +{ + if (ptr_header) + iounmap(ptr_header); + + return 0; +} +EXPORT_SYMBOL(coreboot_table_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h new file mode 100644 index 000000000000..6eff1ae0c5d3 --- /dev/null +++ b/drivers/firmware/google/coreboot_table.h @@ -0,0 +1,50 @@ +/* + * coreboot_table.h + * + * Internal header for coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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. + */ + +#ifndef __COREBOOT_TABLE_H +#define __COREBOOT_TABLE_H + +#include + +/* List of coreboot entry structures that is used */ +struct lb_cbmem_ref { + uint32_t tag; + uint32_t size; + + uint64_t cbmem_addr; +}; + +/* Coreboot table header structure */ +struct coreboot_table_header { + char signature[4]; + u32 header_bytes; + u32 header_checksum; + u32 table_bytes; + u32 table_checksum; + u32 table_entries; +}; + +/* Retrieve coreboot table entry with tag *tag* and copy it to data */ +int coreboot_table_find(int tag, void *data, size_t data_size); + +/* Initialize coreboot table module given a pointer to iomem */ +int coreboot_table_init(void __iomem *ptr); + +/* Cleanup coreboot table module */ +int coreboot_table_exit(void); + +#endif /* __COREBOOT_TABLE_H */ diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c new file mode 100644 index 000000000000..21210144def7 --- /dev/null +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -0,0 +1,109 @@ +/* + * memconsole-coreboot.c + * + * Memory based BIOS console accessed through coreboot table. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 "memconsole.h" +#include "coreboot_table.h" + +#define CB_TAG_CBMEM_CONSOLE 0x17 + +/* CBMEM firmware console log descriptor. */ +struct cbmem_cons { + u32 buffer_size; + u32 buffer_cursor; + u8 buffer_body[0]; +} __packed; + +static struct cbmem_cons __iomem *cbmem_console; + +static int memconsole_coreboot_init(phys_addr_t physaddr) +{ + struct cbmem_cons __iomem *tmp_cbmc; + + tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB); + + if (!tmp_cbmc) + return -ENOMEM; + + cbmem_console = memremap(physaddr, + tmp_cbmc->buffer_size + sizeof(*cbmem_console), + MEMREMAP_WB); + memunmap(tmp_cbmc); + + if (!cbmem_console) + return -ENOMEM; + + memconsole_setup(cbmem_console->buffer_body, + min(cbmem_console->buffer_cursor, cbmem_console->buffer_size)); + + return 0; +} + +static int memconsole_probe(struct platform_device *pdev) +{ + int ret; + struct lb_cbmem_ref entry; + + ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry)); + if (ret) + return ret; + + ret = memconsole_coreboot_init(entry.cbmem_addr); + if (ret) + return ret; + + return memconsole_sysfs_init(); +} + +static int memconsole_remove(struct platform_device *pdev) +{ + memconsole_exit(); + + if (cbmem_console) + memunmap(cbmem_console); + + return 0; +} + +static struct platform_driver memconsole_driver = { + .probe = memconsole_probe, + .remove = memconsole_remove, + .driver = { + .name = "memconsole", + }, +}; + +static int __init platform_memconsole_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("memconsole", -1, NULL, 0); + if (pdev == NULL) + return -ENODEV; + + platform_driver_register(&memconsole_driver); + + return 0; +} + +module_init(platform_memconsole_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From 88cfb1eefca9a403a76dc3e151adb7565b10e77d Mon Sep 17 00:00:00 2001 From: Julius Werner Date: Tue, 28 Mar 2017 18:11:28 +0200 Subject: firmware: Add coreboot device tree binding documentation This patch adds documentation describing a device tree binding for the coreboot firmware. It is meant to be dynamically added during boot and contains address definitions for the coreboot table (a list of variable-sized descriptors providing information about various compile- and run-time generated firmware parameters) and the CBMEM area (the structure containing most run-time resident memory regions set up by coreboot). These definitions allow kernel drivers to easily access data contained in and pointed to by these regions (such as coreboot's in-memory log). (An example implementation can be seen in the following patch) Signed-off-by: Julius Werner Signed-off-by: Thierry Escande Acked-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/firmware/coreboot.txt | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Documentation/devicetree/bindings/firmware/coreboot.txt diff --git a/Documentation/devicetree/bindings/firmware/coreboot.txt b/Documentation/devicetree/bindings/firmware/coreboot.txt new file mode 100644 index 000000000000..4c955703cea8 --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/coreboot.txt @@ -0,0 +1,33 @@ +COREBOOT firmware information + +The device tree node to communicate the location of coreboot's memory-resident +bookkeeping structures to the kernel. Since coreboot itself cannot boot a +device-tree-based kernel (yet), this node needs to be inserted by a +second-stage bootloader (a coreboot "payload"). + +Required properties: + - compatible: Should be "coreboot" + - reg: Address and length of the following two memory regions, in order: + 1.) The coreboot table. This is a list of variable-sized descriptors + that contain various compile- and run-time generated firmware + parameters. It is identified by the magic string "LBIO" in its first + four bytes. + See coreboot's src/commonlib/include/commonlib/coreboot_tables.h for + details. + 2.) The CBMEM area. This is a downward-growing memory region used by + coreboot to dynamically allocate data structures that remain resident. + It may or may not include the coreboot table as one of its members. It + is identified by a root node descriptor with the magic number + 0xc0389481 that resides in the topmost 8 bytes of the area. + See coreboot's src/include/imd.h for details. + +Example: + firmware { + ranges; + + coreboot { + compatible = "coreboot"; + reg = <0xfdfea000 0x264>, + <0xfdfea000 0x16000>; + } + }; -- cgit v1.2.3-70-g09d2 From a1d6f9cfc7c6f55ae65430c2fd0eb2bae69dc246 Mon Sep 17 00:00:00 2001 From: Thierry Escande Date: Tue, 28 Mar 2017 18:11:29 +0200 Subject: firmware: google memconsole: Add ARM/ARM64 support This patch expands the Google firmware memory console driver to also work on certain tree based platforms running coreboot, such as ARM/ARM64 Chromebooks. This patch now adds another path to find the coreboot table through the device tree. In order to find that, a second level bootloader must have installed the 'coreboot' compatible device tree node that describes its base address and size. This patch is a rework/split/merge of patches from the chromeos v4.4 kernel tree originally authored by: Wei-Ning Huang Julius Werner Brian Norris Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/Kconfig | 15 ++++-- drivers/firmware/google/Makefile | 1 + drivers/firmware/google/coreboot_table-of.c | 82 +++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 drivers/firmware/google/coreboot_table-of.c diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 840bd62e3b1c..00000e0376b9 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -1,6 +1,5 @@ menuconfig GOOGLE_FIRMWARE bool "Google Firmware Drivers" - depends on X86 default n help These firmware drivers are used by Google's servers. They are @@ -11,7 +10,7 @@ if GOOGLE_FIRMWARE config GOOGLE_SMI tristate "SMI interface for Google platforms" - depends on ACPI && DMI && EFI + depends on X86 && ACPI && DMI && EFI select EFI_VARS help Say Y here if you want to enable SMI callbacks for Google @@ -21,7 +20,7 @@ config GOOGLE_SMI config GOOGLE_COREBOOT_TABLE tristate - depends on GOOGLE_COREBOOT_TABLE_ACPI + depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF config GOOGLE_COREBOOT_TABLE_ACPI tristate "Coreboot Table Access - ACPI" @@ -33,6 +32,16 @@ config GOOGLE_COREBOOT_TABLE_ACPI pointer is accessed through the ACPI "GOOGCB00" object. If unsure say N. +config GOOGLE_COREBOOT_TABLE_OF + tristate "Coreboot Table Access - Device Tree" + depends on OF + select GOOGLE_COREBOOT_TABLE + help + This option enable the coreboot_table module, which provide other + firmware modules to access coreboot table. The coreboot table pointer + is accessed through the device tree node /firmware/coreboot. + If unsure say N. + config GOOGLE_MEMCONSOLE tristate depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index 662a83edb03c..bb952c686d3b 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o diff --git a/drivers/firmware/google/coreboot_table-of.c b/drivers/firmware/google/coreboot_table-of.c new file mode 100644 index 000000000000..727acdc83e83 --- /dev/null +++ b/drivers/firmware/google/coreboot_table-of.c @@ -0,0 +1,82 @@ +/* + * coreboot_table-of.c + * + * Coreboot table access through open firmware. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 +#include + +#include "coreboot_table.h" + +static int coreboot_table_of_probe(struct platform_device *pdev) +{ + struct device_node *fw_dn = pdev->dev.of_node; + void __iomem *ptr; + + ptr = of_iomap(fw_dn, 0); + of_node_put(fw_dn); + if (!ptr) + return -ENOMEM; + + return coreboot_table_init(ptr); +} + +static int coreboot_table_of_remove(struct platform_device *pdev) +{ + return coreboot_table_exit(); +} + +static const struct of_device_id coreboot_of_match[] = { + { .compatible = "coreboot" }, + {}, +}; + +static struct platform_driver coreboot_table_of_driver = { + .probe = coreboot_table_of_probe, + .remove = coreboot_table_of_remove, + .driver = { + .name = "coreboot_table_of", + .of_match_table = coreboot_of_match, + }, +}; + +static int __init platform_coreboot_table_of_init(void) +{ + struct platform_device *pdev; + struct device_node *of_node; + + /* Limit device creation to the presence of /firmware/coreboot node */ + of_node = of_find_node_by_path("/firmware/coreboot"); + if (!of_node) + return -ENODEV; + + if (!of_match_node(coreboot_of_match, of_node)) + return -ENODEV; + + pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL); + if (!pdev) + return -ENODEV; + + return platform_driver_register(&coreboot_table_of_driver); +} + +module_init(platform_coreboot_table_of_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From a33fd4c27b3ad11c66bdadc5fe6075297ca87a6d Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Tue, 28 Mar 2017 17:16:53 -0700 Subject: Drivers: hv: Issue explicit EOI when autoeoi is not enabled When auto EOI is not enabled; issue an explicit EOI for hyper-v interrupts. Fixes: 6c248aad81c8 ("Drivers: hv: Base autoeoi enablement based on hypervisor hints") Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/mshyperv.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index b5375b9497b3..04cb8d34ccb8 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -49,6 +49,9 @@ void hyperv_vector_handler(struct pt_regs *regs) if (vmbus_handler) vmbus_handler(); + if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED) + ack_APIC_irq(); + exiting_irq(); set_irq_regs(old_regs); } -- cgit v1.2.3-70-g09d2 From 8b932edfb8788862f10434c1018ad8a6078e0115 Mon Sep 17 00:00:00 2001 From: Shile Zhang Date: Fri, 24 Mar 2017 05:41:04 +0800 Subject: hangcheck-timer: Fix typo in comment Fix the typo "alloted" -> "allotted" in comment. Signed-off-by: Shile Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/char/hangcheck-timer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/hangcheck-timer.c b/drivers/char/hangcheck-timer.c index 4f337375252e..5406b90bf626 100644 --- a/drivers/char/hangcheck-timer.c +++ b/drivers/char/hangcheck-timer.c @@ -32,7 +32,7 @@ * timer and 180 seconds for the margin of error. IOW, a timer is set * for 60 seconds. When the timer fires, the callback checks the * actual duration that the timer waited. If the duration exceeds the - * alloted time and margin (here 60 + 180, or 240 seconds), the machine + * allotted time and margin (here 60 + 180, or 240 seconds), the machine * is restarted. A healthy machine will have the duration match the * expected timeout very closely. */ -- cgit v1.2.3-70-g09d2 From 171058fb0883247b3a484a542b5dc89753c57cb5 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Thu, 16 Mar 2017 14:50:08 +0100 Subject: uio: Allow handling of non page-aligned memory regions Since commit b65502879556 ("uio: we cannot mmap unaligned page contents") addresses and sizes of UIO memory regions must be page-aligned. If the address in the BAR register is not page-aligned (which is the case of the mf264 card), the mentioned commit forces the UIO driver to round the address down to the page size. Then, there is no easy way for user-space to learn the offset of the actual memory region within the page, because the offset seen in /sys/class/uio/uio?/maps/map?/offset is calculated from the rounded address and thus it is always zero. Fix that problem by including the offset in struct uio_mem. UIO drivers can set this field and userspace can read its value from /sys/class/uio/uio?/maps/map?/offset. The following commits update the uio_mf264 driver to set this new offs field. Drivers for hardware with page-aligned BARs need not to be modified provided that they initialize struct uio_info (which contains uio_mem) with zeros. Signed-off-by: Michal Sojka Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 2 +- include/linux/uio_driver.h | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 60ce7fd54e89..1c196f87e9d9 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -66,7 +66,7 @@ static ssize_t map_size_show(struct uio_mem *mem, char *buf) static ssize_t map_offset_show(struct uio_mem *mem, char *buf) { - return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr & ~PAGE_MASK); + return sprintf(buf, "0x%llx\n", (unsigned long long)mem->offs); } struct map_sysfs_entry { diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h index 32c0e83d6239..3c85c81b0027 100644 --- a/include/linux/uio_driver.h +++ b/include/linux/uio_driver.h @@ -23,11 +23,13 @@ struct uio_map; /** * struct uio_mem - description of a UIO memory region * @name: name of the memory region for identification - * @addr: address of the device's memory (phys_addr is used since - * addr can be logical, virtual, or physical & phys_addr_t - * should always be large enough to handle any of the - * address types) - * @size: size of IO + * @addr: address of the device's memory rounded to page + * size (phys_addr is used since addr can be + * logical, virtual, or physical & phys_addr_t + * should always be large enough to handle any of + * the address types) + * @offs: offset of device memory within the page + * @size: size of IO (multiple of page size) * @memtype: type of memory addr points to * @internal_addr: ioremap-ped version of addr, for driver internal use * @map: for use by the UIO core only. @@ -35,6 +37,7 @@ struct uio_map; struct uio_mem { const char *name; phys_addr_t addr; + unsigned long offs; resource_size_t size; int memtype; void __iomem *internal_addr; -- cgit v1.2.3-70-g09d2 From a15d2ddba9ee94b252d2737419bff285d6b0e239 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Thu, 16 Mar 2017 14:50:09 +0100 Subject: uio_mf624: Refactor memory info initialization No functional changes. Move initialization of struct uio_mem to a function. This will allow the next commit to change the initialization code at a single place rather that at three different places. Signed-off-by: Michal Sojka Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_mf624.c | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/drivers/uio/uio_mf624.c b/drivers/uio/uio_mf624.c index d1f95a1567bb..8f30fa1af2ab 100644 --- a/drivers/uio/uio_mf624.c +++ b/drivers/uio/uio_mf624.c @@ -127,6 +127,20 @@ static int mf624_irqcontrol(struct uio_info *info, s32 irq_on) return 0; } +static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name) +{ + mem->name = name; + mem->addr = pci_resource_start(dev, bar); + if (!mem->addr) + return -ENODEV; + mem->size = pci_resource_len(dev, bar); + mem->memtype = UIO_MEM_PHYS; + mem->internal_addr = pci_ioremap_bar(dev, bar); + if (!mem->internal_addr) + return -ENODEV; + return 0; +} + static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct uio_info *info; @@ -147,37 +161,15 @@ static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) /* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */ /* BAR0 */ - info->mem[0].name = "PCI chipset, interrupts, status " - "bits, special functions"; - info->mem[0].addr = pci_resource_start(dev, 0); - if (!info->mem[0].addr) + if (mf624_setup_mem(dev, 0, &info->mem[0], "PCI chipset, interrupts, status " + "bits, special functions")) goto out_release; - info->mem[0].size = pci_resource_len(dev, 0); - info->mem[0].memtype = UIO_MEM_PHYS; - info->mem[0].internal_addr = pci_ioremap_bar(dev, 0); - if (!info->mem[0].internal_addr) - goto out_release; - /* BAR2 */ - info->mem[1].name = "ADC, DAC, DIO"; - info->mem[1].addr = pci_resource_start(dev, 2); - if (!info->mem[1].addr) - goto out_unmap0; - info->mem[1].size = pci_resource_len(dev, 2); - info->mem[1].memtype = UIO_MEM_PHYS; - info->mem[1].internal_addr = pci_ioremap_bar(dev, 2); - if (!info->mem[1].internal_addr) + if (mf624_setup_mem(dev, 2, &info->mem[1], "ADC, DAC, DIO")) goto out_unmap0; /* BAR4 */ - info->mem[2].name = "Counter/timer chip"; - info->mem[2].addr = pci_resource_start(dev, 4); - if (!info->mem[2].addr) - goto out_unmap1; - info->mem[2].size = pci_resource_len(dev, 4); - info->mem[2].memtype = UIO_MEM_PHYS; - info->mem[2].internal_addr = pci_ioremap_bar(dev, 4); - if (!info->mem[2].internal_addr) + if (mf624_setup_mem(dev, 4, &info->mem[2], "Counter/timer chip")) goto out_unmap1; info->irq = dev->irq; -- cgit v1.2.3-70-g09d2 From 270579d95f820e93b39dafd4382530b3beb744e4 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Thu, 16 Mar 2017 14:50:10 +0100 Subject: uio_mf624: Align memory regions to page size and set correct offsets mf624 card has its registers not aligned to pages. Since commit b65502879556 ("uio: we cannot mmap unaligned page contents") mmap()ing mf624 registers fails, because now the uio drivers must set uio_mem->addr to be page-aligned. We align the address here and set the newly introduced offs field to the offset of the mf264 registers within the page so that userspace can find the address of the mmap()ed register by reading /sys/class/uio/uio?/maps/map?/offset. Tested with real mf624 card. Signed-off-by: Michal Sojka Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_mf624.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/uio/uio_mf624.c b/drivers/uio/uio_mf624.c index 8f30fa1af2ab..35187c052e8c 100644 --- a/drivers/uio/uio_mf624.c +++ b/drivers/uio/uio_mf624.c @@ -129,11 +129,15 @@ static int mf624_irqcontrol(struct uio_info *info, s32 irq_on) static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name) { + resource_size_t start = pci_resource_start(dev, bar); + resource_size_t len = pci_resource_len(dev, bar); + mem->name = name; - mem->addr = pci_resource_start(dev, bar); + mem->addr = start & PAGE_MASK; + mem->offs = start & ~PAGE_MASK; if (!mem->addr) return -ENODEV; - mem->size = pci_resource_len(dev, bar); + mem->size = ((start & ~PAGE_MASK) + len + PAGE_SIZE - 1) & PAGE_MASK; mem->memtype = UIO_MEM_PHYS; mem->internal_addr = pci_ioremap_bar(dev, bar); if (!mem->internal_addr) -- cgit v1.2.3-70-g09d2 From b04e08549910626cda1dbe68ed600afa3ce2af81 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Thu, 16 Mar 2017 10:19:53 -0500 Subject: w1: Use kernel common min() implementation Signed-off-by: Andrew F. Davis Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1_netlink.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index 49e520ca79c5..18374cad3ace 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -23,8 +23,6 @@ #if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) - /* Bundle together everything required to process a request in one memory * allocation. */ @@ -598,7 +596,7 @@ static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) sizeof(struct w1_netlink_msg) + sizeof(struct w1_netlink_cmd)); } - reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size); + reply_size = min(CONNECTOR_MAX_MSG_SIZE, reply_size); /* allocate space for the block, a copy of the original message, * one node per cmd to point into the original message, -- cgit v1.2.3-70-g09d2 From 91bd2855edbc6a941bf0b16990fbf2894c41c388 Mon Sep 17 00:00:00 2001 From: "Andrew F. Davis" Date: Thu, 16 Mar 2017 10:19:54 -0500 Subject: w1: Remove unneeded use of assert() and remove w1_log.h The only use of assert() is in matrox_w1.c and is used to check the input to probe() from the PCI subsystem for NULL values, these are guaranteed to be populated and no other PCI driver makes this check, remove this. As this was the only definition in w1_log.h, remove this also. Signed-off-by: Andrew F. Davis Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/matrox_w1.c | 6 ------ drivers/w1/w1.c | 1 - drivers/w1/w1_int.c | 1 - drivers/w1/w1_io.c | 1 - drivers/w1/w1_log.h | 31 ------------------------------- drivers/w1/w1_netlink.c | 1 - 6 files changed, 41 deletions(-) delete mode 100644 drivers/w1/w1_log.h diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c index 3749db8b4396..97a676bf5989 100644 --- a/drivers/w1/masters/matrox_w1.c +++ b/drivers/w1/masters/matrox_w1.c @@ -36,7 +36,6 @@ #include "../w1.h" #include "../w1_int.h" -#include "../w1_log.h" MODULE_LICENSE("GPL"); MODULE_AUTHOR("Evgeniy Polyakov "); @@ -157,9 +156,6 @@ static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent struct matrox_device *dev; int err; - assert(pdev != NULL); - assert(ent != NULL); - if (pdev->vendor != PCI_VENDOR_ID_MATROX || pdev->device != PCI_DEVICE_ID_MATROX_G400) return -ENODEV; @@ -224,8 +220,6 @@ static void matrox_w1_remove(struct pci_dev *pdev) { struct matrox_device *dev = pci_get_drvdata(pdev); - assert(dev != NULL); - if (dev->found) { w1_remove_master_device(dev->bus_master); iounmap(dev->virt_addr); diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 90a3d9338fd2..8511d1685db9 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -29,7 +29,6 @@ #include #include "w1.h" -#include "w1_log.h" #include "w1_int.h" #include "w1_family.h" #include "w1_netlink.h" diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 2cae7b29bb5f..1072a2e620bb 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -22,7 +22,6 @@ #include #include "w1.h" -#include "w1_log.h" #include "w1_netlink.h" #include "w1_int.h" diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c index de8bebc27896..1134e6b1eb02 100644 --- a/drivers/w1/w1_io.c +++ b/drivers/w1/w1_io.c @@ -19,7 +19,6 @@ #include #include "w1.h" -#include "w1_log.h" static int w1_delay_parm = 1; module_param_named(delay_coef, w1_delay_parm, int, 0); diff --git a/drivers/w1/w1_log.h b/drivers/w1/w1_log.h deleted file mode 100644 index dd1422b6afbb..000000000000 --- a/drivers/w1/w1_log.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2004 Evgeniy Polyakov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - */ - -#ifndef __W1_LOG_H -#define __W1_LOG_H - -#define DEBUG - -#ifdef W1_DEBUG -# define assert(expr) do {} while (0) -#else -# define assert(expr) \ - if(unlikely(!(expr))) { \ - pr_err("Assertion failed! %s,%s,%s,line=%d\n", \ - #expr, __FILE__, __func__, __LINE__); \ - } -#endif - -#endif /* __W1_LOG_H */ - diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index 18374cad3ace..027950f997d1 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -18,7 +18,6 @@ #include #include "w1.h" -#include "w1_log.h" #include "w1_netlink.h" #if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) -- cgit v1.2.3-70-g09d2 From d2ce8d6fee4428f80005c71e1529def3edcfe259 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 21 Mar 2017 10:50:47 -0300 Subject: misc: tsl2550: Add OF device ID table The driver doesn't have a struct of_device_id table but supported devices are registered via Device Trees. This is working on the assumption that a I2C device registered via OF will always match a legacy I2C device ID and that the MODALIAS reported will always be of the form i2c:. But this could change in the future so the correct approach is to have an OF device ID table if the devices are registered via OF. Signed-off-by: Javier Martinez Canillas Acked-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/misc/tsl2550.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c index 87a13374fdc0..adf46072cb37 100644 --- a/drivers/misc/tsl2550.c +++ b/drivers/misc/tsl2550.c @@ -443,9 +443,16 @@ static const struct i2c_device_id tsl2550_id[] = { }; MODULE_DEVICE_TABLE(i2c, tsl2550_id); +static const struct of_device_id tsl2550_of_match[] = { + { .compatible = "taos,tsl2550" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2550_of_match); + static struct i2c_driver tsl2550_driver = { .driver = { .name = TSL2550_DRV_NAME, + .of_match_table = tsl2550_of_match, .pm = TSL2550_PM_OPS, }, .probe = tsl2550_probe, -- cgit v1.2.3-70-g09d2 From 5ef168545798cf50c5b67c0ac8748eb60cff600b Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 21 Mar 2017 10:50:48 -0300 Subject: misc: ds1682: Add OF device ID table The driver doesn't have a struct of_device_id table but supported devices are registered via Device Trees. This is working on the assumption that a I2C device registered via OF will always match a legacy I2C device ID and that the MODALIAS reported will always be of the form i2c:. But this could change in the future so the correct approach is to have an OF device ID table if the devices are registered via OF. Signed-off-by: Javier Martinez Canillas Acked-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ds1682.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c index c7112276a039..28bb495f0cf1 100644 --- a/drivers/misc/ds1682.c +++ b/drivers/misc/ds1682.c @@ -227,9 +227,16 @@ static const struct i2c_device_id ds1682_id[] = { }; MODULE_DEVICE_TABLE(i2c, ds1682_id); +static const struct of_device_id ds1682_of_match[] = { + { .compatible = "dallas,ds1682", }, + {} +}; +MODULE_DEVICE_TABLE(of, ds1682_of_match); + static struct i2c_driver ds1682_driver = { .driver = { .name = "ds1682", + .of_match_table = ds1682_of_match, }, .probe = ds1682_probe, .remove = ds1682_remove, -- cgit v1.2.3-70-g09d2 From 615cdd7cb475ede36642c5af65e8e32122445370 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 21 Mar 2017 10:50:49 -0300 Subject: eeprom: idt_89hpesx: Add OF device ID table The driver doesn't have a struct of_device_id table but supported devices are registered via Device Trees. This is working on the assumption that a I2C device registered via OF will always match a legacy I2C device ID and that the MODALIAS reported will always be of the form i2c:. But this could change in the future so the correct approach is to have an OF device ID table if the devices are registered via OF. Signed-off-by: Javier Martinez Canillas Acked-by: Arnd Bergmann Acked-by: Serge Semin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/idt_89hpesx.c | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c index 4a22a1d99395..ab0df6a17690 100644 --- a/drivers/misc/eeprom/idt_89hpesx.c +++ b/drivers/misc/eeprom/idt_89hpesx.c @@ -1541,12 +1541,69 @@ static const struct i2c_device_id idt_ids[] = { }; MODULE_DEVICE_TABLE(i2c, idt_ids); +static const struct of_device_id idt_of_match[] = { + { .compatible = "idt,89hpes8nt2", }, + { .compatible = "idt,89hpes12nt3", }, + + { .compatible = "idt,89hpes24nt6ag2", }, + { .compatible = "idt,89hpes32nt8ag2", }, + { .compatible = "idt,89hpes32nt8bg2", }, + { .compatible = "idt,89hpes12nt12g2", }, + { .compatible = "idt,89hpes16nt16g2", }, + { .compatible = "idt,89hpes24nt24g2", }, + { .compatible = "idt,89hpes32nt24ag2", }, + { .compatible = "idt,89hpes32nt24bg2", }, + + { .compatible = "idt,89hpes12n3", }, + { .compatible = "idt,89hpes12n3a", }, + { .compatible = "idt,89hpes24n3", }, + { .compatible = "idt,89hpes24n3a", }, + + { .compatible = "idt,89hpes32h8", }, + { .compatible = "idt,89hpes32h8g2", }, + { .compatible = "idt,89hpes48h12", }, + { .compatible = "idt,89hpes48h12g2", }, + { .compatible = "idt,89hpes48h12ag2", }, + { .compatible = "idt,89hpes16h16", }, + { .compatible = "idt,89hpes22h16", }, + { .compatible = "idt,89hpes22h16g2", }, + { .compatible = "idt,89hpes34h16", }, + { .compatible = "idt,89hpes34h16g2", }, + { .compatible = "idt,89hpes64h16", }, + { .compatible = "idt,89hpes64h16g2", }, + { .compatible = "idt,89hpes64h16ag2", }, + + { .compatible = "idt,89hpes12t3g2", }, + { .compatible = "idt,89hpes24t3g2", }, + + { .compatible = "idt,89hpes16t4", }, + { .compatible = "idt,89hpes4t4g2", }, + { .compatible = "idt,89hpes10t4g2", }, + { .compatible = "idt,89hpes16t4g2", }, + { .compatible = "idt,89hpes16t4ag2", }, + { .compatible = "idt,89hpes5t5", }, + { .compatible = "idt,89hpes6t5", }, + { .compatible = "idt,89hpes8t5", }, + { .compatible = "idt,89hpes8t5a", }, + { .compatible = "idt,89hpes24t6", }, + { .compatible = "idt,89hpes6t6g2", }, + { .compatible = "idt,89hpes24t6g2", }, + { .compatible = "idt,89hpes16t7", }, + { .compatible = "idt,89hpes32t8", }, + { .compatible = "idt,89hpes32t8g2", }, + { .compatible = "idt,89hpes48t12", }, + { .compatible = "idt,89hpes48t12g2", }, + { }, +}; +MODULE_DEVICE_TABLE(of, idt_of_match); + /* * idt_driver - IDT 89HPESx driver structure */ static struct i2c_driver idt_driver = { .driver = { .name = IDT_NAME, + .of_match_table = idt_of_match, }, .probe = idt_probe, .remove = idt_remove, -- cgit v1.2.3-70-g09d2 From 9e18308a5dcc2250a271e598dfe0d917b5522475 Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Fri, 14 Apr 2017 14:15:09 -0700 Subject: misc: lkdtm: Add volatile to intentional NULL pointer reference Add a volatile qualifier where a NULL pointer is deliberately dereferenced to trigger a panic. Without the volatile qualifier clang will issue the following warning: "indirection of non-volatile null pointer will be deleted, not trap [-Wnull-dereference]" and replace the pointer reference with a __builtin_trap() (which generates a ud2 instruction on x86_64). Signed-off-by: Michael Davidson Signed-off-by: Matthias Kaehlcke Acked-by: Kees Cook Signed-off-by: Greg Kroah-Hartman --- drivers/misc/lkdtm_bugs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c index ed4f4c56c796..d9028ef50fbe 100644 --- a/drivers/misc/lkdtm_bugs.c +++ b/drivers/misc/lkdtm_bugs.c @@ -69,7 +69,7 @@ void lkdtm_WARNING(void) void lkdtm_EXCEPTION(void) { - *((int *) 0) = 0; + *((volatile int *) 0) = 0; } void lkdtm_LOOP(void) -- cgit v1.2.3-70-g09d2 From ad2ac9d5c5e0e5fa7e21575d3cd9d0227803ea99 Mon Sep 17 00:00:00 2001 From: Wei-Ning Huang Date: Wed, 12 Apr 2017 18:56:18 +0200 Subject: firmware: Google VPD: import lib_vpd source files This patch imports lib_vpd.h and vpd_decode.c from the Chromium Vital Product Data project. This library is used to parse VPD sections obtained from coreboot table entries describing Chromebook devices product data. Only the sections of type VPD_TYPE_STRING are decoded. The VPD string sections in the coreboot tables contain the type (1 byte set to 0x01 for strings), the key length, the key ascii array, the value length, and the value ascii array. The key and value arrays are not null terminated. Signed-off-by: Wei-Ning Huang Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/vpd_decode.c | 99 ++++++++++++++++++++++++++++++++++++ drivers/firmware/google/vpd_decode.h | 58 +++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 drivers/firmware/google/vpd_decode.c create mode 100644 drivers/firmware/google/vpd_decode.h diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/google/vpd_decode.c new file mode 100644 index 000000000000..943acaa8aa76 --- /dev/null +++ b/drivers/firmware/google/vpd_decode.c @@ -0,0 +1,99 @@ +/* + * vpd_decode.c + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 "vpd_decode.h" + +static int vpd_decode_len(const s32 max_len, const u8 *in, + s32 *length, s32 *decoded_len) +{ + u8 more; + int i = 0; + + if (!length || !decoded_len) + return VPD_FAIL; + + *length = 0; + do { + if (i >= max_len) + return VPD_FAIL; + + more = in[i] & 0x80; + *length <<= 7; + *length |= in[i] & 0x7f; + ++i; + } while (more); + + *decoded_len = i; + + return VPD_OK; +} + +int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed, + vpd_decode_callback callback, void *callback_arg) +{ + int type; + int res; + s32 key_len; + s32 value_len; + s32 decoded_len; + const u8 *key; + const u8 *value; + + /* type */ + if (*consumed >= max_len) + return VPD_FAIL; + + type = input_buf[*consumed]; + + switch (type) { + case VPD_TYPE_INFO: + case VPD_TYPE_STRING: + (*consumed)++; + + /* key */ + res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed], + &key_len, &decoded_len); + if (res != VPD_OK || *consumed + decoded_len >= max_len) + return VPD_FAIL; + + *consumed += decoded_len; + key = &input_buf[*consumed]; + *consumed += key_len; + + /* value */ + res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed], + &value_len, &decoded_len); + if (res != VPD_OK || *consumed + decoded_len > max_len) + return VPD_FAIL; + + *consumed += decoded_len; + value = &input_buf[*consumed]; + *consumed += value_len; + + if (type == VPD_TYPE_STRING) + return callback(key, key_len, value, value_len, + callback_arg); + break; + + default: + return VPD_FAIL; + } + + return VPD_OK; +} diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/google/vpd_decode.h new file mode 100644 index 000000000000..be3d62c5ca2f --- /dev/null +++ b/drivers/firmware/google/vpd_decode.h @@ -0,0 +1,58 @@ +/* + * vpd_decode.h + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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. + */ + +#ifndef __VPD_DECODE_H +#define __VPD_DECODE_H + +#include + +enum { + VPD_OK = 0, + VPD_FAIL, +}; + +enum { + VPD_TYPE_TERMINATOR = 0, + VPD_TYPE_STRING, + VPD_TYPE_INFO = 0xfe, + VPD_TYPE_IMPLICIT_TERMINATOR = 0xff, +}; + +/* Callback for vpd_decode_string to invoke. */ +typedef int vpd_decode_callback(const u8 *key, s32 key_len, + const u8 *value, s32 value_len, + void *arg); + +/* + * vpd_decode_string + * + * Given the encoded string, this function invokes callback with extracted + * (key, value). The *consumed will be plused the number of bytes consumed in + * this function. + * + * The input_buf points to the first byte of the input buffer. + * + * The *consumed starts from 0, which is actually the next byte to be decoded. + * It can be non-zero to be used in multiple calls. + * + * If one entry is successfully decoded, sends it to callback and returns the + * result. + */ +int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed, + vpd_decode_callback callback, void *callback_arg); + +#endif /* __VPD_DECODE_H */ -- cgit v1.2.3-70-g09d2 From 049a59db34eb4c41a0231f983f180053db8f80d4 Mon Sep 17 00:00:00 2001 From: Wei-Ning Huang Date: Wed, 12 Apr 2017 18:56:19 +0200 Subject: firmware: Google VPD sysfs driver This patch introduces the Google Vital Product Data driver. This driver reads Vital Product Data from coreboot tables and then creates the corresponding sysfs entries under /sys/firmware/vpd to provide easy access for userspace programs (does not require flashrom). The sysfs is structured as follow: /sys/firmware/vpd |-- ro | |-- key1 | `-- key2 |-- ro_raw |-- rw | `-- key1 `-- rw_raw Where ro_raw and rw_raw contain the raw VPD partition. The files under ro and rw correspond to the key name in the VPD and the the file content is the value for the key. Signed-off-by: Wei-Ning Huang Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/Kconfig | 7 + drivers/firmware/google/Makefile | 3 + drivers/firmware/google/vpd.c | 332 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 drivers/firmware/google/vpd.c diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 00000e0376b9..f16b381a569c 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -64,4 +64,11 @@ config GOOGLE_MEMCONSOLE_COREBOOT the coreboot table. If found, this log is exported to userland in the file /sys/firmware/log. +config GOOGLE_VPD + tristate "Vital Product Data" + depends on GOOGLE_COREBOOT_TABLE + help + This option enables the kernel to expose the content of Google VPD + under /sys/firmware/vpd. + endif # GOOGLE_FIRMWARE diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index bb952c686d3b..bc4de02202ad 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -6,3 +6,6 @@ obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o + +vpd-sysfs-y := vpd.o vpd_decode.o +obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c new file mode 100644 index 000000000000..619f4bae474f --- /dev/null +++ b/drivers/firmware/google/vpd.c @@ -0,0 +1,332 @@ +/* + * vpd.c + * + * Driver for exporting VPD content to sysfs. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "coreboot_table.h" +#include "vpd_decode.h" + +#define CB_TAG_VPD 0x2c +#define VPD_CBMEM_MAGIC 0x43524f53 + +static struct kobject *vpd_kobj; + +struct vpd_cbmem { + u32 magic; + u32 version; + u32 ro_size; + u32 rw_size; + u8 blob[0]; +}; + +struct vpd_section { + bool enabled; + const char *name; + char *raw_name; /* the string name_raw */ + struct kobject *kobj; /* vpd/name directory */ + char *baseaddr; + struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */ + struct list_head attribs; /* key/value in vpd_attrib_info list */ +}; + +struct vpd_attrib_info { + char *key; + const char *value; + struct bin_attribute bin_attr; + struct list_head list; +}; + +static struct vpd_section ro_vpd; +static struct vpd_section rw_vpd; + +static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_attrib_info *info = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, info->value, + info->bin_attr.size); +} + +/* + * vpd_section_check_key_name() + * + * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but + * old firmware versions may have entries like "S/N" which are problematic when + * exporting them as sysfs attributes. These keys present in old firmwares are + * ignored. + * + * Returns VPD_OK for a valid key name, VPD_FAIL otherwise. + * + * @key: The key name to check + * @key_len: key name length + */ +static int vpd_section_check_key_name(const u8 *key, s32 key_len) +{ + int c; + + while (key_len-- > 0) { + c = *key++; + + if (!isalnum(c) && c != '_') + return VPD_FAIL; + } + + return VPD_OK; +} + +static int vpd_section_attrib_add(const u8 *key, s32 key_len, + const u8 *value, s32 value_len, + void *arg) +{ + int ret; + struct vpd_section *sec = arg; + struct vpd_attrib_info *info; + + /* + * Return VPD_OK immediately to decode next entry if the current key + * name contains invalid characters. + */ + if (vpd_section_check_key_name(key, key_len) != VPD_OK) + return VPD_OK; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + info->key = kzalloc(key_len + 1, GFP_KERNEL); + if (!info->key) + return -ENOMEM; + + memcpy(info->key, key, key_len); + + sysfs_bin_attr_init(&info->bin_attr); + info->bin_attr.attr.name = info->key; + info->bin_attr.attr.mode = 0444; + info->bin_attr.size = value_len; + info->bin_attr.read = vpd_attrib_read; + info->bin_attr.private = info; + + info->value = value; + + INIT_LIST_HEAD(&info->list); + list_add_tail(&info->list, &sec->attribs); + + ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr); + if (ret) { + kfree(info->key); + return ret; + } + + return 0; +} + +static void vpd_section_attrib_destroy(struct vpd_section *sec) +{ + struct vpd_attrib_info *info; + struct vpd_attrib_info *temp; + + list_for_each_entry_safe(info, temp, &sec->attribs, list) { + kfree(info->key); + sysfs_remove_bin_file(sec->kobj, &info->bin_attr); + kfree(info); + } +} + +static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_section *sec = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, sec->baseaddr, + sec->bin_attr.size); +} + +static int vpd_section_create_attribs(struct vpd_section *sec) +{ + s32 consumed; + int ret; + + consumed = 0; + do { + ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr, + &consumed, vpd_section_attrib_add, sec); + } while (ret == VPD_OK); + + return 0; +} + +static int vpd_section_init(const char *name, struct vpd_section *sec, + phys_addr_t physaddr, size_t size) +{ + int ret; + int raw_len; + + sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB); + if (!sec->baseaddr) + return -ENOMEM; + + sec->name = name; + + /* We want to export the raw partion with name ${name}_raw */ + raw_len = strlen(name) + 5; + sec->raw_name = kzalloc(raw_len, GFP_KERNEL); + strncpy(sec->raw_name, name, raw_len); + strncat(sec->raw_name, "_raw", raw_len); + + sysfs_bin_attr_init(&sec->bin_attr); + sec->bin_attr.attr.name = sec->raw_name; + sec->bin_attr.attr.mode = 0444; + sec->bin_attr.size = size; + sec->bin_attr.read = vpd_section_read; + sec->bin_attr.private = sec; + + ret = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr); + if (ret) + goto free_sec; + + sec->kobj = kobject_create_and_add(name, vpd_kobj); + if (!sec->kobj) { + ret = -EINVAL; + goto sysfs_remove; + } + + INIT_LIST_HEAD(&sec->attribs); + vpd_section_create_attribs(sec); + + sec->enabled = true; + + return 0; + +sysfs_remove: + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + +free_sec: + kfree(sec->raw_name); + iounmap(sec->baseaddr); + + return ret; +} + +static int vpd_section_destroy(struct vpd_section *sec) +{ + if (sec->enabled) { + vpd_section_attrib_destroy(sec); + kobject_del(sec->kobj); + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + kfree(sec->raw_name); + iounmap(sec->baseaddr); + } + + return 0; +} + +static int vpd_sections_init(phys_addr_t physaddr) +{ + struct vpd_cbmem __iomem *temp; + struct vpd_cbmem header; + int ret = 0; + + temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB); + if (!temp) + return -ENOMEM; + + memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem)); + iounmap(temp); + + if (header.magic != VPD_CBMEM_MAGIC) + return -ENODEV; + + if (header.ro_size) { + ret = vpd_section_init("ro", &ro_vpd, + physaddr + sizeof(struct vpd_cbmem), + header.ro_size); + if (ret) + return ret; + } + + if (header.rw_size) { + ret = vpd_section_init("rw", &rw_vpd, + physaddr + sizeof(struct vpd_cbmem) + + header.ro_size, header.rw_size); + if (ret) + return ret; + } + + return 0; +} + +static int vpd_probe(struct platform_device *pdev) +{ + int ret; + struct lb_cbmem_ref entry; + + ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry)); + if (ret) + return ret; + + return vpd_sections_init(entry.cbmem_addr); +} + +static struct platform_driver vpd_driver = { + .probe = vpd_probe, + .driver = { + .name = "vpd", + }, +}; + +static int __init vpd_platform_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("vpd", -1, NULL, 0); + if (!pdev) + return -ENODEV; + + vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); + if (!vpd_kobj) + return -ENOMEM; + + memset(&ro_vpd, 0, sizeof(ro_vpd)); + memset(&rw_vpd, 0, sizeof(rw_vpd)); + + platform_driver_register(&vpd_driver); + + return 0; +} + +static void __exit vpd_platform_exit(void) +{ + vpd_section_destroy(&ro_vpd); + vpd_section_destroy(&rw_vpd); + kobject_del(vpd_kobj); +} + +module_init(vpd_platform_init); +module_exit(vpd_platform_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From 139752a210544dd02bac5c21322751de300ece93 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 20 Apr 2017 09:49:03 +0300 Subject: mei: drop the TODO from samples The TODO file is not relevant anymore and it's just a leftover from the time the driver was in the staging tree. Signed-off-by: Tomas Winkler Reviewed-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- samples/mei/TODO | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 samples/mei/TODO diff --git a/samples/mei/TODO b/samples/mei/TODO deleted file mode 100644 index 6b3625d3058c..000000000000 --- a/samples/mei/TODO +++ /dev/null @@ -1,2 +0,0 @@ -TODO: - - Cleanup and split the timer function -- cgit v1.2.3-70-g09d2 From d721f9bbe6a7d9543f560053f8be5237e7db18e9 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Mon, 24 Apr 2017 16:34:20 -0500 Subject: fpga altera-hps2fpga: disable/unprepare clock on error in alt_fpga_bridge_probe() If either _alt_hps2fpga_enable_set() or fpga_bridge_register() fail in alt_fpga_bridge_probe(), the clock remains enabled and prepared. Also, in the error path for _alt_hps2fpga_enable_set() a call to fpga_bridge_unregister() is made even though the bridge was not registered yet. Remove the unnecessary call to fpga_bridge_unregister() and call clk_disable_unprepare() in both error paths in order to make sure the clock gets properly disabled and unprepared. Signed-off-by: Tobias Klauser Acked-by: Moritz Fischer Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/altera-hps2fpga.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c index 4b354c79be31..3066b805f2d0 100644 --- a/drivers/fpga/altera-hps2fpga.c +++ b/drivers/fpga/altera-hps2fpga.c @@ -181,15 +181,18 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev) (enable ? "enabling" : "disabling")); ret = _alt_hps2fpga_enable_set(priv, enable); - if (ret) { - fpga_bridge_unregister(&pdev->dev); - return ret; - } + if (ret) + goto err; } } - return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, - priv); + ret = fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, + priv); +err: + if (ret) + clk_disable_unprepare(priv->clk); + + return ret; } static int alt_fpga_bridge_remove(struct platform_device *pdev) -- cgit v1.2.3-70-g09d2 From e73bbf64907c88d3bb811756fc25548131524035 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Mon, 24 Apr 2017 16:34:21 -0500 Subject: fpga: region: release FPGA region reference in error path If fpga_region_get_manager() fails in fpga_region_program_fpga(), a reference to the fpga_manager instance previously acquired through fpga_region_get() is retained. Make sure to properly release it in the error case by using a separate jump label which will call fpga_region_put() in before returning. Signed-off-by: Tobias Klauser Acked-by: Moritz Fischer Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-region.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index ae4c61aada99..3b6b2f4182a1 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -245,7 +245,8 @@ static int fpga_region_program_fpga(struct fpga_region *region, mgr = fpga_region_get_manager(region); if (IS_ERR(mgr)) { pr_err("failed to get fpga region manager\n"); - return PTR_ERR(mgr); + ret = PTR_ERR(mgr); + goto err_put_region; } ret = fpga_region_get_bridges(region, overlay); @@ -281,6 +282,7 @@ err_put_br: fpga_bridges_put(®ion->bridge_list); err_put_mgr: fpga_mgr_put(mgr); +err_put_region: fpga_region_put(region); return ret; -- cgit v1.2.3-70-g09d2 From dd17cc7bf8b9447f5612c3887b84a88eeb5e3dff Mon Sep 17 00:00:00 2001 From: Matthew Gerlach Date: Mon, 24 Apr 2017 16:34:22 -0500 Subject: fpga fr br: update supported version numbers The value in the version register of the altera freeze bridge controller changed from the beta value of 2 to the value of 0xad000003 in the official release of the IP. This patch supports the old and new version numbers, and the driver's probe function will fail if neither of the supported versions is found. Signed-off-by: Matthew Gerlach Reviewed-by: Moritz Fischer Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/altera-freeze-bridge.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c index 8c1bc7e7ee69..6159cfcf78a2 100644 --- a/drivers/fpga/altera-freeze-bridge.c +++ b/drivers/fpga/altera-freeze-bridge.c @@ -28,6 +28,7 @@ #define FREEZE_CSR_REG_VERSION 12 #define FREEZE_CSR_SUPPORTED_VERSION 2 +#define FREEZE_CSR_OFFICIAL_VERSION 0xad000003 #define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0) #define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1) @@ -218,6 +219,7 @@ static int altera_freeze_br_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; + void __iomem *base_addr; struct altera_freeze_br_data *priv; struct resource *res; u32 status, revision; @@ -225,26 +227,32 @@ static int altera_freeze_br_probe(struct platform_device *pdev) if (!np) return -ENODEV; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(base_addr)) + return PTR_ERR(base_addr); + + revision = readl(base_addr + FREEZE_CSR_REG_VERSION); + if ((revision != FREEZE_CSR_SUPPORTED_VERSION) && + (revision != FREEZE_CSR_OFFICIAL_VERSION)) { + dev_err(dev, + "%s unexpected revision 0x%x != 0x%x != 0x%x\n", + __func__, revision, FREEZE_CSR_SUPPORTED_VERSION, + FREEZE_CSR_OFFICIAL_VERSION); + return -EINVAL; + } + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base_addr = devm_ioremap_resource(dev, res); - if (IS_ERR(priv->base_addr)) - return PTR_ERR(priv->base_addr); - - status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + status = readl(base_addr + FREEZE_CSR_STATUS_OFFSET); if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) priv->enable = 1; - revision = readl(priv->base_addr + FREEZE_CSR_REG_VERSION); - if (revision != FREEZE_CSR_SUPPORTED_VERSION) - dev_warn(dev, - "%s Freeze Controller unexpected revision %d != %d\n", - __func__, revision, FREEZE_CSR_SUPPORTED_VERSION); + priv->base_addr = base_addr; return fpga_bridge_register(dev, FREEZE_BRIDGE_NAME, &altera_freeze_br_br_ops, priv); -- cgit v1.2.3-70-g09d2 From 726ea1a8ea96b2bba34ee2073b58f0770800701c Mon Sep 17 00:00:00 2001 From: Jin Qian Date: Thu, 20 Apr 2017 16:05:11 +0200 Subject: goldfish_pipe: An implementation of more parallel pipe This is a driver code for a redesigned android pipe. Currently it works for x86 and x64 emulators with the following performance results: ADB push to /dev/null, Ubuntu, 400 MB file, times are for (1 / 10 / 100) parallel adb commands x86 adb push: (4.4s / 11.5s / 2m10s) -> (2.8s / 6s / 51s) x64 adb push: (7s / 15s / (too long, 6m+) -> (2.7s / 6.2s / 52s) ADB pull and push to /data/ have the same %% of speedup More importantly, I don't see any signs of slowdowns when run in parallel with Antutu benchmark, so it is definitely making much better job at multithreading. Signed-off-by: Yurii Zubrytskyi Signed-off-by: Jin Qian Signed-off-by: Greg Kroah-Hartman --- drivers/platform/goldfish/goldfish_pipe.c | 970 ++++++++++++++++++++---------- 1 file changed, 641 insertions(+), 329 deletions(-) diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 2b21033f11f0..9804e9e455d2 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -1,8 +1,8 @@ /* - * Copyright (C) 2011 Google, Inc. * Copyright (C) 2012 Intel, Inc. * Copyright (C) 2013 Intel, Inc. * Copyright (C) 2014 Linaro Limited + * Copyright (C) 2011-2016 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 @@ -46,6 +46,7 @@ * exchange is properly mapped during a transfer. */ + #include #include #include @@ -62,123 +63,233 @@ #include #include +/* + * Update this when something changes in the driver's behavior so the host + * can benefit from knowing it + */ +enum { + PIPE_DRIVER_VERSION = 2, + PIPE_CURRENT_DEVICE_VERSION = 2 +}; + /* * IMPORTANT: The following constants must match the ones used and defined * in external/qemu/hw/goldfish_pipe.c in the Android source tree. */ -/* pipe device registers */ -#define PIPE_REG_COMMAND 0x00 /* write: value = command */ -#define PIPE_REG_STATUS 0x04 /* read */ -#define PIPE_REG_CHANNEL 0x08 /* read/write: channel id */ -#define PIPE_REG_CHANNEL_HIGH 0x30 /* read/write: channel id */ -#define PIPE_REG_SIZE 0x0c /* read/write: buffer size */ -#define PIPE_REG_ADDRESS 0x10 /* write: physical address */ -#define PIPE_REG_ADDRESS_HIGH 0x34 /* write: physical address */ -#define PIPE_REG_WAKES 0x14 /* read: wake flags */ -#define PIPE_REG_PARAMS_ADDR_LOW 0x18 /* read/write: batch data address */ -#define PIPE_REG_PARAMS_ADDR_HIGH 0x1c /* read/write: batch data address */ -#define PIPE_REG_ACCESS_PARAMS 0x20 /* write: batch access */ -#define PIPE_REG_VERSION 0x24 /* read: device version */ - -/* list of commands for PIPE_REG_COMMAND */ -#define CMD_OPEN 1 /* open new channel */ -#define CMD_CLOSE 2 /* close channel (from guest) */ -#define CMD_POLL 3 /* poll read/write status */ - /* List of bitflags returned in status of CMD_POLL command */ -#define PIPE_POLL_IN (1 << 0) -#define PIPE_POLL_OUT (1 << 1) -#define PIPE_POLL_HUP (1 << 2) - -/* The following commands are related to write operations */ -#define CMD_WRITE_BUFFER 4 /* send a user buffer to the emulator */ -#define CMD_WAKE_ON_WRITE 5 /* tell the emulator to wake us when writing - is possible */ -#define CMD_READ_BUFFER 6 /* receive a user buffer from the emulator */ -#define CMD_WAKE_ON_READ 7 /* tell the emulator to wake us when reading - * is possible */ +enum PipePollFlags { + PIPE_POLL_IN = 1 << 0, + PIPE_POLL_OUT = 1 << 1, + PIPE_POLL_HUP = 1 << 2 +}; /* Possible status values used to signal errors - see goldfish_pipe_error_convert */ -#define PIPE_ERROR_INVAL -1 -#define PIPE_ERROR_AGAIN -2 -#define PIPE_ERROR_NOMEM -3 -#define PIPE_ERROR_IO -4 +enum PipeErrors { + PIPE_ERROR_INVAL = -1, + PIPE_ERROR_AGAIN = -2, + PIPE_ERROR_NOMEM = -3, + PIPE_ERROR_IO = -4 +}; /* Bit-flags used to signal events from the emulator */ -#define PIPE_WAKE_CLOSED (1 << 0) /* emulator closed pipe */ -#define PIPE_WAKE_READ (1 << 1) /* pipe can now be read from */ -#define PIPE_WAKE_WRITE (1 << 2) /* pipe can now be written to */ - -struct access_params { - unsigned long channel; - u32 size; - unsigned long address; - u32 cmd; - u32 result; - /* reserved for future extension */ +enum PipeWakeFlags { + PIPE_WAKE_CLOSED = 1 << 0, /* emulator closed pipe */ + PIPE_WAKE_READ = 1 << 1, /* pipe can now be read from */ + PIPE_WAKE_WRITE = 1 << 2 /* pipe can now be written to */ +}; + +/* Bit flags for the 'flags' field */ +enum PipeFlagsBits { + BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */ + BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */ + BIT_WAKE_ON_READ = 2, /* want to be woken on reads */ +}; + +enum PipeRegs { + PIPE_REG_CMD = 0, + + PIPE_REG_SIGNAL_BUFFER_HIGH = 4, + PIPE_REG_SIGNAL_BUFFER = 8, + PIPE_REG_SIGNAL_BUFFER_COUNT = 12, + + PIPE_REG_OPEN_BUFFER_HIGH = 20, + PIPE_REG_OPEN_BUFFER = 24, + + PIPE_REG_VERSION = 36, + + PIPE_REG_GET_SIGNALLED = 48, +}; + +enum PipeCmdCode { + PIPE_CMD_OPEN = 1, /* to be used by the pipe device itself */ + PIPE_CMD_CLOSE, + PIPE_CMD_POLL, + PIPE_CMD_WRITE, + PIPE_CMD_WAKE_ON_WRITE, + PIPE_CMD_READ, + PIPE_CMD_WAKE_ON_READ, + + /* + * TODO(zyy): implement a deferred read/write execution to allow + * parallel processing of pipe operations on the host. + */ + PIPE_CMD_WAKE_ON_DONE_IO, +}; + +enum { + MAX_BUFFERS_PER_COMMAND = 336, + MAX_SIGNALLED_PIPES = 64, + INITIAL_PIPES_CAPACITY = 64 +}; + +struct goldfish_pipe_dev; +struct goldfish_pipe; +struct goldfish_pipe_command; + +/* A per-pipe command structure, shared with the host */ +struct goldfish_pipe_command { + s32 cmd; /* PipeCmdCode, guest -> host */ + s32 id; /* pipe id, guest -> host */ + s32 status; /* command execution status, host -> guest */ + s32 reserved; /* to pad to 64-bit boundary */ + union { + /* Parameters for PIPE_CMD_{READ,WRITE} */ + struct { + /* number of buffers, guest -> host */ + u32 buffers_count; + /* number of consumed bytes, host -> guest */ + s32 consumed_size; + /* buffer pointers, guest -> host */ + u64 ptrs[MAX_BUFFERS_PER_COMMAND]; + /* buffer sizes, guest -> host */ + u32 sizes[MAX_BUFFERS_PER_COMMAND]; + } rw_params; + }; +}; + +/* A single signalled pipe information */ +struct signalled_pipe_buffer { + u32 id; u32 flags; }; -/* The global driver data. Holds a reference to the i/o page used to - * communicate with the emulator, and a wake queue for blocked tasks - * waiting to be awoken. - */ -struct goldfish_pipe_dev { - spinlock_t lock; - unsigned char __iomem *base; - struct access_params *aps; - int irq; - u32 version; +/* Parameters for the PIPE_CMD_OPEN command */ +struct open_command_param { + u64 command_buffer_ptr; + u32 rw_params_max_count; }; -static struct goldfish_pipe_dev pipe_dev[1]; +/* Device-level set of buffers shared with the host */ +struct goldfish_pipe_dev_buffers { + struct open_command_param open_command_params; + struct signalled_pipe_buffer signalled_pipe_buffers[ + MAX_SIGNALLED_PIPES]; +}; /* This data type models a given pipe instance */ struct goldfish_pipe { - struct goldfish_pipe_dev *dev; - struct mutex lock; + /* pipe ID - index into goldfish_pipe_dev::pipes array */ + u32 id; + /* The wake flags pipe is waiting for + * Note: not protected with any lock, uses atomic operations + * and barriers to make it thread-safe. + */ unsigned long flags; + /* wake flags host have signalled, + * - protected by goldfish_pipe_dev::lock + */ + unsigned long signalled_flags; + + /* A pointer to command buffer */ + struct goldfish_pipe_command *command_buffer; + + /* doubly linked list of signalled pipes, protected by + * goldfish_pipe_dev::lock + */ + struct goldfish_pipe *prev_signalled; + struct goldfish_pipe *next_signalled; + + /* + * A pipe's own lock. Protects the following: + * - *command_buffer - makes sure a command can safely write its + * parameters to the host and read the results back. + */ + struct mutex lock; + + /* A wake queue for sleeping until host signals an event */ wait_queue_head_t wake_queue; + /* Pointer to the parent goldfish_pipe_dev instance */ + struct goldfish_pipe_dev *dev; }; +/* The global driver data. Holds a reference to the i/o page used to + * communicate with the emulator, and a wake queue for blocked tasks + * waiting to be awoken. + */ +struct goldfish_pipe_dev { + /* + * Global device spinlock. Protects the following members: + * - pipes, pipes_capacity + * - [*pipes, *pipes + pipes_capacity) - array data + * - first_signalled_pipe, + * goldfish_pipe::prev_signalled, + * goldfish_pipe::next_signalled, + * goldfish_pipe::signalled_flags - all singnalled-related fields, + * in all allocated pipes + * - open_command_params - PIPE_CMD_OPEN-related buffers + * + * It looks like a lot of different fields, but the trick is that + * the only operation that happens often is the signalled pipes array + * manipulation. That's why it's OK for now to keep the rest of the + * fields under the same lock. If we notice too much contention because + * of PIPE_CMD_OPEN, then we should add a separate lock there. + */ + spinlock_t lock; -/* Bit flags for the 'flags' field */ -enum { - BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */ - BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */ - BIT_WAKE_ON_READ = 2, /* want to be woken on reads */ + /* + * Array of the pipes of |pipes_capacity| elements, + * indexed by goldfish_pipe::id + */ + struct goldfish_pipe **pipes; + u32 pipes_capacity; + + /* Pointers to the buffers host uses for interaction with this driver */ + struct goldfish_pipe_dev_buffers *buffers; + + /* Head of a doubly linked list of signalled pipes */ + struct goldfish_pipe *first_signalled_pipe; + + /* Some device-specific data */ + int irq; + int version; + unsigned char __iomem *base; }; +struct goldfish_pipe_dev pipe_dev[1] = {}; -static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd) +static int goldfish_cmd_locked(struct goldfish_pipe *pipe, enum PipeCmdCode cmd) { - unsigned long flags; - u32 status; - struct goldfish_pipe_dev *dev = pipe->dev; - - spin_lock_irqsave(&dev->lock, flags); - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(cmd, dev->base + PIPE_REG_COMMAND); - status = readl(dev->base + PIPE_REG_STATUS); - spin_unlock_irqrestore(&dev->lock, flags); - return status; + pipe->command_buffer->cmd = cmd; + /* failure by default */ + pipe->command_buffer->status = PIPE_ERROR_INVAL; + writel(pipe->id, pipe->dev->base + PIPE_REG_CMD); + return pipe->command_buffer->status; } -static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd) +static int goldfish_cmd(struct goldfish_pipe *pipe, enum PipeCmdCode cmd) { - unsigned long flags; - struct goldfish_pipe_dev *dev = pipe->dev; + int status; - spin_lock_irqsave(&dev->lock, flags); - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(cmd, dev->base + PIPE_REG_COMMAND); - spin_unlock_irqrestore(&dev->lock, flags); + if (mutex_lock_interruptible(&pipe->lock)) + return PIPE_ERROR_IO; + status = goldfish_cmd_locked(pipe, cmd); + mutex_unlock(&pipe->lock); + return status; } -/* This function converts an error code returned by the emulator through +/* + * This function converts an error code returned by the emulator through * the PIPE_REG_STATUS i/o register into a valid negative errno value. */ static int goldfish_pipe_error_convert(int status) @@ -195,184 +306,202 @@ static int goldfish_pipe_error_convert(int status) } } -/* - * Notice: QEMU will return 0 for un-known register access, indicating - * param_acess is supported or not - */ -static int valid_batchbuffer_addr(struct goldfish_pipe_dev *dev, - struct access_params *aps) +static int pin_user_pages(unsigned long first_page, unsigned long last_page, + unsigned int last_page_size, int is_write, + struct page *pages[MAX_BUFFERS_PER_COMMAND], + unsigned int *iter_last_page_size) { - u32 aph, apl; - u64 paddr; - aph = readl(dev->base + PIPE_REG_PARAMS_ADDR_HIGH); - apl = readl(dev->base + PIPE_REG_PARAMS_ADDR_LOW); + int ret; + int requested_pages = ((last_page - first_page) >> PAGE_SHIFT) + 1; + + if (requested_pages > MAX_BUFFERS_PER_COMMAND) { + requested_pages = MAX_BUFFERS_PER_COMMAND; + *iter_last_page_size = PAGE_SIZE; + } else { + *iter_last_page_size = last_page_size; + } + + ret = get_user_pages_fast( + first_page, requested_pages, !is_write, pages); + if (ret <= 0) + return -EFAULT; + if (ret < requested_pages) + *iter_last_page_size = PAGE_SIZE; + return ret; - paddr = ((u64)aph << 32) | apl; - if (paddr != (__pa(aps))) - return 0; - return 1; } -/* 0 on success */ -static int setup_access_params_addr(struct platform_device *pdev, - struct goldfish_pipe_dev *dev) +static void release_user_pages(struct page **pages, int pages_count, + int is_write, s32 consumed_size) { - dma_addr_t dma_handle; - struct access_params *aps; + int i; - aps = dmam_alloc_coherent(&pdev->dev, sizeof(struct access_params), - &dma_handle, GFP_KERNEL); - if (!aps) - return -ENOMEM; + for (i = 0; i < pages_count; i++) { + if (!is_write && consumed_size > 0) + set_page_dirty(pages[i]); + put_page(pages[i]); + } +} + +/* Populate the call parameters, merging adjacent pages together */ +static void populate_rw_params( + struct page **pages, int pages_count, + unsigned long address, unsigned long address_end, + unsigned long first_page, unsigned long last_page, + unsigned int iter_last_page_size, int is_write, + struct goldfish_pipe_command *command) +{ + /* + * Process the first page separately - it's the only page that + * needs special handling for its start address. + */ + unsigned long xaddr = page_to_phys(pages[0]); + unsigned long xaddr_prev = xaddr; + int buffer_idx = 0; + int i = 1; + int size_on_page = first_page == last_page + ? (int)(address_end - address) + : (PAGE_SIZE - (address & ~PAGE_MASK)); + command->rw_params.ptrs[0] = (u64)(xaddr | (address & ~PAGE_MASK)); + command->rw_params.sizes[0] = size_on_page; + for (; i < pages_count; ++i) { + xaddr = page_to_phys(pages[i]); + size_on_page = (i == pages_count - 1) ? + iter_last_page_size : PAGE_SIZE; + if (xaddr == xaddr_prev + PAGE_SIZE) { + command->rw_params.sizes[buffer_idx] += size_on_page; + } else { + ++buffer_idx; + command->rw_params.ptrs[buffer_idx] = (u64)xaddr; + command->rw_params.sizes[buffer_idx] = size_on_page; + } + xaddr_prev = xaddr; + } + command->rw_params.buffers_count = buffer_idx + 1; +} - writel(upper_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_HIGH); - writel(lower_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_LOW); +static int transfer_max_buffers(struct goldfish_pipe *pipe, + unsigned long address, unsigned long address_end, int is_write, + unsigned long last_page, unsigned int last_page_size, + s32 *consumed_size, int *status) +{ + struct page *pages[MAX_BUFFERS_PER_COMMAND]; + unsigned long first_page = address & PAGE_MASK; + unsigned int iter_last_page_size; + int pages_count = pin_user_pages(first_page, last_page, + last_page_size, is_write, + pages, &iter_last_page_size); - if (valid_batchbuffer_addr(dev, aps)) { - dev->aps = aps; - return 0; - } else - return -1; + if (pages_count < 0) + return pages_count; + + /* Serialize access to the pipe command buffers */ + if (mutex_lock_interruptible(&pipe->lock)) + return -ERESTARTSYS; + + populate_rw_params(pages, pages_count, address, address_end, + first_page, last_page, iter_last_page_size, is_write, + pipe->command_buffer); + + /* Transfer the data */ + *status = goldfish_cmd_locked(pipe, + is_write ? PIPE_CMD_WRITE : PIPE_CMD_READ); + + *consumed_size = pipe->command_buffer->rw_params.consumed_size; + + mutex_unlock(&pipe->lock); + + release_user_pages(pages, pages_count, is_write, *consumed_size); + + return 0; } -/* A value that will not be set by qemu emulator */ -#define INITIAL_BATCH_RESULT (0xdeadbeaf) -static int access_with_param(struct goldfish_pipe_dev *dev, const int cmd, - unsigned long address, unsigned long avail, - struct goldfish_pipe *pipe, int *status) +static int wait_for_host_signal(struct goldfish_pipe *pipe, int is_write) { - struct access_params *aps = dev->aps; + u32 wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ; - if (aps == NULL) - return -1; + set_bit(wakeBit, &pipe->flags); + + /* Tell the emulator we're going to wait for a wake event */ + (void)goldfish_cmd(pipe, + is_write ? PIPE_CMD_WAKE_ON_WRITE : PIPE_CMD_WAKE_ON_READ); + + while (test_bit(wakeBit, &pipe->flags)) { + if (wait_event_interruptible( + pipe->wake_queue, + !test_bit(wakeBit, &pipe->flags))) + return -ERESTARTSYS; + + if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) + return -EIO; + } - aps->result = INITIAL_BATCH_RESULT; - aps->channel = (unsigned long)pipe; - aps->size = avail; - aps->address = address; - aps->cmd = cmd; - writel(cmd, dev->base + PIPE_REG_ACCESS_PARAMS); - /* - * If the aps->result has not changed, that means - * that the batch command failed - */ - if (aps->result == INITIAL_BATCH_RESULT) - return -1; - *status = aps->result; return 0; } -static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, - size_t bufflen, int is_write) +static ssize_t goldfish_pipe_read_write(struct file *filp, + char __user *buffer, size_t bufflen, int is_write) { - unsigned long irq_flags; struct goldfish_pipe *pipe = filp->private_data; - struct goldfish_pipe_dev *dev = pipe->dev; - unsigned long address, address_end; int count = 0, ret = -EINVAL; + unsigned long address, address_end, last_page; + unsigned int last_page_size; /* If the emulator already closed the pipe, no need to go further */ - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) + if (unlikely(test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))) return -EIO; - /* Null reads or writes succeeds */ if (unlikely(bufflen == 0)) return 0; - /* Check the buffer range for access */ - if (!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ, - buffer, bufflen)) + if (unlikely(!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ, + buffer, bufflen))) return -EFAULT; - /* Serialize access to the pipe */ - if (mutex_lock_interruptible(&pipe->lock)) - return -ERESTARTSYS; - - address = (unsigned long)(void *)buffer; + address = (unsigned long)buffer; address_end = address + bufflen; + last_page = (address_end - 1) & PAGE_MASK; + last_page_size = ((address_end - 1) & ~PAGE_MASK) + 1; while (address < address_end) { - unsigned long page_end = (address & PAGE_MASK) + PAGE_SIZE; - unsigned long next = page_end < address_end ? page_end - : address_end; - unsigned long avail = next - address; - int status, wakeBit; - struct page *page; - - /* Either vaddr or paddr depending on the device version */ - unsigned long xaddr; + s32 consumed_size; + int status; - /* - * We grab the pages on a page-by-page basis in case user - * space gives us a potentially huge buffer but the read only - * returns a small amount, then there's no need to pin that - * much memory to the process. - */ - ret = get_user_pages_unlocked(address, 1, &page, - is_write ? 0 : FOLL_WRITE); + ret = transfer_max_buffers(pipe, address, address_end, is_write, + last_page, last_page_size, &consumed_size, + &status); if (ret < 0) break; - if (dev->version) { - /* Device version 1 or newer (qemu-android) expects the - * physical address. + if (consumed_size > 0) { + /* No matter what's the status, we've transferred + * something. */ - xaddr = page_to_phys(page) | (address & ~PAGE_MASK); - } else { - /* Device version 0 (classic emulator) expects the - * virtual address. - */ - xaddr = address; + count += consumed_size; + address += consumed_size; } - - /* Now, try to transfer the bytes in the current page */ - spin_lock_irqsave(&dev->lock, irq_flags); - if (access_with_param(dev, - is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER, - xaddr, avail, pipe, &status)) { - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(avail, dev->base + PIPE_REG_SIZE); - gf_write_ptr((void *)xaddr, - dev->base + PIPE_REG_ADDRESS, - dev->base + PIPE_REG_ADDRESS_HIGH); - writel(is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER, - dev->base + PIPE_REG_COMMAND); - status = readl(dev->base + PIPE_REG_STATUS); - } - spin_unlock_irqrestore(&dev->lock, irq_flags); - - if (status > 0 && !is_write) - set_page_dirty(page); - put_page(page); - - if (status > 0) { /* Correct transfer */ - count += status; - address += status; + if (status > 0) continue; - } else if (status == 0) { /* EOF */ + if (status == 0) { + /* EOF */ ret = 0; break; - } else if (status < 0 && count > 0) { + } + if (count > 0) { /* - * An error occurred and we already transferred - * something on one of the previous pages. + * An error occurred, but we already transferred + * something on one of the previous iterations. * Just return what we already copied and log this * err. - * - * Note: This seems like an incorrect approach but - * cannot change it until we check if any user space - * ABI relies on this behavior. */ if (status != PIPE_ERROR_AGAIN) - pr_info_ratelimited("goldfish_pipe: backend returned error %d on %s\n", + pr_info_ratelimited("goldfish_pipe: backend error %d on %s\n", status, is_write ? "write" : "read"); - ret = 0; break; } /* - * If the error is not PIPE_ERROR_AGAIN, or if we are not in + * If the error is not PIPE_ERROR_AGAIN, or if we are in * non-blocking mode, just return the error code. */ if (status != PIPE_ERROR_AGAIN || @@ -381,139 +510,214 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, break; } - /* - * The backend blocked the read/write, wait until the backend - * tells us it's ready to process more data. - */ - wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ; - set_bit(wakeBit, &pipe->flags); - - /* Tell the emulator we're going to wait for a wake event */ - goldfish_cmd(pipe, - is_write ? CMD_WAKE_ON_WRITE : CMD_WAKE_ON_READ); - - /* Unlock the pipe, then wait for the wake signal */ - mutex_unlock(&pipe->lock); - - while (test_bit(wakeBit, &pipe->flags)) { - if (wait_event_interruptible( - pipe->wake_queue, - !test_bit(wakeBit, &pipe->flags))) - return -ERESTARTSYS; - - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) - return -EIO; - } - - /* Try to re-acquire the lock */ - if (mutex_lock_interruptible(&pipe->lock)) - return -ERESTARTSYS; + status = wait_for_host_signal(pipe, is_write); + if (status < 0) + return status; } - mutex_unlock(&pipe->lock); - if (ret < 0) - return ret; - else + if (count > 0) return count; + return ret; } static ssize_t goldfish_pipe_read(struct file *filp, char __user *buffer, - size_t bufflen, loff_t *ppos) + size_t bufflen, loff_t *ppos) { - return goldfish_pipe_read_write(filp, buffer, bufflen, 0); + return goldfish_pipe_read_write(filp, buffer, bufflen, + /* is_write */ 0); } static ssize_t goldfish_pipe_write(struct file *filp, const char __user *buffer, size_t bufflen, loff_t *ppos) { - return goldfish_pipe_read_write(filp, (char __user *)buffer, - bufflen, 1); + return goldfish_pipe_read_write(filp, + /* cast away the const */(char __user *)buffer, bufflen, + /* is_write */ 1); } - static unsigned int goldfish_pipe_poll(struct file *filp, poll_table *wait) { struct goldfish_pipe *pipe = filp->private_data; unsigned int mask = 0; int status; - mutex_lock(&pipe->lock); - poll_wait(filp, &pipe->wake_queue, wait); - status = goldfish_cmd_status(pipe, CMD_POLL); - - mutex_unlock(&pipe->lock); + status = goldfish_cmd(pipe, PIPE_CMD_POLL); + if (status < 0) + return -ERESTARTSYS; if (status & PIPE_POLL_IN) mask |= POLLIN | POLLRDNORM; - if (status & PIPE_POLL_OUT) mask |= POLLOUT | POLLWRNORM; - if (status & PIPE_POLL_HUP) mask |= POLLHUP; - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) mask |= POLLERR; return mask; } -static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) +static void signalled_pipes_add_locked(struct goldfish_pipe_dev *dev, + u32 id, u32 flags) { - struct goldfish_pipe_dev *dev = dev_id; - unsigned long irq_flags; - int count = 0; + struct goldfish_pipe *pipe; - /* - * We're going to read from the emulator a list of (channel,flags) - * pairs corresponding to the wake events that occurred on each - * blocked pipe (i.e. channel). - */ - spin_lock_irqsave(&dev->lock, irq_flags); - for (;;) { - /* First read the channel, 0 means the end of the list */ - struct goldfish_pipe *pipe; - unsigned long wakes; - unsigned long channel = 0; + if (WARN_ON(id >= dev->pipes_capacity)) + return; + + pipe = dev->pipes[id]; + if (!pipe) + return; + pipe->signalled_flags |= flags; + + if (pipe->prev_signalled || pipe->next_signalled + || dev->first_signalled_pipe == pipe) + return; /* already in the list */ + pipe->next_signalled = dev->first_signalled_pipe; + if (dev->first_signalled_pipe) + dev->first_signalled_pipe->prev_signalled = pipe; + dev->first_signalled_pipe = pipe; +} -#ifdef CONFIG_64BIT - channel = (u64)readl(dev->base + PIPE_REG_CHANNEL_HIGH) << 32; +static void signalled_pipes_remove_locked(struct goldfish_pipe_dev *dev, + struct goldfish_pipe *pipe) { + if (pipe->prev_signalled) + pipe->prev_signalled->next_signalled = pipe->next_signalled; + if (pipe->next_signalled) + pipe->next_signalled->prev_signalled = pipe->prev_signalled; + if (pipe == dev->first_signalled_pipe) + dev->first_signalled_pipe = pipe->next_signalled; + pipe->prev_signalled = NULL; + pipe->next_signalled = NULL; +} - if (channel == 0) - break; -#endif - channel |= readl(dev->base + PIPE_REG_CHANNEL); +static struct goldfish_pipe *signalled_pipes_pop_front( + struct goldfish_pipe_dev *dev, int *wakes) +{ + struct goldfish_pipe *pipe; + unsigned long flags; - if (channel == 0) - break; + spin_lock_irqsave(&dev->lock, flags); - /* Convert channel to struct pipe pointer + read wake flags */ - wakes = readl(dev->base + PIPE_REG_WAKES); - pipe = (struct goldfish_pipe *)(ptrdiff_t)channel; + pipe = dev->first_signalled_pipe; + if (pipe) { + *wakes = pipe->signalled_flags; + pipe->signalled_flags = 0; + /* + * This is an optimized version of + * signalled_pipes_remove_locked() + * - We want to make it as fast as possible to + * wake the sleeping pipe operations faster. + */ + dev->first_signalled_pipe = pipe->next_signalled; + if (dev->first_signalled_pipe) + dev->first_signalled_pipe->prev_signalled = NULL; + pipe->next_signalled = NULL; + } - /* Did the emulator just closed a pipe? */ + spin_unlock_irqrestore(&dev->lock, flags); + return pipe; +} + +static void goldfish_interrupt_task(unsigned long unused) +{ + struct goldfish_pipe_dev *dev = pipe_dev; + /* Iterate over the signalled pipes and wake them one by one */ + struct goldfish_pipe *pipe; + int wakes; + + while ((pipe = signalled_pipes_pop_front(dev, &wakes)) != NULL) { if (wakes & PIPE_WAKE_CLOSED) { - set_bit(BIT_CLOSED_ON_HOST, &pipe->flags); - wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE; + pipe->flags = 1 << BIT_CLOSED_ON_HOST; + } else { + if (wakes & PIPE_WAKE_READ) + clear_bit(BIT_WAKE_ON_READ, &pipe->flags); + if (wakes & PIPE_WAKE_WRITE) + clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags); } - if (wakes & PIPE_WAKE_READ) - clear_bit(BIT_WAKE_ON_READ, &pipe->flags); - if (wakes & PIPE_WAKE_WRITE) - clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags); - + /* + * wake_up_interruptible() implies a write barrier, so don't + * explicitly add another one here. + */ wake_up_interruptible(&pipe->wake_queue); - count++; } - spin_unlock_irqrestore(&dev->lock, irq_flags); +} +DECLARE_TASKLET(goldfish_interrupt_tasklet, goldfish_interrupt_task, 0); - return (count == 0) ? IRQ_NONE : IRQ_HANDLED; +/* + * The general idea of the interrupt handling: + * + * 1. device raises an interrupt if there's at least one signalled pipe + * 2. IRQ handler reads the signalled pipes and their count from the device + * 3. device writes them into a shared buffer and returns the count + * it only resets the IRQ if it has returned all signalled pipes, + * otherwise it leaves it raised, so IRQ handler will be called + * again for the next chunk + * 4. IRQ handler adds all returned pipes to the device's signalled pipes list + * 5. IRQ handler launches a tasklet to process the signalled pipes from the + * list in a separate context + */ +static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) +{ + u32 count; + u32 i; + unsigned long flags; + struct goldfish_pipe_dev *dev = dev_id; + + if (dev != pipe_dev) + return IRQ_NONE; + + /* Request the signalled pipes from the device */ + spin_lock_irqsave(&dev->lock, flags); + + count = readl(dev->base + PIPE_REG_GET_SIGNALLED); + if (count == 0) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_NONE; + } + if (count > MAX_SIGNALLED_PIPES) + count = MAX_SIGNALLED_PIPES; + + for (i = 0; i < count; ++i) + signalled_pipes_add_locked(dev, + dev->buffers->signalled_pipe_buffers[i].id, + dev->buffers->signalled_pipe_buffers[i].flags); + + spin_unlock_irqrestore(&dev->lock, flags); + + tasklet_schedule(&goldfish_interrupt_tasklet); + return IRQ_HANDLED; +} + +static int get_free_pipe_id_locked(struct goldfish_pipe_dev *dev) +{ + int id; + + for (id = 0; id < dev->pipes_capacity; ++id) + if (!dev->pipes[id]) + return id; + + { + /* Reallocate the array */ + u32 new_capacity = 2 * dev->pipes_capacity; + struct goldfish_pipe **pipes = + kcalloc(new_capacity, sizeof(*pipes), GFP_KERNEL); + if (!pipes) + return -ENOMEM; + memcpy(pipes, dev->pipes, sizeof(*pipes) * dev->pipes_capacity); + kfree(dev->pipes); + dev->pipes = pipes; + id = dev->pipes_capacity; + dev->pipes_capacity = new_capacity; + } + return id; } /** - * goldfish_pipe_open - open a channel to the AVD + * goldfish_pipe_open - open a channel to the AVD * @inode: inode of device * @file: file struct of opener * @@ -525,12 +729,13 @@ static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) */ static int goldfish_pipe_open(struct inode *inode, struct file *file) { - struct goldfish_pipe *pipe; struct goldfish_pipe_dev *dev = pipe_dev; - int32_t status; + unsigned long flags; + int id; + int status; /* Allocate new pipe kernel object */ - pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); + struct goldfish_pipe *pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); if (pipe == NULL) return -ENOMEM; @@ -539,29 +744,69 @@ static int goldfish_pipe_open(struct inode *inode, struct file *file) init_waitqueue_head(&pipe->wake_queue); /* - * Now, tell the emulator we're opening a new pipe. We use the - * pipe object's address as the channel identifier for simplicity. + * Command buffer needs to be allocated on its own page to make sure + * it is physically contiguous in host's address space. */ + pipe->command_buffer = + (struct goldfish_pipe_command *)__get_free_page(GFP_KERNEL); + if (!pipe->command_buffer) { + status = -ENOMEM; + goto err_pipe; + } - status = goldfish_cmd_status(pipe, CMD_OPEN); - if (status < 0) { - kfree(pipe); - return status; + spin_lock_irqsave(&dev->lock, flags); + + id = get_free_pipe_id_locked(dev); + if (id < 0) { + status = id; + goto err_id_locked; } + dev->pipes[id] = pipe; + pipe->id = id; + pipe->command_buffer->id = id; + + /* Now tell the emulator we're opening a new pipe. */ + dev->buffers->open_command_params.rw_params_max_count = + MAX_BUFFERS_PER_COMMAND; + dev->buffers->open_command_params.command_buffer_ptr = + (u64)(unsigned long)__pa(pipe->command_buffer); + status = goldfish_cmd_locked(pipe, PIPE_CMD_OPEN); + spin_unlock_irqrestore(&dev->lock, flags); + if (status < 0) + goto err_cmd; /* All is done, save the pipe into the file's private data field */ file->private_data = pipe; return 0; + +err_cmd: + spin_lock_irqsave(&dev->lock, flags); + dev->pipes[id] = NULL; +err_id_locked: + spin_unlock_irqrestore(&dev->lock, flags); + free_page((unsigned long)pipe->command_buffer); +err_pipe: + kfree(pipe); + return status; } static int goldfish_pipe_release(struct inode *inode, struct file *filp) { + unsigned long flags; struct goldfish_pipe *pipe = filp->private_data; + struct goldfish_pipe_dev *dev = pipe->dev; /* The guest is closing the channel, so tell the emulator right now */ - goldfish_cmd(pipe, CMD_CLOSE); - kfree(pipe); + (void)goldfish_cmd(pipe, PIPE_CMD_CLOSE); + + spin_lock_irqsave(&dev->lock, flags); + dev->pipes[pipe->id] = NULL; + signalled_pipes_remove_locked(dev, pipe); + spin_unlock_irqrestore(&dev->lock, flags); + filp->private_data = NULL; + free_page((unsigned long)pipe->command_buffer); + kfree(pipe); return 0; } @@ -574,18 +819,91 @@ static const struct file_operations goldfish_pipe_fops = { .release = goldfish_pipe_release, }; -static struct miscdevice goldfish_pipe_device = { +static struct miscdevice goldfish_pipe_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "goldfish_pipe", .fops = &goldfish_pipe_fops, }; +static int goldfish_pipe_device_init(struct platform_device *pdev) +{ + char *page; + struct goldfish_pipe_dev *dev = pipe_dev; + int err = devm_request_irq(&pdev->dev, dev->irq, + goldfish_pipe_interrupt, + IRQF_SHARED, "goldfish_pipe", dev); + if (err) { + dev_err(&pdev->dev, "unable to allocate IRQ for v2\n"); + return err; + } + + err = misc_register(&goldfish_pipe_dev); + if (err) { + dev_err(&pdev->dev, "unable to register v2 device\n"); + return err; + } + + dev->first_signalled_pipe = NULL; + dev->pipes_capacity = INITIAL_PIPES_CAPACITY; + dev->pipes = kcalloc(dev->pipes_capacity, sizeof(*dev->pipes), + GFP_KERNEL); + if (!dev->pipes) + return -ENOMEM; + + /* + * We're going to pass two buffers, open_command_params and + * signalled_pipe_buffers, to the host. This means each of those buffers + * needs to be contained in a single physical page. The easiest choice + * is to just allocate a page and place the buffers in it. + */ + if (WARN_ON(sizeof(*dev->buffers) > PAGE_SIZE)) + return -ENOMEM; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + kfree(dev->pipes); + return -ENOMEM; + } + dev->buffers = (struct goldfish_pipe_dev_buffers *)page; + + /* Send the buffer addresses to the host */ + { + u64 paddr = __pa(&dev->buffers->signalled_pipe_buffers); + + writel((u32)(unsigned long)(paddr >> 32), + dev->base + PIPE_REG_SIGNAL_BUFFER_HIGH); + writel((u32)(unsigned long)paddr, + dev->base + PIPE_REG_SIGNAL_BUFFER); + writel((u32)MAX_SIGNALLED_PIPES, + dev->base + PIPE_REG_SIGNAL_BUFFER_COUNT); + + paddr = __pa(&dev->buffers->open_command_params); + writel((u32)(unsigned long)(paddr >> 32), + dev->base + PIPE_REG_OPEN_BUFFER_HIGH); + writel((u32)(unsigned long)paddr, + dev->base + PIPE_REG_OPEN_BUFFER); + } + return 0; +} + +static void goldfish_pipe_device_deinit(struct platform_device *pdev) +{ + struct goldfish_pipe_dev *dev = pipe_dev; + + misc_deregister(&goldfish_pipe_dev); + kfree(dev->pipes); + free_page((unsigned long)dev->buffers); +} + static int goldfish_pipe_probe(struct platform_device *pdev) { int err; struct resource *r; struct goldfish_pipe_dev *dev = pipe_dev; + if (WARN_ON(sizeof(struct goldfish_pipe_command) > PAGE_SIZE)) + return -ENOMEM; + /* not thread safe, but this should not happen */ WARN_ON(dev->base != NULL); @@ -609,26 +927,21 @@ static int goldfish_pipe_probe(struct platform_device *pdev) } dev->irq = r->start; - err = devm_request_irq(&pdev->dev, dev->irq, goldfish_pipe_interrupt, - IRQF_SHARED, "goldfish_pipe", dev); - if (err) { - dev_err(&pdev->dev, "unable to allocate IRQ\n"); - goto error; - } - - err = misc_register(&goldfish_pipe_device); - if (err) { - dev_err(&pdev->dev, "unable to register device\n"); - goto error; - } - setup_access_params_addr(pdev, dev); - - /* Although the pipe device in the classic Android emulator does not - * recognize the 'version' register, it won't treat this as an error - * either and will simply return 0, which is fine. + /* + * Exchange the versions with the host device + * + * Note: v1 driver used to not report its version, so we write it before + * reading device version back: this allows the host implementation to + * detect the old driver (if there was no version write before read). */ + writel((u32)PIPE_DRIVER_VERSION, dev->base + PIPE_REG_VERSION); dev->version = readl(dev->base + PIPE_REG_VERSION); - return 0; + if (WARN_ON(dev->version < PIPE_CURRENT_DEVICE_VERSION)) + return -EINVAL; + + err = goldfish_pipe_device_init(pdev); + if (!err) + return 0; error: dev->base = NULL; @@ -638,7 +951,7 @@ error: static int goldfish_pipe_remove(struct platform_device *pdev) { struct goldfish_pipe_dev *dev = pipe_dev; - misc_deregister(&goldfish_pipe_device); + goldfish_pipe_device_deinit(pdev); dev->base = NULL; return 0; } @@ -655,17 +968,16 @@ static const struct of_device_id goldfish_pipe_of_match[] = { }; MODULE_DEVICE_TABLE(of, goldfish_pipe_of_match); -static struct platform_driver goldfish_pipe = { +static struct platform_driver goldfish_pipe_driver = { .probe = goldfish_pipe_probe, .remove = goldfish_pipe_remove, .driver = { .name = "goldfish_pipe", - .owner = THIS_MODULE, .of_match_table = goldfish_pipe_of_match, .acpi_match_table = ACPI_PTR(goldfish_pipe_acpi_match), } }; -module_platform_driver(goldfish_pipe); +module_platform_driver(goldfish_pipe_driver); MODULE_AUTHOR("David Turner "); MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From f563dab41733c0e73202a8a5977b441715dc9946 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 26 Apr 2017 11:43:39 +0200 Subject: goldfish_pipe: fix build warning about using too much stack. The new goldfish_pipe code used too much stack space in the transfer_max_buffers() call. As the function is serialized with a lock, let's make the buffer static to not use the stack for the large buffer. Reported-by: kbuild test robot Cc: Yurii Zubrytskyi Cc: Jin Qian Signed-off-by: Greg Kroah-Hartman --- drivers/platform/goldfish/goldfish_pipe.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 9804e9e455d2..2de1e603bd2b 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -385,7 +385,7 @@ static int transfer_max_buffers(struct goldfish_pipe *pipe, unsigned long last_page, unsigned int last_page_size, s32 *consumed_size, int *status) { - struct page *pages[MAX_BUFFERS_PER_COMMAND]; + static struct page *pages[MAX_BUFFERS_PER_COMMAND]; unsigned long first_page = address & PAGE_MASK; unsigned int iter_last_page_size; int pages_count = pin_user_pages(first_page, last_page, @@ -409,10 +409,10 @@ static int transfer_max_buffers(struct goldfish_pipe *pipe, *consumed_size = pipe->command_buffer->rw_params.consumed_size; - mutex_unlock(&pipe->lock); - release_user_pages(pages, pages_count, is_write, *consumed_size); + mutex_unlock(&pipe->lock); + return 0; } -- cgit v1.2.3-70-g09d2 From 856c634dd204581710157d539a37b7340fb60894 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 26 Apr 2017 13:42:52 +0000 Subject: firmware: Google VPD: Fix return value check in vpd_platform_init() In case of error, the function platform_device_register_simple() returns ERR_PTR() and never returns NULL. The NULL test in the return value check should be replaced with IS_ERR(). Fixes: 049a59db34eb ("firmware: Google VPD sysfs driver") Signed-off-by: Wei Yongjun Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/vpd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index 619f4bae474f..3ce813110d5e 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -303,8 +303,8 @@ static int __init vpd_platform_init(void) struct platform_device *pdev; pdev = platform_device_register_simple("vpd", -1, NULL, 0); - if (!pdev) - return -ENODEV; + if (IS_ERR(pdev)) + return PTR_ERR(pdev); vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); if (!vpd_kobj) -- cgit v1.2.3-70-g09d2 From 2a76f89fa58c769241cfc21f2614705591519ae3 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 25 Apr 2017 03:11:48 +0000 Subject: firmware: google memconsole: Fix return value check in platform_memconsole_init() In case of error, the function platform_device_register_simple() returns ERR_PTR() and never returns NULL. The NULL test in the return value check should be replaced with IS_ERR(). Fixes: d384d6f43d1e ("firmware: google memconsole: Add coreboot support") Signed-off-by: Wei Yongjun Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/memconsole-coreboot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c index 21210144def7..02711114dece 100644 --- a/drivers/firmware/google/memconsole-coreboot.c +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -95,8 +95,8 @@ static int __init platform_memconsole_init(void) struct platform_device *pdev; pdev = platform_device_register_simple("memconsole", -1, NULL, 0); - if (pdev == NULL) - return -ENODEV; + if (IS_ERR(pdev)) + return PTR_ERR(pdev); platform_driver_register(&memconsole_driver); -- cgit v1.2.3-70-g09d2