diff --git b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt new file mode 100644 index 0000000..ebf0d47 --- /dev/null +++ b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt @@ -0,0 +1,48 @@ +The generic power sequence library + +Some hard-wired devices (eg USB/MMC) need to do power sequence before +the device can be enumerated on the bus, the typical power sequence +like: enable USB PHY clock, toggle reset pin, etc. But current +Linux device driver lacks of such code to do it, it may cause some +hard-wired devices works abnormal or can't be recognized by +controller at all. The power sequence will be done before this device +can be found at the bus. + +The power sequence properties is under the device node. + +Optional properties: +- clocks: the input clocks for device. +- reset-gpios: Should specify the GPIO for reset. +- reset-duration-us: the duration in microsecond for assert reset signal. + +Below is the example of USB power sequence properties on USB device +nodes which have two level USB hubs. + +&usbotg1 { + vbus-supply = <®_usb_otg1_vbus>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb_otg1_id>; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + genesys: hub@1 { + compatible = "usb5e3,608"; + reg = <1>; + + clocks = <&clks IMX6SX_CLK_CKO>; + reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* hub reset pin */ + reset-duration-us = <10>; + + #address-cells = <1>; + #size-cells = <0>; + asix: ethernet@1 { + compatible = "usbb95,1708"; + reg = <1>; + + clocks = <&clks IMX6SX_CLK_IPG>; + reset-gpios = <&gpio4 6 GPIO_ACTIVE_LOW>; /* ethernet_rst */ + reset-duration-us = <15>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/usb/usb-device.txt b/Documentation/devicetree/bindings/usb/usb-device.txt index 036be17..cb85f82 100644 --- a/Documentation/devicetree/bindings/usb/usb-device.txt +++ b/Documentation/devicetree/bindings/usb/usb-device.txt @@ -65,6 +65,9 @@ Required properties for host-controller nodes with device nodes: - #address-cells: shall be 1 - #size-cells: shall be 0 +Optional properties: +power sequence properties, see +Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt for detail Example: @@ -72,9 +75,13 @@ Example: #address-cells = <1>; #size-cells = <0>; - hub@1 { /* hub connected to port 1 */ + genesys: hub@1 { /* hub connected to port 1 */ compatible = "usb5e3,608"; reg = <1>; + + clocks = <&clks IMX6SX_CLK_CKO>; + reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* hub reset pin */ + reset-duration-us = <10>; }; device@2 { /* device connected to port 2 */ diff --git b/Documentation/power/power-sequence/design.rst b/Documentation/power/power-sequence/design.rst new file mode 100644 index 0000000..554608e --- /dev/null +++ b/Documentation/power/power-sequence/design.rst @@ -0,0 +1,54 @@ +==================================== +Power Sequence Library +==================================== + +:Date: Feb, 2017 +:Author: Peter Chen + + +Introduction +============ + +We have an well-known problem that the device needs to do a power +sequence before it can be recognized by related host, the typical +examples are hard-wired mmc devices and usb devices. The host controller +can't know what kinds of this device is in its bus if the power +sequence has not done, since the related devices driver's probe calling +is determined by runtime according to eunumeration results. Besides, +the devices may have custom power sequence, so the power sequence library +which is independent with the devices is needed. + +Design +============ + +The power sequence library includes the core file and customer power +sequence library. The core file exports interfaces are called by +host controller driver for power sequence and customer power sequence +library files to register its power sequence instance to global +power sequence list. The custom power sequence library creates power +sequence instance and implement custom power sequence. + +Since the power sequence describes hardware design, the description is +located at board description file, eg, device tree dts file. And +a specific power sequence belongs to device, so its description +is under the device node, please refer to: +Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt + +Custom power sequence library allocates one power sequence instance at +bootup periods using postcore_initcall, this static allocated instance is +used to compare with device-tree (DT) node to see if this library can be +used for the node or not. When the result is matched, the core API will +try to get resourses (->get, implemented at each library) for power +sequence, if all resources are got, it will try to allocate another +instance for next possible request from host driver. + +Then, the host controller driver can carry out power sequence on for this +DT node, the library will do corresponding operations, like open clocks, +toggle gpio, etc. The power sequence off routine will close and free the +resources, and is called when the parent is removed. And the power +sequence suspend and resume routine can be called at host driver's +suspend and resume routine if needed. + +The exported interfaces +.. kernel-doc:: drivers/power/pwrseq/core.c + :export: diff --git a/MAINTAINERS b/MAINTAINERS index 8671573..29fc387 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13890,6 +13890,15 @@ F: drivers/firmware/psci/ F: include/linux/psci.h F: include/uapi/linux/psci.h +POWER SEQUENCE LIBRARY +M: Peter Chen +T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git +L: linux-pm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/power/pwrseq/ +F: drivers/power/pwrseq/ +F: include/linux/power/pwrseq.h + POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS M: Sebastian Reichel L: linux-pm@vger.kernel.org diff --git a/arch/arm/boot/Makefile b/arch/arm/boot/Makefile index 0b3cd7a..87a8b8d 100644 --- a/arch/arm/boot/Makefile +++ b/arch/arm/boot/Makefile @@ -29,6 +29,10 @@ export ZRELADDR INITRD_PHYS PARAMS_PHYS targets := Image zImage xipImage bootpImage uImage +ifeq ($(CONFIG_OF_OVERLAY),y) +DTC_FLAGS += -@ +endif + ifeq ($(CONFIG_XIP_KERNEL),y) cmd_deflate_xip_data = $(CONFIG_SHELL) -c \ diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 4572db3..f5fe7b7 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -1,4 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 + +ifeq ($(CONFIG_OF_OVERLAY),y) +DTC_FLAGS += -@ +endif + dtb-$(CONFIG_ARCH_ALPINE) += \ alpine-db.dtb dtb-$(CONFIG_MACH_ARTPEC6) += \ diff --git a/arch/arm/boot/dts/imx6q-evi.dts b/arch/arm/boot/dts/imx6q-evi.dts index c63f371..546d9d4 100644 --- a/arch/arm/boot/dts/imx6q-evi.dts +++ b/arch/arm/boot/dts/imx6q-evi.dts @@ -55,18 +55,6 @@ reg = <0x10000000 0x40000000>; }; - reg_usbh1_vbus: regulator-usbhubreset { - compatible = "regulator-fixed"; - regulator-name = "usbh1_vbus"; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - enable-active-high; - startup-delay-us = <2>; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_usbh1_hubreset>; - gpio = <&gpio7 12 GPIO_ACTIVE_HIGH>; - }; - reg_usb_otg_vbus: regulator-usbotgvbus { compatible = "regulator-fixed"; regulator-name = "usb_otg_vbus"; @@ -214,12 +202,18 @@ }; &usbh1 { - vbus-supply = <®_usbh1_vbus>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usbh1>; dr_mode = "host"; disable-over-current; status = "okay"; + + usb2415host: hub@1 { + compatible = "usb424,2513"; + reg = <1>; + reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>; + reset-duration-us = <3000>; + }; }; &usbotg { @@ -482,11 +476,6 @@ MX6QDL_PAD_GPIO_3__USB_H1_OC 0x1b0b0 /* usbh1_b OC */ MX6QDL_PAD_GPIO_0__GPIO1_IO00 0x1b0b0 - >; - }; - - pinctrl_usbh1_hubreset: usbh1hubresetgrp { - fsl,pins = < MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x1b0b0 >; }; diff --git a/arch/arm/boot/dts/imx6qdl-udoo.dtsi b/arch/arm/boot/dts/imx6qdl-udoo.dtsi index 828dd20..a6cf7c9 100644 --- a/arch/arm/boot/dts/imx6qdl-udoo.dtsi +++ b/arch/arm/boot/dts/imx6qdl-udoo.dtsi @@ -5,6 +5,8 @@ * Author: Fabio Estevam */ +#include + / { aliases { backlight = &backlight; @@ -62,17 +64,6 @@ #address-cells = <1>; #size-cells = <0>; - reg_usb_h1_vbus: regulator@0 { - compatible = "regulator-fixed"; - reg = <0>; - regulator-name = "usb_h1_vbus"; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - enable-active-high; - startup-delay-us = <2>; /* USB2415 requires a POR of 1 us minimum */ - gpio = <&gpio7 12 0>; - }; - reg_panel: regulator@1 { compatible = "regulator-fixed"; reg = <1>; @@ -93,6 +84,17 @@ mux-int-port = <1>; mux-ext-port = <6>; }; + + udoo_ard: udoo_ard_manager { + compatible = "udoo,imx6q-udoo-ard"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_udooard>; + bossac-clk-gpio = <&gpio6 3 0>; + bossac-dat-gpio = <&gpio5 18 0>; + bossac-erase-gpio = <&gpio4 21 0>; + bossac-reset-gpio = <&gpio1 0 0>; + status = "okay"; + }; }; &fec { @@ -205,7 +207,7 @@ pinctrl_usbh: usbhgrp { fsl,pins = < - MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x80000000 + MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x1b0b0 MX6QDL_PAD_NANDF_CS2__CCM_CLKO2 0x130b0 >; }; @@ -218,6 +220,15 @@ >; }; + pinctrl_udooard: udooardgrp { + fsl,pins = < + MX6QDL_PAD_DISP0_DAT0__GPIO4_IO21 0x80000000 + MX6QDL_PAD_CSI0_DAT17__GPIO6_IO03 0x80000000 + MX6QDL_PAD_CSI0_PIXCLK__GPIO5_IO18 0x80000000 + MX6QDL_PAD_GPIO_0__GPIO1_IO00 0x80000000 + >; + }; + pinctrl_usdhc3: usdhc3grp { fsl,pins = < MX6QDL_PAD_SD3_CMD__SD3_CMD 0x17059 @@ -290,9 +301,16 @@ &usbh1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usbh>; - vbus-supply = <®_usb_h1_vbus>; - clocks = <&clks IMX6QDL_CLK_CKO>; status = "okay"; + + usb2415: hub@1 { + compatible = "usb424,2514"; + reg = <1>; + + clocks = <&clks IMX6QDL_CLK_CKO>; + reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>; + reset-duration-us = <3000>; + }; }; &usbotg { diff --git a/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi b/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi index e781a45..68daf55 100644 --- a/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi +++ b/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi @@ -6,6 +6,24 @@ #include "imx6qdl-wandboard.dtsi" +/ { + rfkill { + compatible = "wand,imx6qdl-wandboard-rfkill"; + pinctrl-names = "default"; + pinctrl-0 = <>; + + bluetooth-on = <&gpio3 13 0>; + bluetooth-wake = <&gpio3 14 0>; + bluetooth-host-wake = <&gpio3 15 0>; + + wifi-ref-on = <&gpio2 29 0>; + wifi-rst-n = <&gpio5 2 0>; + wifi-reg-on = <&gpio1 26 0>; + wifi-host-wake = <&gpio1 29 0>; + wifi-wake = <&gpio1 30 0>; + }; +}; + &iomuxc { pinctrl-0 = <&pinctrl_hog>; @@ -31,6 +49,5 @@ &usdhc2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usdhc2>; - non-removable; status = "okay"; }; diff --git a/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi b/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi index 3874e74..be5445d 100644 --- a/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi +++ b/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi @@ -6,6 +6,24 @@ #include "imx6qdl-wandboard.dtsi" +/ { + rfkill { + compatible = "wand,imx6qdl-wandboard-rfkill"; + pinctrl-names = "default"; + pinctrl-0 = <>; + + bluetooth-on = <&gpio5 21 0>; + bluetooth-wake = <&gpio5 30 0>; + bluetooth-host-wake = <&gpio5 20 0>; + + wifi-ref-on = <&gpio5 31 0>; /* Wifi Power Enable */ + wifi-rst-n = <&gpio6 0 0>; /* WIFI_ON reset */ + wifi-reg-on = <&gpio1 26 0>; /* WL_REG_ON */ + wifi-host-wake = <&gpio1 29 0>; /* WL_HOST_WAKE */ + wifi-wake = <&gpio1 30 0>; /* WL_WAKE */ + }; +}; + &iomuxc { pinctrl-0 = <&pinctrl_hog>; diff --git a/arch/arm/boot/dts/imx6qdl-wandboard-revd1.dtsi b/arch/arm/boot/dts/imx6qdl-wandboard-revd1.dtsi index 9390979..d595f4e 100644 --- a/arch/arm/boot/dts/imx6qdl-wandboard-revd1.dtsi +++ b/arch/arm/boot/dts/imx6qdl-wandboard-revd1.dtsi @@ -7,6 +7,26 @@ #include "imx6qdl-wandboard.dtsi" / { + rfkill { + compatible = "wand,imx6qdl-wandboard-rfkill"; + pinctrl-names = "default"; + pinctrl-0 = <>; + + bluetooth-on = <&gpio5 30 0>; /* BT_RST_N */ + bluetooth-wake = <&gpio5 21 0>; /* BT_WAKE */ + bluetooth-host-wake = <&gpio5 20 0>; /* BT_HOST_WAKE */ + + wifi-ref-on = <&gpio6 0 0>; /* WIFI_ON: M4: CSI0_DAT14 */ + wifi-reg-on = <&gpio1 26 0>; /* WL_REG_ON: W22: ENET_RXD1 */ + wifi-host-wake = <&gpio1 29 0>; /* WL_HOST_WAKE: W20: ENET_TXD1 */ + + //HACK: use un-populated pins for missing rfkill pins, dont want to fix the driver... + //GPIO6_IO17 - SD3_DAT7 + //GPIO6_IO18 - SD3_DAT6 + wifi-rst-n = <&gpio6 17 0>; /* NOT POPULATED */ + wifi-wake = <&gpio6 18 0>; /* NOT POPULATED */ + }; + reg_eth_phy: regulator-eth-phy { compatible = "regulator-fixed"; regulator-name = "ETH_PHY"; diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi index 23d4ec0..8fec548 100644 --- a/arch/arm/boot/dts/imx6qdl.dtsi +++ b/arch/arm/boot/dts/imx6qdl.dtsi @@ -986,6 +986,8 @@ usbh1: usb@2184200 { compatible = "fsl,imx6q-usb", "fsl,imx27-usb"; + #address-cells = <1>; + #size-cells = <0>; reg = <0x02184200 0x200>; interrupts = <0 40 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_USBOH3>; @@ -1000,6 +1002,8 @@ usbh2: usb@2184400 { compatible = "fsl,imx6q-usb", "fsl,imx27-usb"; + #address-cells = <1>; + #size-cells = <0>; reg = <0x02184400 0x200>; interrupts = <0 41 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_USBOH3>; @@ -1015,6 +1019,8 @@ usbh3: usb@2184600 { compatible = "fsl,imx6q-usb", "fsl,imx27-usb"; + #address-cells = <1>; + #size-cells = <0>; reg = <0x02184600 0x200>; interrupts = <0 42 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_USBOH3>; diff --git a/arch/arm/boot/dts/imx7d-pico-pi.dts b/arch/arm/boot/dts/imx7d-pico-pi.dts index 70bea95..a50edba 100644 --- a/arch/arm/boot/dts/imx7d-pico-pi.dts +++ b/arch/arm/boot/dts/imx7d-pico-pi.dts @@ -8,6 +8,10 @@ model = "TechNexion PICO-IMX7D Board and PI baseboard"; compatible = "technexion,imx7d-pico-pi", "fsl,imx7d"; + chosen { + stdout-path = "serial4:115200n8"; + }; + leds { compatible = "gpio-leds"; pinctrl-names = "default"; diff --git a/arch/arm/mach-imx/devices/Kconfig b/arch/arm/mach-imx/devices/Kconfig index fdca73d..95e0e8e 100644 --- a/arch/arm/mach-imx/devices/Kconfig +++ b/arch/arm/mach-imx/devices/Kconfig @@ -69,3 +69,9 @@ config IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX config IMX_HAVE_PLATFORM_SPI_IMX bool + +config WAND_RFKILL + tristate "Wandboard RF Kill support" + depends on SOC_IMX6Q + default m + select RFKILL diff --git a/arch/arm/mach-imx/devices/Makefile b/arch/arm/mach-imx/devices/Makefile index e44758a..c179a55 100644 --- a/arch/arm/mach-imx/devices/Makefile +++ b/arch/arm/mach-imx/devices/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_IMX_HAVE_PLATFORM_MXC_W1) += platform-mxc_w1.o obj-$(CONFIG_IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX) += platform-sdhci-esdhc-imx.o obj-$(CONFIG_IMX_HAVE_PLATFORM_SPI_IMX) += platform-spi_imx.o obj-$(CONFIG_IMX_HAVE_PLATFORM_MX2_EMMA) += platform-mx2-emma.o +obj-$(CONFIG_WAND_RFKILL) += wand-rfkill.o diff --git b/arch/arm/mach-imx/devices/wand-rfkill.c b/arch/arm/mach-imx/devices/wand-rfkill.c new file mode 100644 index 0000000..da7ef9f --- /dev/null +++ b/arch/arm/mach-imx/devices/wand-rfkill.c @@ -0,0 +1,290 @@ +/* + * arch/arm/mach-imx/devices/wand-rfkill.c + * + * Copyright (C) 2013 Vladimir Ermakov + * + * based on net/rfkill/rfkill-gpio.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct wand_rfkill_data { + struct rfkill *rfkill_dev; + int shutdown_gpio; + const char *shutdown_name; +}; + +static int wand_rfkill_set_block(void *data, bool blocked) +{ + struct wand_rfkill_data *rfkill = data; + + pr_debug("wandboard-rfkill: set block %d\n", blocked); + + if (blocked) { + if (gpio_is_valid(rfkill->shutdown_gpio)) + gpio_direction_output(rfkill->shutdown_gpio, 0); + } else { + if (gpio_is_valid(rfkill->shutdown_gpio)) + gpio_direction_output(rfkill->shutdown_gpio, 1); + } + + return 0; +} + +static const struct rfkill_ops wand_rfkill_ops = { + .set_block = wand_rfkill_set_block, +}; + +static int wand_rfkill_wifi_probe(struct device *dev, + struct device_node *np, + struct wand_rfkill_data *rfkill) +{ + int ret; + int wl_ref_on, wl_rst_n, wl_reg_on, wl_wake, wl_host_wake; + + wl_ref_on = of_get_named_gpio(np, "wifi-ref-on", 0); + wl_rst_n = of_get_named_gpio(np, "wifi-rst-n", 0); + wl_reg_on = of_get_named_gpio(np, "wifi-reg-on", 0); + wl_wake = of_get_named_gpio(np, "wifi-wake", 0); + wl_host_wake = of_get_named_gpio(np, "wifi-host-wake", 0); + + if (!gpio_is_valid(wl_rst_n) || !gpio_is_valid(wl_ref_on) || + !gpio_is_valid(wl_reg_on) || !gpio_is_valid(wl_wake) || + !gpio_is_valid(wl_host_wake)) { + + dev_err(dev, "incorrect wifi gpios (%d %d %d %d %d)\n", + wl_rst_n, wl_ref_on, wl_reg_on, wl_wake, wl_host_wake); + return -EINVAL; + } + + dev_info(dev, "initialize wifi chip\n"); + + gpio_request(wl_rst_n, "wl_rst_n"); + gpio_direction_output(wl_rst_n, 0); + msleep(11); + gpio_set_value(wl_rst_n, 1); + + gpio_request(wl_ref_on, "wl_ref_on"); + gpio_direction_output(wl_ref_on, 1); + + gpio_request(wl_reg_on, "wl_reg_on"); + gpio_direction_output(wl_reg_on, 1); + + gpio_request(wl_wake, "wl_wake"); + gpio_direction_output(wl_wake, 1); + + gpio_request(wl_host_wake, "wl_host_wake"); + gpio_direction_input(wl_host_wake); + + rfkill->shutdown_name = "wifi_shutdown"; + rfkill->shutdown_gpio = wl_wake; + + rfkill->rfkill_dev = rfkill_alloc("wifi-rfkill", dev, RFKILL_TYPE_WLAN, + &wand_rfkill_ops, rfkill); + if (!rfkill->rfkill_dev) { + ret = -ENOMEM; + goto wifi_fail_free_gpio; + } + + ret = rfkill_register(rfkill->rfkill_dev); + if (ret < 0) + goto wifi_fail_unregister; + + dev_info(dev, "wifi-rfkill registered.\n"); + + return 0; + +wifi_fail_unregister: + rfkill_destroy(rfkill->rfkill_dev); +wifi_fail_free_gpio: + if (gpio_is_valid(wl_rst_n)) gpio_free(wl_rst_n); + if (gpio_is_valid(wl_ref_on)) gpio_free(wl_ref_on); + if (gpio_is_valid(wl_reg_on)) gpio_free(wl_reg_on); + if (gpio_is_valid(wl_wake)) gpio_free(wl_wake); + if (gpio_is_valid(wl_host_wake)) gpio_free(wl_host_wake); + + return ret; +} + +static int wand_rfkill_bt_probe(struct device *dev, + struct device_node *np, + struct wand_rfkill_data *rfkill) +{ + int ret; + int bt_on, bt_wake, bt_host_wake; + + bt_on = of_get_named_gpio(np, "bluetooth-on", 0); + bt_wake = of_get_named_gpio(np, "bluetooth-wake", 0); + bt_host_wake = of_get_named_gpio(np, "bluetooth-host-wake", 0); + + if (!gpio_is_valid(bt_on) || !gpio_is_valid(bt_wake) || + !gpio_is_valid(bt_host_wake)) { + + dev_err(dev, "incorrect bt gpios (%d %d %d)\n", + bt_on, bt_wake, bt_host_wake); + return -EINVAL; + } + + dev_info(dev, "initialize bluetooth chip\n"); + + gpio_request(bt_on, "bt_on"); + gpio_direction_output(bt_on, 0); + msleep(11); + gpio_set_value(bt_on, 1); + + gpio_request(bt_wake, "bt_wake"); + gpio_direction_output(bt_wake, 1); + + gpio_request(bt_host_wake, "bt_host_wake"); + gpio_direction_input(bt_host_wake); + + rfkill->shutdown_name = "bluetooth_shutdown"; + rfkill->shutdown_gpio = bt_wake; + + rfkill->rfkill_dev = rfkill_alloc("bluetooth-rfkill", dev, RFKILL_TYPE_BLUETOOTH, + &wand_rfkill_ops, rfkill); + if (!rfkill->rfkill_dev) { + ret = -ENOMEM; + goto bt_fail_free_gpio; + } + + ret = rfkill_register(rfkill->rfkill_dev); + if (ret < 0) + goto bt_fail_unregister; + + dev_info(dev, "bluetooth-rfkill registered.\n"); + + return 0; + +bt_fail_unregister: + rfkill_destroy(rfkill->rfkill_dev); +bt_fail_free_gpio: + if (gpio_is_valid(bt_on)) gpio_free(bt_on); + if (gpio_is_valid(bt_wake)) gpio_free(bt_wake); + if (gpio_is_valid(bt_host_wake)) gpio_free(bt_host_wake); + + return ret; +} + +static int wand_rfkill_probe(struct platform_device *pdev) +{ + struct wand_rfkill_data *rfkill; + struct pinctrl *pinctrl; + int ret; + + dev_info(&pdev->dev, "Wandboard rfkill initialization\n"); + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "no device tree node\n"); + return -ENODEV; + } + + rfkill = kzalloc(sizeof(*rfkill) * 2, GFP_KERNEL); + if (!rfkill) + return -ENOMEM; + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + int ret = PTR_ERR(pinctrl); + dev_err(&pdev->dev, "failed to get default pinctrl: %d\n", ret); + return ret; + } + + /* setup WiFi */ + ret = wand_rfkill_wifi_probe(&pdev->dev, pdev->dev.of_node, &rfkill[0]); + if (ret < 0) + goto fail_free_rfkill; + + /* setup bluetooth */ + ret = wand_rfkill_bt_probe(&pdev->dev, pdev->dev.of_node, &rfkill[1]); + if (ret < 0) + goto fail_unregister_wifi; + + platform_set_drvdata(pdev, rfkill); + + return 0; + +fail_unregister_wifi: + if (rfkill[1].rfkill_dev) { + rfkill_unregister(rfkill[1].rfkill_dev); + rfkill_destroy(rfkill[1].rfkill_dev); + } + + /* TODO free gpio */ + +fail_free_rfkill: + kfree(rfkill); + + return ret; +} + +static int wand_rfkill_remove(struct platform_device *pdev) +{ + struct wand_rfkill_data *rfkill = platform_get_drvdata(pdev); + + dev_info(&pdev->dev, "Module unloading\n"); + + if (!rfkill) + return 0; + + /* WiFi */ + if (gpio_is_valid(rfkill[0].shutdown_gpio)) + gpio_free(rfkill[0].shutdown_gpio); + + rfkill_unregister(rfkill[0].rfkill_dev); + rfkill_destroy(rfkill[0].rfkill_dev); + + /* Bt */ + if (gpio_is_valid(rfkill[1].shutdown_gpio)) + gpio_free(rfkill[1].shutdown_gpio); + + rfkill_unregister(rfkill[1].rfkill_dev); + rfkill_destroy(rfkill[1].rfkill_dev); + + kfree(rfkill); + + return 0; +} + +static struct of_device_id wand_rfkill_match[] = { + { .compatible = "wand,imx6q-wandboard-rfkill", }, + { .compatible = "wand,imx6dl-wandboard-rfkill", }, + { .compatible = "wand,imx6qdl-wandboard-rfkill", }, + {} +}; + +static struct platform_driver wand_rfkill_driver = { + .driver = { + .name = "wandboard-rfkill", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(wand_rfkill_match), + }, + .probe = wand_rfkill_probe, + .remove = wand_rfkill_remove +}; + +module_platform_driver(wand_rfkill_driver); + +MODULE_AUTHOR("Vladimir Ermakov "); +MODULE_DESCRIPTION("Wandboard rfkill driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/Makefile b/drivers/Makefile index c0cd1b9..da81dab 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -56,6 +56,9 @@ obj-$(CONFIG_RESET_CONTROLLER) += reset/ obj-y += tty/ obj-y += char/ +# put mmc early as many morden devices use emm/sd card as rootfs storage +obj-y += mmc/ + # iommu/ comes before gpu as gpu are using iommu controllers obj-y += iommu/ @@ -128,7 +131,6 @@ obj-$(CONFIG_EISA) += eisa/ obj-$(CONFIG_PM_OPP) += opp/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ -obj-y += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-$(CONFIG_NEW_LEDS) += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ diff --git a/drivers/gnss/serial.c b/drivers/gnss/serial.c index def64b3..473faee 100644 --- a/drivers/gnss/serial.c +++ b/drivers/gnss/serial.c @@ -110,10 +110,9 @@ static int gnss_serial_set_power(struct gnss_serial *gserial, static int gnss_serial_parse_dt(struct serdev_device *serdev) { struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); - struct device_node *node = serdev->dev.of_node; u32 speed = 4800; - of_property_read_u32(node, "current-speed", &speed); + device_property_read_u32(&serdev->dev, "current-speed", &speed); gserial->speed = speed; diff --git a/drivers/gnss/ubx.c b/drivers/gnss/ubx.c index 7b05bc4..e50056c 100644 --- a/drivers/gnss/ubx.c +++ b/drivers/gnss/ubx.c @@ -138,6 +138,14 @@ static const struct of_device_id ubx_of_match[] = { MODULE_DEVICE_TABLE(of, ubx_of_match); #endif +static const struct serdev_device_id ubx_serdev_id[] = { + { "neo-6m", }, + { "neo-8", }, + { "neo-m8", }, + {} +}; +MODULE_DEVICE_TABLE(serdev, ubx_serdev_id); + static struct serdev_device_driver ubx_driver = { .driver = { .name = "gnss-ubx", @@ -146,6 +154,7 @@ static struct serdev_device_driver ubx_driver = { }, .probe = ubx_probe, .remove = ubx_remove, + .id_table = ubx_serdev_id, }; module_serdev_device_driver(ubx_driver); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 8030fd9..71f6ccb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -69,6 +69,20 @@ config GPIO_SYSFS ioctl() operations instead. The character device is always available. +config GPIO_OF_HELPER + bool "GPIO OF helper device (EXPERIMENTAL)" + depends on OF_GPIO + help + Say Y here to add an GPIO OF helper driver + + Allows you specify a GPIO helper based on OF + which allows simple export of GPIO functionality + in user-space. + + Features include, value set/get, direction control, + interrupt/value change poll support, event counting + and others. + config GPIO_GENERIC depends on HAS_IOMEM # Only for IOMEM drivers tristate diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 4f9abff..5d2fbba 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib-cdev.o obj-$(CONFIG_OF_GPIO) += gpiolib-of.o obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o +obj-$(CONFIG_GPIO_OF_HELPER) += gpio-of-helper.o # Device drivers. Generally keep list sorted alphabetically obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o diff --git b/drivers/gpio/gpio-of-helper.c b/drivers/gpio/gpio-of-helper.c new file mode 100644 index 0000000..83f362f --- /dev/null +++ b/drivers/gpio/gpio-of-helper.c @@ -0,0 +1,435 @@ +/* + * GPIO OF based helper + * + * A simple DT based driver to provide access to GPIO functionality + * to user-space via sysfs. + * + * Copyright (C) 2013 Pantelis Antoniou + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* fwd decl. */ +struct gpio_of_helper_info; + +enum gpio_type { + GPIO_TYPE_INPUT = 0, + GPIO_TYPE_OUTPUT = 1, +}; + +struct gpio_of_entry { + int id; + struct gpio_of_helper_info *info; + struct device_node *node; + enum gpio_type type; + int gpio; + int irq; + const char *name; + atomic64_t counter; + unsigned int count_flags; +#define COUNT_RISING_EDGE (1 << 0) +#define COUNT_FALLING_EDGE (1 << 1) +}; + +struct gpio_of_helper_info { + struct platform_device *pdev; + struct idr idr; +}; + +static const struct of_device_id gpio_of_helper_of_match[] = { + { + .compatible = "gpio-of-helper", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, gpio_of_helper_of_match); + +static ssize_t gpio_of_helper_show_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_of_helper_info *info = platform_get_drvdata(pdev); + struct gpio_of_entry *entry; + char *p, *e; + int id, n; + + p = buf; + e = p + PAGE_SIZE; + n = 0; + idr_for_each_entry(&info->idr, entry, id) { + switch (entry->type) { + case GPIO_TYPE_INPUT: + n = snprintf(p, e - p, "%2d %-24s %3d %-3s %llu\n", + entry->id, entry->name, entry->gpio, "IN", + (unsigned long long) + atomic64_read(&entry->counter)); + break; + case GPIO_TYPE_OUTPUT: + n = snprintf(p, e - p, "%2d %-24s %3d %-3s\n", + entry->id, entry->name, entry->gpio, "OUT"); + break; + } + p += n; + } + + return p - buf; +} + +static DEVICE_ATTR(status, S_IRUGO, + gpio_of_helper_show_status, NULL); + +static irqreturn_t gpio_of_helper_handler(int irq, void *ptr) +{ + struct gpio_of_entry *entry = ptr; + + /* caution - low speed interfaces only! */ + atomic64_inc(&entry->counter); + + return IRQ_HANDLED; +} + +static struct gpio_of_entry * +gpio_of_entry_create(struct gpio_of_helper_info *info, + struct device_node *node) +{ + struct platform_device *pdev = info->pdev; + struct device *dev = &pdev->dev; + struct gpio_of_entry *entry; + int err, gpio, irq; + unsigned int req_flags, count_flags, irq_flags; + enum gpio_type type; + enum of_gpio_flags gpio_flags; + const char *name; + + /* get the type of the node first */ + if (of_property_read_bool(node, "input")) + type = GPIO_TYPE_INPUT; + else if (of_property_read_bool(node, "output") + || of_property_read_bool(node, "init-low") + || of_property_read_bool(node, "init-high")) + type = GPIO_TYPE_OUTPUT; + else { + dev_err(dev, "Not valid gpio node type\n"); + err = -EINVAL; + goto err_bad_node; + } + + /* get the name */ + if (of_property_read_string(node, "line-name", &name)) + if (of_property_read_string(node, "gpio-name", &name)) + name = node->name; + + err = of_get_named_gpio_flags(node, "gpio", 0, &gpio_flags); + if (IS_ERR_VALUE(err)) { + dev_err(dev, "Failed to get gpio property of '%s'\n", name); + goto err_bad_node; + } + gpio = err; + + req_flags = 0; + count_flags = 0; + + /* set the request flags */ + switch (type) { + case GPIO_TYPE_INPUT: + req_flags = GPIOF_DIR_IN | GPIOF_EXPORT; + if (of_property_read_bool(node, "count-falling-edge")) + count_flags |= COUNT_FALLING_EDGE; + if (of_property_read_bool(node, "count-rising-edge")) + count_flags |= COUNT_RISING_EDGE; + break; + case GPIO_TYPE_OUTPUT: + req_flags = GPIOF_DIR_OUT | GPIOF_EXPORT; + if (of_property_read_bool(node, "init-high")) + req_flags |= GPIOF_OUT_INIT_HIGH; + else if (of_property_read_bool(node, "init-low")) + req_flags |= GPIOF_OUT_INIT_LOW; + break; + } + if (of_property_read_bool(node, "dir-changeable")) + req_flags |= GPIOF_EXPORT_CHANGEABLE; + if (gpio_flags & OF_GPIO_ACTIVE_LOW) + req_flags |= GPIOF_ACTIVE_LOW; + if (gpio_flags & OF_GPIO_SINGLE_ENDED) { + if (gpio_flags & OF_GPIO_ACTIVE_LOW) + req_flags |= GPIOF_OPEN_DRAIN; + else + req_flags |= GPIOF_OPEN_SOURCE; + } + + /* request the gpio */ + err = devm_gpio_request_one(dev, gpio, req_flags, name); + if (err != 0) { + dev_err(dev, "Failed to request gpio '%s'\n", name); + goto err_bad_node; + } + + irq = -1; + irq_flags = 0; + + /* counter mode requested - need an interrupt */ + if (count_flags != 0) { + irq = gpio_to_irq(gpio); + if (IS_ERR_VALUE(irq)) { + dev_err(dev, "Failed to request gpio '%s'\n", name); + goto err_bad_node; + } + + if (count_flags & COUNT_RISING_EDGE) + irq_flags |= IRQF_TRIGGER_RISING; + if (count_flags & COUNT_FALLING_EDGE) + irq_flags |= IRQF_TRIGGER_FALLING; + } + +// if (!idr_pre_get(&info->idr, GFP_KERNEL)) { +// dev_err(dev, "Failed on idr_pre_get of '%s'\n", name); +// err = -ENOMEM; +// goto err_no_mem; +// } + + idr_preload(GFP_KERNEL); + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (entry == NULL) { + dev_err(dev, "Failed to allocate gpio entry of '%s'\n", name); + err = -ENOMEM; + goto err_no_mem; + } + + entry->id = -1; + entry->info = info; + entry->node = of_node_get(node); /* get node reference */ + entry->type = type; + entry->gpio = gpio; + entry->irq = irq; + entry->name = name; + + /* interrupt enable is last thing done */ + if (irq >= 0) { + atomic64_set(&entry->counter, 0); + entry->count_flags = count_flags; + err = devm_request_irq(dev, irq, gpio_of_helper_handler, + irq_flags, name, entry); + if (err != 0) { + dev_err(dev, "Failed to request irq of '%s'\n", name); + goto err_no_irq; + } + } + + /* all done; insert */ +// err = idr_get_new(&info->idr, entry, &entry->id); +// if (IS_ERR_VALUE(err)) { +// dev_err(dev, "Failed to idr_get_new of '%s'\n", name); +// goto err_fail_idr; +// } + + err = idr_alloc(&info->idr, entry, 0, 0, GFP_NOWAIT); + if (err >= 0) + entry->id = err; + + idr_preload_end(); + + if (err < 0) { + dev_err(dev, "Failed to idr_get_new of '%s'\n", name); + goto err_fail_idr; + } + + dev_dbg(dev, "Allocated GPIO id=%d name='%s'\n", entry->id, name); + + return entry; + +err_fail_idr: + /* nothing to do */ +err_no_irq: + /* release node ref */ + of_node_put(node); + /* nothing else needs to be done, devres handles it */ +err_no_mem: +err_bad_node: + return ERR_PTR(err); +} + +static int gpio_of_entry_destroy(struct gpio_of_entry *entry) +{ + struct gpio_of_helper_info *info = entry->info; + struct platform_device *pdev = info->pdev; + struct device *dev = &pdev->dev; + + dev_dbg(dev, "Destroying GPIO id=%d\n", entry->id); + + /* remove from the IDR */ + idr_remove(&info->idr, entry->id); + + /* remove node ref */ + of_node_put(entry->node); + + /* free gpio */ + devm_gpio_free(dev, entry->gpio); + + /* gree irq */ + if (entry->irq >= 0) + devm_free_irq(dev, entry->irq, entry); + + /* and free */ + devm_kfree(dev, entry); + + return 0; +} + +static int gpio_of_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_of_helper_info *info; + struct gpio_of_entry *entry; + struct device_node *pnode = pdev->dev.of_node; + struct device_node *cnode; + struct pinctrl *pinctrl; + int err; + + /* we only support OF */ + if (pnode == NULL) { + dev_err(&pdev->dev, "No platform of_node!\n"); + return -ENODEV; + } + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + /* special handling for probe defer */ + if (PTR_ERR(pinctrl) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_warn(&pdev->dev, + "pins are not configured from the driver\n"); + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + dev_err(&pdev->dev, "Failed to allocate info\n"); + err = -ENOMEM; + goto err_no_mem; + } + platform_set_drvdata(pdev, info); + info->pdev = pdev; + + idr_init(&info->idr); + + err = device_create_file(dev, &dev_attr_status); + if (err != 0) { + dev_err(dev, "Failed to create status sysfs attribute\n"); + goto err_no_sysfs; + } + + for_each_child_of_node(pnode, cnode) { + + entry = gpio_of_entry_create(info, cnode); + if (IS_ERR_OR_NULL(entry)) { + dev_err(dev, "Failed to create gpio entry\n"); + err = PTR_ERR(entry); + goto err_fail_entry; + } + } + + dev_info(&pdev->dev, "ready\n"); + + return 0; +err_fail_entry: + device_remove_file(&pdev->dev, &dev_attr_status); +err_no_sysfs: +err_no_mem: + return err; +} + +static int gpio_of_helper_remove(struct platform_device *pdev) +{ + struct gpio_of_helper_info *info = platform_get_drvdata(pdev); + struct gpio_of_entry *entry; + int id; + + dev_info(&pdev->dev, "removing\n"); + + device_remove_file(&pdev->dev, &dev_attr_status); + + id = 0; + idr_for_each_entry(&info->idr, entry, id) { + /* destroy each and every one */ + gpio_of_entry_destroy(entry); + } + + return 0; +} + +#ifdef CONFIG_PM +//#ifdef CONFIG_PM_RUNTIME +static int gpio_of_helper_runtime_suspend(struct device *dev) +{ + /* place holder */ + return 0; +} + +static int gpio_of_helper_runtime_resume(struct device *dev) +{ + /* place holder */ + return 0; +} +//#endif /* CONFIG_PM_RUNTIME */ + +static struct dev_pm_ops gpio_of_helper_pm_ops = { + SET_RUNTIME_PM_OPS(gpio_of_helper_runtime_suspend, + gpio_of_helper_runtime_resume, NULL) +}; +#define GPIO_OF_HELPER_PM_OPS (&gpio_of_helper_pm_ops) +#else +#define GPIO_OF_HELPER_PM_OPS NULL +#endif /* CONFIG_PM */ + +struct platform_driver gpio_of_helper_driver = { + .probe = gpio_of_helper_probe, + .remove = gpio_of_helper_remove, + .driver = { + .name = "gpio-of-helper", + .owner = THIS_MODULE, + .pm = GPIO_OF_HELPER_PM_OPS, + .of_match_table = gpio_of_helper_of_match, + }, +}; + +module_platform_driver(gpio_of_helper_driver); + +MODULE_AUTHOR("Pantelis Antoniou "); +MODULE_DESCRIPTION("GPIO OF Helper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-of-helper"); diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 728f6c6..0a297d8 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -38,10 +38,10 @@ static DEFINE_MUTEX(sysfs_lock); /* * /sys/class/gpio/gpioN... only for GPIOs that are exported * /direction - * * MAY BE OMITTED if kernel won't allow direction changes * * is read/write as "in" or "out" * * may also be written as "high" or "low", initializing * output value as specified ("out" implies "low") + * * read-only if kernel won't allow direction changes * /value * * always readable, subject to hardware behavior * * may be writable, as zero/nonzero @@ -54,6 +54,8 @@ static DEFINE_MUTEX(sysfs_lock); * * is read/write as zero/nonzero * * also affects existing and subsequent "falling" and "rising" * /edge configuration + * /label + * * descriptor label */ static ssize_t direction_show(struct device *dev, @@ -84,7 +86,9 @@ static ssize_t direction_store(struct device *dev, mutex_lock(&data->mutex); - if (sysfs_streq(buf, "high")) + if (!data->direction_can_change) + status = -EPERM; + else if (sysfs_streq(buf, "high")) status = gpiod_direction_output_raw(desc, 1); else if (sysfs_streq(buf, "out") || sysfs_streq(buf, "low")) status = gpiod_direction_output_raw(desc, 0); @@ -363,6 +367,23 @@ static ssize_t active_low_store(struct device *dev, } static DEVICE_ATTR_RW(active_low); +static ssize_t label_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpiod_data *data = dev_get_drvdata(dev); + struct gpio_desc *desc = data->desc; + ssize_t status; + + mutex_lock(&data->mutex); + + status = sprintf(buf, "%s\n", desc->label); + + mutex_unlock(&data->mutex); + + return status; +} +static DEVICE_ATTR_RO(label); + static umode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr, int n) { @@ -374,12 +395,15 @@ static umode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr, if (attr == &dev_attr_direction.attr) { if (!show_direction) - mode = 0; + mode &= 0444; } else if (attr == &dev_attr_edge.attr) { if (gpiod_to_irq(desc) < 0) mode = 0; if (!show_direction && test_bit(FLAG_IS_OUT, &desc->flags)) mode = 0; + } else if (attr == &dev_attr_value.attr) { + if (!show_direction && !test_bit(FLAG_IS_OUT, &desc->flags)) + mode &= 0444; } return mode; @@ -390,6 +414,7 @@ static struct attribute *gpio_attrs[] = { &dev_attr_edge.attr, &dev_attr_value.attr, &dev_attr_active_low.attr, + &dev_attr_label.attr, NULL, }; @@ -403,6 +428,10 @@ static const struct attribute_group *gpio_groups[] = { NULL }; +/* bwlegh, a second device in the same file... get out of my namespace! */ +#define dev_attr_label dev_attr_chip_label +#define label_show chip_label_show + /* * /sys/class/gpio/gpiochipN/ * /base ... matching gpio_chip.base (N) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 80137c1..ad68b7b 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1918,10 +1918,10 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label) if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) { desc_set_label(desc, label ? : "?"); ret = 0; - } else { - kfree_const(label); - ret = -EBUSY; - goto done; +// } else { +// kfree_const(label); +// ret = -EBUSY; +// goto done; } if (gc->request) { diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c index abc8d7d..5bf2bfb 100644 --- a/drivers/iio/light/tsl2563.c +++ b/drivers/iio/light/tsl2563.c @@ -12,6 +12,8 @@ */ #include +#include +#include #include #include #include @@ -703,7 +705,6 @@ static int tsl2563_probe(struct i2c_client *client, struct iio_dev *indio_dev; struct tsl2563_chip *chip; struct tsl2563_platform_data *pdata = client->dev.platform_data; - struct device_node *np = client->dev.of_node; int err = 0; u8 id = 0; @@ -738,13 +739,14 @@ static int tsl2563_probe(struct i2c_client *client, chip->calib0 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); chip->calib1 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); - if (pdata) + if (pdata) { chip->cover_comp_gain = pdata->cover_comp_gain; - else if (np) - of_property_read_u32(np, "amstaos,cover-comp-gain", - &chip->cover_comp_gain); - else - chip->cover_comp_gain = 1; + } else { + err = device_property_read_u32(&client->dev, "amstaos,cover-comp-gain", + &chip->cover_comp_gain); + if (err) + chip->cover_comp_gain = 1; + } dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); indio_dev->name = client->name; diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c index 765c44a..73a28e3 100644 --- a/drivers/iio/light/vcnl4035.c +++ b/drivers/iio/light/vcnl4035.c @@ -652,6 +652,12 @@ static const struct dev_pm_ops vcnl4035_pm_ops = { vcnl4035_runtime_resume, NULL) }; +static const struct i2c_device_id vcnl4035_id[] = { + { "vcnl4035", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, vcnl4035_id); + static const struct of_device_id vcnl4035_of_match[] = { { .compatible = "vishay,vcnl4035", }, { } @@ -666,6 +672,7 @@ static struct i2c_driver vcnl4035_driver = { }, .probe = vcnl4035_probe, .remove = vcnl4035_remove, + .id_table = vcnl4035_id, }; module_i2c_driver(vcnl4035_driver); diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c index 5fbda94..7c29d4c 100644 --- a/drivers/iio/proximity/vl53l0x-i2c.c +++ b/drivers/iio/proximity/vl53l0x-i2c.c @@ -143,6 +143,12 @@ static int vl53l0x_probe(struct i2c_client *client) return devm_iio_device_register(&client->dev, indio_dev); } +static const struct i2c_device_id vl53l0x_id[] = { + { "vl53l0x", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl53l0x_id); + static const struct of_device_id st_vl53l0x_dt_match[] = { { .compatible = "st,vl53l0x", }, { } @@ -155,6 +161,7 @@ static struct i2c_driver vl53l0x_driver = { .of_match_table = st_vl53l0x_dt_match, }, .probe_new = vl53l0x_probe, + .id_table = vl53l0x_id, }; module_i2c_driver(vl53l0x_driver); diff --git a/drivers/input/misc/tps65218-pwrbutton.c b/drivers/input/misc/tps65218-pwrbutton.c index f011447..e0c6dea 100644 --- a/drivers/input/misc/tps65218-pwrbutton.c +++ b/drivers/input/misc/tps65218-pwrbutton.c @@ -36,7 +36,7 @@ struct tps6521x_data { static const struct tps6521x_data tps65217_data = { .reg_status = TPS65217_REG_STATUS, .pb_mask = TPS65217_STATUS_PB, - .name = "tps65217_pwrbutton", + .name = "tps65217_pwr_but", }; static const struct tps6521x_data tps65218_data = { diff --git a/drivers/input/touchscreen/ar1021_i2c.c b/drivers/input/touchscreen/ar1021_i2c.c index c0d5c24..740956f 100644 --- a/drivers/input/touchscreen/ar1021_i2c.c +++ b/drivers/input/touchscreen/ar1021_i2c.c @@ -17,6 +17,7 @@ #define AR1021_MAX_X 4095 #define AR1021_MAX_Y 4095 +#define AR1021_MAX_PRESSURE 255 #define AR1021_CMD 0x55 @@ -26,8 +27,29 @@ struct ar1021_i2c { struct i2c_client *client; struct input_dev *input; u8 data[AR1021_TOUCH_PKG_SIZE]; + bool invert_x; + bool invert_y; + bool swap_xy; }; +static bool ar1021_get_prop_u32(struct device *dev, + const char *property, + unsigned int default_value, + unsigned int *value) +{ + u32 val; + int error; + + error = device_property_read_u32(dev, property, &val); + if (error) { + *value = default_value; + return false; + } + + *value = val; + return true; +} + static irqreturn_t ar1021_i2c_irq(int irq, void *dev_id) { struct ar1021_i2c *ar1021 = dev_id; @@ -49,9 +71,22 @@ static irqreturn_t ar1021_i2c_irq(int irq, void *dev_id) x = ((data[2] & 0x1f) << 7) | (data[1] & 0x7f); y = ((data[4] & 0x1f) << 7) | (data[3] & 0x7f); - input_report_abs(input, ABS_X, x); - input_report_abs(input, ABS_Y, y); + if (ar1021->invert_x) + x = AR1021_MAX_X - x; + + if (ar1021->invert_y) + y = AR1021_MAX_Y - y; + + if (ar1021->swap_xy) { + input_report_abs(input, ABS_X, y); + input_report_abs(input, ABS_Y, x); + } else { + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + } + input_report_key(input, BTN_TOUCH, button); + input_report_abs(input, ABS_PRESSURE, AR1021_MAX_PRESSURE); input_sync(input); out: @@ -93,6 +128,8 @@ static int ar1021_i2c_probe(struct i2c_client *client, struct ar1021_i2c *ar1021; struct input_dev *input; int error; + unsigned int offset_x, offset_y; + bool data_present; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { dev_err(&client->dev, "i2c_check_functionality error\n"); @@ -116,10 +153,44 @@ static int ar1021_i2c_probe(struct i2c_client *client, input->open = ar1021_i2c_open; input->close = ar1021_i2c_close; + ar1021->invert_x = device_property_read_bool(&client->dev, "touchscreen-inverted-x"); + ar1021->invert_y = device_property_read_bool(&client->dev, "touchscreen-inverted-y"); + ar1021->swap_xy = device_property_read_bool(&client->dev, "touchscreen-swapped-x-y"); + + data_present = ar1021_get_prop_u32(&client->dev, + "touchscreen-offset-x", + 0, + &offset_x); + + if (data_present) + dev_info(&client->dev, "touchscreen-offset-x: %d\n", offset_x); + + data_present = ar1021_get_prop_u32(&client->dev, + "touchscreen-offset-y", + 0, + &offset_y); + + if (data_present) + dev_info(&client->dev, "touchscreen-offset-y: %d\n", offset_y); + __set_bit(INPUT_PROP_DIRECT, input->propbit); - input_set_capability(input, EV_KEY, BTN_TOUCH); - input_set_abs_params(input, ABS_X, 0, AR1021_MAX_X, 0, 0); - input_set_abs_params(input, ABS_Y, 0, AR1021_MAX_Y, 0, 0); + //input_set_capability(input, EV_KEY, BTN_TOUCH); + + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + if(ar1021->swap_xy) + { + input_set_abs_params(input, ABS_X, 0, AR1021_MAX_Y, 0, 0); + input_set_abs_params(input, ABS_Y, 0, AR1021_MAX_X, 0, 0); + } + else + { + input_set_abs_params(input, ABS_X, offset_x, AR1021_MAX_X-offset_x, 0, 0); + input_set_abs_params(input, ABS_Y, offset_y, AR1021_MAX_Y-offset_y, 0, 0); + } + + input_set_abs_params(input, ABS_PRESSURE, 0, AR1021_MAX_PRESSURE, 0, 0); input_set_drvdata(input, ar1021); diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c index 83e6855..dd358bf 100644 --- a/drivers/input/touchscreen/ti_am335x_tsc.c +++ b/drivers/input/touchscreen/ti_am335x_tsc.c @@ -34,6 +34,7 @@ #define ADCFSM_STEPID 0x10 #define SEQ_SETTLE 275 #define MAX_12BIT ((1 << 12) - 1) +#define PRESSURE_MAX 1000 #define TSC_IRQENB_MASK (IRQENB_FIFO0THRES | IRQENB_EOS | IRQENB_HW_PEN) @@ -234,6 +235,7 @@ static void titsc_read_coordinates(struct titsc *ts_dev, for (i = 0; i < creads; i++) { xvals[i] = titsc_readl(ts_dev, REG_FIFO0); xvals[i] &= 0xfff; + pr_debug("i %d xval %d yval %d z1 %d z2 %d\n", i, xvals[i], yvals[i], *z1, *z2); } /* @@ -312,13 +314,13 @@ static irqreturn_t titsc_irq(int irq, void *dev) * Resistance(touch) = x plate resistance * * x postion/4096 * ((z2 / z1) - 1) */ - z = z1 - z2; + z = z2 - z1; z *= x; z *= ts_dev->x_plate_resistance; - z /= z2; + z /= z1; z = (z + 2047) >> 12; - - if (z <= MAX_12BIT) { + pr_debug("x %d y %d z1 %d z2 %d z %d\n", x, y, z1, z2, z); + if (z <= PRESSURE_MAX) { input_report_abs(input_dev, ABS_X, x); input_report_abs(input_dev, ABS_Y, y); input_report_abs(input_dev, ABS_PRESSURE, z); @@ -459,6 +461,7 @@ static int titsc_probe(struct platform_device *pdev) input_dev->name = "ti-tsc"; input_dev->dev.parent = &pdev->dev; + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ce136d6..d861cfb 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -456,6 +456,13 @@ config PVPANIC a paravirtualized device provided by QEMU; it lets a virtual machine (guest) communicate panic events to the host. +config UDOO_ARD + tristate "UDOO-Arduino erase/reset Driver" + default y + help + This driver is used to erase and reset arduino board via command sent + over USB-to-SERIAL connection. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" @@ -466,10 +473,12 @@ source "drivers/misc/mei/Kconfig" source "drivers/misc/vmw_vmci/Kconfig" source "drivers/misc/mic/Kconfig" source "drivers/misc/genwqe/Kconfig" +source "drivers/misc/cape/Kconfig" source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" source "drivers/misc/ocxl/Kconfig" source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" source "drivers/misc/uacce/Kconfig" +source "drivers/misc/mikrobus/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c7bd01a..0e7da59 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ +obj-y += mikrobus/ obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ @@ -48,8 +49,10 @@ obj-$(CONFIG_SRAM) += sram.o obj-$(CONFIG_SRAM_EXEC) += sram-exec.o obj-y += mic/ obj-$(CONFIG_GENWQE) += genwqe/ +obj-y += cape/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_UDOO_ARD) += udoo_ard.o obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_OCXL) += ocxl/ obj-y += cardreader/ diff --git b/drivers/misc/cape/Kconfig b/drivers/misc/cape/Kconfig new file mode 100644 index 0000000..a2ef85e --- /dev/null +++ b/drivers/misc/cape/Kconfig @@ -0,0 +1,5 @@ +# +# Capes +# + +source "drivers/misc/cape/beaglebone/Kconfig" diff --git b/drivers/misc/cape/Makefile b/drivers/misc/cape/Makefile new file mode 100644 index 0000000..7c4eb96 --- /dev/null +++ b/drivers/misc/cape/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for cape like devices +# + +obj-y += beaglebone/ diff --git b/drivers/misc/cape/beaglebone/Kconfig b/drivers/misc/cape/beaglebone/Kconfig new file mode 100644 index 0000000..eeb6782 --- /dev/null +++ b/drivers/misc/cape/beaglebone/Kconfig @@ -0,0 +1,10 @@ +# +# Beaglebone capes +# + +config BEAGLEBONE_PINMUX_HELPER + tristate "Beaglebone Pinmux Helper" + depends on ARCH_OMAP2PLUS && OF + default n + help + Say Y here to include support for the pinmux helper diff --git b/drivers/misc/cape/beaglebone/Makefile b/drivers/misc/cape/beaglebone/Makefile new file mode 100644 index 0000000..7f4617a --- /dev/null +++ b/drivers/misc/cape/beaglebone/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for beaglebone capes +# + +obj-$(CONFIG_BEAGLEBONE_PINMUX_HELPER) += bone-pinmux-helper.o diff --git b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c new file mode 100644 index 0000000..57703d8 --- /dev/null +++ b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c @@ -0,0 +1,242 @@ +/* + * Pinmux helper driver + * + * Copyright (C) 2013 Pantelis Antoniou + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct of_device_id bone_pinmux_helper_of_match[] = { + { + .compatible = "bone-pinmux-helper", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, bone_pinmux_helper_of_match); + +struct pinmux_helper_data { + struct pinctrl *pinctrl; + char *selected_state_name; +}; + +static ssize_t pinmux_helper_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pinmux_helper_data *data = platform_get_drvdata(pdev); + const char *name; + + name = data->selected_state_name; + if (name == NULL || strlen(name) == 0) + name = "none"; + return sprintf(buf, "%s\n", name); +} + +static ssize_t pinmux_helper_store_state(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pinmux_helper_data *data = platform_get_drvdata(pdev); + struct pinctrl_state *state; + char *state_name; + char *s; + int err; + + /* duplicate (as a null terminated string) */ + state_name = kmalloc(count + 1, GFP_KERNEL); + if (state_name == NULL) + return -ENOMEM; + memcpy(state_name, buf, count); + state_name[count] = '\0'; + + /* and chop off newline */ + s = strchr(state_name, '\n'); + if (s != NULL) + *s = '\0'; + + /* try to select default state at first (if it exists) */ + state = pinctrl_lookup_state(data->pinctrl, state_name); + if (!IS_ERR(state)) { + err = pinctrl_select_state(data->pinctrl, state); + if (err != 0) + dev_err(dev, "Failed to select state %s\n", + state_name); + } else { + dev_err(dev, "Failed to find state %s\n", state_name); + err = PTR_ERR_OR_ZERO(state); + } + + if (err == 0) { + kfree(data->selected_state_name); + data->selected_state_name = state_name; + } + + return err ? err : count; +} + +static DEVICE_ATTR(state, S_IWUSR | S_IRUGO, + pinmux_helper_show_state, pinmux_helper_store_state); + +static struct attribute *pinmux_helper_attributes[] = { + &dev_attr_state.attr, + NULL +}; + +static const struct attribute_group pinmux_helper_attr_group = { + .attrs = pinmux_helper_attributes, +}; + +static int bone_pinmux_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pinmux_helper_data *data; + struct pinctrl_state *state; + char *state_name; + const char *mode_name; + int mode_len; + int err; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Failed to allocate data\n"); + err = -ENOMEM; + goto err_no_mem; + } + + state_name = kmalloc(strlen(PINCTRL_STATE_DEFAULT) + 1, + GFP_KERNEL); + if (state_name == NULL) { + dev_err(dev, "Failed to allocate state name\n"); + err = -ENOMEM; + goto err_no_state_mem; + } + data->selected_state_name = state_name; + strcpy(data->selected_state_name, PINCTRL_STATE_DEFAULT); + + platform_set_drvdata(pdev, data); + + data->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(data->pinctrl)) { + dev_err(dev, "Failed to get pinctrl\n"); + err = PTR_ERR_OR_ZERO(data->pinctrl); + goto err_no_pinctrl; + } + + /* See if an initial mode is specified in the device tree */ + mode_name = of_get_property(dev->of_node, "mode", &mode_len); + + err = -1; + if (mode_name != NULL ) { + state_name = kmalloc(mode_len + 1, GFP_KERNEL); + if (state_name == NULL) { + dev_err(dev, "Failed to allocate state name\n"); + err = -ENOMEM; + goto err_no_mode_mem; + } + strncpy(state_name, mode_name, mode_len); + + /* try to select requested mode */ + state = pinctrl_lookup_state(data->pinctrl, state_name); + if (!IS_ERR(state)) { + err = pinctrl_select_state(data->pinctrl, state); + if (err != 0) { + dev_warn(dev, "Unable to select requested mode %s\n", state_name); + kfree(state_name); + } else { + kfree(data->selected_state_name); + data->selected_state_name = state_name; + dev_notice(dev, "Set initial pinmux mode to %s\n", state_name); + } + } + } + + /* try to select default state if mode_name failed */ + if ( err != 0) { + state = pinctrl_lookup_state(data->pinctrl, + data->selected_state_name); + if (!IS_ERR(state)) { + err = pinctrl_select_state(data->pinctrl, state); + if (err != 0) { + dev_err(dev, "Failed to select default state\n"); + goto err_no_state; + } + } else { + data->selected_state_name = '\0'; + } + } + + /* Register sysfs hooks */ + err = sysfs_create_group(&dev->kobj, &pinmux_helper_attr_group); + if (err) { + dev_err(dev, "Failed to create sysfs group\n"); + goto err_no_sysfs; + } + + return 0; + +err_no_sysfs: +err_no_state: +err_no_mode_mem: + devm_pinctrl_put(data->pinctrl); +err_no_pinctrl: + devm_kfree(dev, data->selected_state_name); +err_no_state_mem: + devm_kfree(dev, data); +err_no_mem: + return err; +} + +static int bone_pinmux_helper_remove(struct platform_device *pdev) +{ + struct pinmux_helper_data *data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + sysfs_remove_group(&dev->kobj, &pinmux_helper_attr_group); + kfree(data->selected_state_name); + devm_pinctrl_put(data->pinctrl); + devm_kfree(dev, data); + + return 0; +} + +struct platform_driver bone_pinmux_helper_driver = { + .probe = bone_pinmux_helper_probe, + .remove = bone_pinmux_helper_remove, + .driver = { + .name = "bone-pinmux-helper", + .owner = THIS_MODULE, + .of_match_table = bone_pinmux_helper_of_match, + }, +}; + +module_platform_driver(bone_pinmux_helper_driver); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("Beaglebone pinmux helper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bone-pinmux-helper"); diff --git b/drivers/misc/mikrobus/Kconfig b/drivers/misc/mikrobus/Kconfig new file mode 100644 index 0000000..5f42bc4 --- /dev/null +++ b/drivers/misc/mikrobus/Kconfig @@ -0,0 +1,16 @@ +menuconfig MIKROBUS + tristate "Module for instantiating devices on mikroBUS ports" + help + This option enables the mikroBUS driver. mikroBUS is an add-on + board socket standard that offers maximum expandability with + the smallest number of pins. The mikroBUS driver instantiates + devices on a mikroBUS port described by identifying data present + in an add-on board resident EEPROM, more details on the mikroBUS + driver support and discussion can be found in this eLinux wiki : + elinux.org/Mikrobus + + + Say Y here to enable support for this driver. + + To compile this code as a module, chose M here: the module + will be called mikrobus.ko diff --git b/drivers/misc/mikrobus/Makefile b/drivers/misc/mikrobus/Makefile new file mode 100644 index 0000000..982bdec --- /dev/null +++ b/drivers/misc/mikrobus/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +# mikroBUS Core + +obj-$(CONFIG_MIKROBUS) += mikrobus.o +mikrobus-y := mikrobus_core.o mikrobus_manifest.o \ No newline at end of file diff --git b/drivers/misc/mikrobus/mikrobus_core.c b/drivers/misc/mikrobus/mikrobus_core.c new file mode 100644 index 0000000..7e342e2 --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_core.c @@ -0,0 +1,880 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mikroBUS driver for instantiating add-on + * board devices with an identifier EEPROM + * + * Copyright 2020 Vaishnav M A, BeagleBoard.org Foundation. + */ + +#define pr_fmt(fmt) "mikrobus:%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mikrobus_core.h" +#include "mikrobus_manifest.h" + +static DEFINE_MUTEX(core_lock); +static DEFINE_IDR(mikrobus_port_idr); +static struct class_compat *mikrobus_port_compat_class; +int __mikrobus_first_dynamic_bus_num; +static bool is_registered; + +const char *MIKROBUS_PINCTRL_STR[] = {"pwm", "uart", "i2c", "spi"}; + +struct bus_type mikrobus_bus_type = { + .name = "mikrobus", +}; +EXPORT_SYMBOL_GPL(mikrobus_bus_type); + +static int mikrobus_port_scan_eeprom(struct mikrobus_port *port) +{ + struct addon_board_info *board; + int manifest_size; + char header[12]; + int retval; + char *buf; + + retval = nvmem_device_read(port->eeprom, 0, 12, header); + if (retval != 12) { + dev_err(&port->dev, "failed to fetch manifest header %d\n", + retval); + return -EINVAL; + } + manifest_size = mikrobus_manifest_header_validate(header, 12); + if (manifest_size < 0) { + dev_err(&port->dev, "invalid manifest size %d\n", + manifest_size); + return -EINVAL; + } + buf = kzalloc(manifest_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + retval = nvmem_device_read(port->eeprom, 0, manifest_size, buf); + if (retval != manifest_size) { + dev_err(&port->dev, "failed to fetch manifest %d\n", retval); + retval = -EINVAL; + goto err_free_buf; + } + board = kzalloc(sizeof(*board), GFP_KERNEL); + if (!board) { + retval = -ENOMEM; + goto err_free_buf; + } + INIT_LIST_HEAD(&board->manifest_descs); + INIT_LIST_HEAD(&board->devices); + retval = mikrobus_manifest_parse(board, buf, manifest_size); + if (!retval) { + dev_err(&port->dev, "failed to parse manifest, size %d\n", + manifest_size); + retval = -EINVAL; + goto err_free_board; + } + retval = mikrobus_board_register(port, board); + if (retval) { + dev_err(&port->dev, "failed to register board %s\n", + board->name); + goto err_free_board; + } + kfree(buf); + return 0; +err_free_board: + kfree(board); +err_free_buf: + kfree(buf); + return retval; +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", to_mikrobus_port(dev)->name); +} +static DEVICE_ATTR_RO(name); + +static ssize_t new_device_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mikrobus_port *port = to_mikrobus_port(dev); + struct addon_board_info *board; + int retval; + + if (port->board) { + dev_err(dev, "already has board registered\n"); + return -EBUSY; + } + + board = kzalloc(sizeof(*board), GFP_KERNEL); + if (!board) + return -ENOMEM; + INIT_LIST_HEAD(&board->manifest_descs); + INIT_LIST_HEAD(&board->devices); + retval = mikrobus_manifest_parse(board, (void *)buf, count); + if (!retval) { + dev_err(dev, "failed to parse manifest\n"); + retval = -EINVAL; + goto err_free_board; + } + retval = mikrobus_board_register(port, board); + if (retval) { + dev_err(dev, "failed to register board %s\n", board->name); + retval = -EINVAL; + goto err_free_board; + } + return count; +err_free_board: + kfree(board); + return retval; +} +static DEVICE_ATTR_WO(new_device); + +static ssize_t rescan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mikrobus_port *port = to_mikrobus_port(dev); + unsigned long id; + int retval; + + if (kstrtoul(buf, 0, &id)) { + dev_err(dev, "cannot parse trigger\n"); + return -EINVAL; + } + if (port->board) { + dev_err(dev, "already has board registered\n"); + return -EBUSY; + } + retval = mikrobus_port_scan_eeprom(port); + if (retval) { + dev_err(dev, "board register from manifest failed\n"); + return -EINVAL; + } + return count; +} +static DEVICE_ATTR_WO(rescan); + +static ssize_t delete_device_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mikrobus_port *port = to_mikrobus_port(dev); + unsigned long id; + + if (kstrtoul(buf, 0, &id)) { + dev_err(dev, "cannot parse board id"); + return -EINVAL; + } + if (!port->board) { + dev_err(dev, "does not have registered boards"); + return -ENODEV; + } + mikrobus_board_unregister(port, port->board); + return count; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, delete_device_store); + +static struct attribute *mikrobus_port_attrs[] = { + &dev_attr_new_device.attr, &dev_attr_rescan.attr, + &dev_attr_delete_device.attr, &dev_attr_name.attr, NULL}; +ATTRIBUTE_GROUPS(mikrobus_port); + +static void mikrobus_port_release(struct device *dev) +{ + struct mikrobus_port *port = to_mikrobus_port(dev); + + mutex_lock(&core_lock); + idr_remove(&mikrobus_port_idr, port->id); + mutex_unlock(&core_lock); + kfree(port); +} + +struct device_type mikrobus_port_type = { + .groups = mikrobus_port_groups, + .release = mikrobus_port_release, +}; +EXPORT_SYMBOL_GPL(mikrobus_port_type); + + + +int mikrobus_port_pinctrl_select(struct mikrobus_port *port) +{ + struct pinctrl_state *state; + int retval; + int i; + + for (i = 0; i < MIKROBUS_NUM_PINCTRL_STATE; i++) { + state = pinctrl_lookup_state(port->pinctrl, + port->pinctrl_selected[i]); + if (!IS_ERR(state)) { + retval = pinctrl_select_state(port->pinctrl, state); + pr_info("setting pinctrl %s\n", + port->pinctrl_selected[i]); + if (retval != 0) { + dev_err(&port->dev, "failed to select state %s\n", + port->pinctrl_selected[i]); + return retval; + } + } else { + dev_err(&port->dev, "failed to find state %s\n", + port->pinctrl_selected[i]); + return PTR_ERR(state); + } + } + + return retval; +} +EXPORT_SYMBOL_GPL(mikrobus_port_pinctrl_select); + +static int mikrobus_port_pinctrl_setup(struct mikrobus_port *port, struct addon_board_info *board) +{ + int retval; + int i; + + for (i = 0; i < MIKROBUS_NUM_PINCTRL_STATE; i++) { + switch (i) { + case MIKROBUS_PINCTRL_PWM: + if (board->pin_state[MIKROBUS_PIN_PWM] == MIKROBUS_STATE_PWM) + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], PINCTRL_STATE_DEFAULT); + else + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], MIKROBUS_PINCTRL_STATE_GPIO); + break; + case MIKROBUS_PINCTRL_UART: + if (board->pin_state[MIKROBUS_PIN_RX] == MIKROBUS_STATE_UART) + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], PINCTRL_STATE_DEFAULT); + else + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], MIKROBUS_PINCTRL_STATE_GPIO); + break; + case MIKROBUS_PINCTRL_I2C: + if (board->pin_state[MIKROBUS_PIN_SCL] == MIKROBUS_STATE_I2C) + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], PINCTRL_STATE_DEFAULT); + else + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], MIKROBUS_PINCTRL_STATE_GPIO); + break; + case MIKROBUS_PINCTRL_SPI: + if (board->pin_state[MIKROBUS_PIN_MOSI] == MIKROBUS_STATE_SPI) + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], PINCTRL_STATE_DEFAULT); + else + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], MIKROBUS_PINCTRL_STATE_GPIO); + break; + } + } + + retval = mikrobus_port_pinctrl_select(port); + if (retval) + dev_err(&port->dev, "failed to select pinctrl states [%d]", retval); + return retval; +} + +static int mikrobus_irq_get(struct mikrobus_port *port, int irqno, + int irq_type) +{ + int irq; + + if (irqno > port->gpios->ndescs - 1) { + dev_err(&port->dev, "GPIO %d does not exist", irqno); + return -ENODEV; + } + + irq = gpiod_to_irq(port->gpios->desc[irqno]); + if (irq < 0) { + dev_err(&port->dev, "could not get irq %d", irqno); + return -EINVAL; + } + irq_set_irq_type(irq, irq_type); + return irq; +} + +static int mikrobus_gpio_setup(struct gpio_desc *gpio, int gpio_state) +{ + int retval; + + switch (gpio_state) { + case MIKROBUS_STATE_INPUT: + retval = gpiod_direction_input(gpio); + break; + case MIKROBUS_STATE_OUTPUT_HIGH: + retval = gpiod_direction_output(gpio, 1); + break; + case MIKROBUS_STATE_OUTPUT_LOW: + retval = gpiod_direction_output(gpio, 0); + break; + case MIKROBUS_STATE_PWM: + case MIKROBUS_STATE_SPI: + case MIKROBUS_STATE_I2C: + default: + return 0; + } + return retval; +} + +static char *mikrobus_gpio_chip_name_get(struct mikrobus_port *port, int gpio) +{ + char *name; + struct gpio_chip *gpiochip; + + if (gpio > port->gpios->ndescs - 1) + return NULL; + + gpiochip = gpiod_to_chip(port->gpios->desc[gpio]); + name = kmemdup(gpiochip->label, MIKROBUS_NAME_SIZE, GFP_KERNEL); + return name; +} + +static int mikrobus_gpio_hwnum_get(struct mikrobus_port *port, int gpio) +{ + int hwnum; + struct gpio_chip *gpiochip; + + if (gpio > port->gpios->ndescs - 1) + return -ENODEV; + + gpiochip = gpiod_to_chip(port->gpios->desc[gpio]); + hwnum = desc_to_gpio(port->gpios->desc[gpio]) - gpiochip->base; + return hwnum; +} + +static void mikrobus_board_device_release_all(struct addon_board_info *info) +{ + struct board_device_info *dev; + struct board_device_info *next; + + list_for_each_entry_safe(dev, next, &info->devices, links) { + list_del(&dev->links); + kfree(dev); + } +} + +static int mikrobus_device_register(struct mikrobus_port *port, + struct board_device_info *dev, char *board_name) +{ + struct i2c_board_info *i2c; + struct spi_device *spi; + struct serdev_device *serdev; + struct platform_device *pdev; + struct gpiod_lookup_table *lookup; + struct regulator_consumer_supply regulator; + char devname[MIKROBUS_NAME_SIZE]; + int i; + u64 *val; + + dev_info(&port->dev, "registering device : %s", dev->drv_name); + + if (dev->gpio_lookup) { + lookup = dev->gpio_lookup; + if (dev->protocol == GREYBUS_PROTOCOL_SPI) { + snprintf(devname, sizeof(devname), "%s.%u", + dev_name(&port->spi_mstr->dev), + port->chip_select[dev->reg]); + lookup->dev_id = kmemdup(devname, MIKROBUS_NAME_SIZE, GFP_KERNEL); + } else if (dev->protocol == GREYBUS_PROTOCOL_RAW) { + snprintf(devname, sizeof(devname), "%s.%u", + dev->drv_name, dev->reg); + lookup->dev_id = kmemdup(devname, MIKROBUS_NAME_SIZE, GFP_KERNEL); + } else + lookup->dev_id = dev->drv_name; + dev_info(&port->dev, " adding lookup table : %s\n", + lookup->dev_id); + for (i = 0; i < dev->num_gpio_resources; i++) { + lookup->table[i].key = + mikrobus_gpio_chip_name_get(port, + lookup->table[i].chip_hwnum); + lookup->table[i].chip_hwnum = + mikrobus_gpio_hwnum_get(port, + lookup->table[i].chip_hwnum); + } + gpiod_add_lookup_table(lookup); + } + if (dev->regulators) { + if (dev->protocol == GREYBUS_PROTOCOL_SPI) { + snprintf(devname, sizeof(devname), "%s.%u", + dev_name(&port->spi_mstr->dev), + port->chip_select[dev->reg]); + regulator.dev_name = kmemdup(devname, MIKROBUS_NAME_SIZE, GFP_KERNEL); + } else if (dev->protocol == GREYBUS_PROTOCOL_RAW) { + snprintf(devname, sizeof(devname), "%s.%u", + dev->drv_name, dev->reg); + regulator.dev_name = kmemdup(devname, MIKROBUS_NAME_SIZE, GFP_KERNEL); + } else + regulator.dev_name = dev->drv_name; + + for (i = 0; i < dev->num_regulators; i++) { + val = dev->regulators[i].value.u64_data; + regulator.supply = kmemdup(dev->regulators[i].name, MIKROBUS_NAME_SIZE, GFP_KERNEL); + dev_info(&port->dev, " adding fixed regulator %llu uv, %s for %s\n", + *val, regulator.supply, regulator.dev_name); + regulator_register_always_on(0, dev->regulators[i].name, ®ulator, + 1, *val); + } + } + switch (dev->protocol) { + case GREYBUS_PROTOCOL_SPI: + spi = spi_alloc_device(port->spi_mstr); + if (!spi) + return -ENOMEM; + strncpy(spi->modalias, dev->drv_name, sizeof(spi->modalias) - 1); + if (dev->irq) + spi->irq = mikrobus_irq_get(port, dev->irq, dev->irq_type); + if (dev->properties) + device_add_properties(&spi->dev, dev->properties); + spi->chip_select = port->chip_select[dev->reg]; + spi->max_speed_hz = dev->max_speed_hz; + spi->mode = dev->mode; + if (dev->clocks) { + for (i = 0; i < dev->num_clocks; i++) { + val = dev->clocks[i].value.u64_data; + dev_info(&port->dev, " adding fixed clock %s, %llu hz\n", + dev->clocks[i].name, *val); + //failing: under debug + clk_register_fixed_rate(&spi->dev, dev->clocks[i].name, devname, 0, *val); + } + } + dev->dev_client = (void *) spi_add_device(spi); + break; + case GREYBUS_PROTOCOL_I2C: + i2c = kzalloc(sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + strncpy(i2c->type, dev->drv_name, sizeof(i2c->type) - 1); + if (dev->irq) + i2c->irq = mikrobus_irq_get(port, dev->irq, dev->irq_type); + if (dev->properties) + i2c->properties = dev->properties; + i2c->addr = dev->reg; + dev->dev_client = (void *) i2c_new_client_device(port->i2c_adap, i2c); + break; + case GREYBUS_PROTOCOL_RAW: + pdev = platform_device_alloc(dev->drv_name, 0); + if (!pdev) + return -ENOMEM; + if (dev->properties) + platform_device_add_properties(pdev, dev->properties); + dev->dev_client = pdev; + platform_device_add(dev->dev_client); + break; + case GREYBUS_PROTOCOL_UART: + serdev = serdev_device_alloc(port->ser_ctrl); + if (!serdev) + return -ENOMEM; + strncpy(serdev->modalias, dev->drv_name, sizeof(serdev->modalias) - 1); + if (dev->properties) + device_add_properties(&serdev->dev, dev->properties); + dev->dev_client = serdev; + serdev_device_add(serdev); + break; + break; + default: + return -EINVAL; + } + return 0; +} + +static void mikrobus_device_unregister(struct mikrobus_port *port, + struct board_device_info *dev, char *board_name) +{ + dev_info(&port->dev, "removing device %s\n", dev->drv_name); + if (dev->gpio_lookup) { + gpiod_remove_lookup_table(dev->gpio_lookup); + kfree(dev->gpio_lookup); + } + kfree(dev->properties); + switch (dev->protocol) { + case GREYBUS_PROTOCOL_SPI: + spi_unregister_device((struct spi_device *)dev->dev_client); + break; + case GREYBUS_PROTOCOL_I2C: + i2c_unregister_device((struct i2c_client *)dev->dev_client); + break; + case GREYBUS_PROTOCOL_RAW: + platform_device_unregister((struct platform_device *)dev->dev_client); + break; + case GREYBUS_PROTOCOL_UART: + serdev_device_remove((struct serdev_device *)dev->dev_client); + break; + } +} + +int mikrobus_board_register(struct mikrobus_port *port, struct addon_board_info *board) +{ + struct board_device_info *devinfo; + struct board_device_info *next; + int retval; + int i; + + if (WARN_ON(list_empty(&board->devices))) + return false; + if (port->pinctrl) { + retval = mikrobus_port_pinctrl_setup(port, board); + if (retval) + dev_err(&port->dev, "failed to setup pinctrl state [%d]", retval); + + } + if (port->gpios) { + for (i = 0; i < port->gpios->ndescs; i++) { + retval = mikrobus_gpio_setup(port->gpios->desc[i], board->pin_state[i]); + if (retval) + dev_err(&port->dev, "failed to setup gpio %d, state %d", + i, board->pin_state[i]); + } + } + list_for_each_entry_safe(devinfo, next, &board->devices, links) + mikrobus_device_register(port, devinfo, board->name); + port->board = board; + return 0; +} +EXPORT_SYMBOL_GPL(mikrobus_board_register); + +void mikrobus_board_unregister(struct mikrobus_port *port, struct addon_board_info *board) +{ + struct board_device_info *devinfo; + struct board_device_info *next; + + if (WARN_ON(list_empty(&board->devices))) + return; + port->board = NULL; + list_for_each_entry_safe(devinfo, next, &board->devices, links) + mikrobus_device_unregister(port, devinfo, board->name); + mikrobus_board_device_release_all(board); + kfree(board); + port->board = NULL; +} +EXPORT_SYMBOL_GPL(mikrobus_board_unregister); + +static struct i2c_board_info mikrobus_eeprom_info = { + I2C_BOARD_INFO("24c32", 0x57), +}; + +static int mikrobus_port_eeprom_probe(struct mikrobus_port *port) +{ + struct i2c_client *eeprom_client; + struct nvmem_device *eeprom; + char dev_name[MIKROBUS_NAME_SIZE]; + + eeprom_client = i2c_new_client_device(port->i2c_adap, &mikrobus_eeprom_info); + if (!IS_ERR(eeprom_client)) { + pr_info(" mikrobus port %d default eeprom is probed at %02x\n", port->id, + eeprom_client->addr); + snprintf(dev_name, sizeof(dev_name), "%d-%04x0", port->i2c_adap->nr, + eeprom_client->addr); + eeprom = nvmem_device_get(&eeprom_client->dev, dev_name); + if (IS_ERR(eeprom)) { + pr_err(" mikrobus port %d eeprom nvmem device probe failed\n", port->id); + i2c_unregister_device(eeprom_client); + port->eeprom = NULL; + return 0; + } + } else { + pr_info(" mikrobus port %d default eeprom probe failed\n", port->id); + return 0; + } + port->eeprom = eeprom; + port->eeprom_client = eeprom_client; + return 0; +} + +int mikrobus_port_register(struct mikrobus_port *port) +{ + struct device *dev = &port->dev; + int retval; + int id; + + if (WARN_ON(!is_registered)) + return -EAGAIN; + + if (dev->of_node) { + id = of_alias_get_id(dev->of_node, "mikrobus"); + if (id >= 0) { + port->id = id; + mutex_lock(&core_lock); + id = idr_alloc(&mikrobus_port_idr, port, port->id, port->id + 1, + GFP_KERNEL); + mutex_unlock(&core_lock); + if (WARN(id < 0, "couldn't get idr")) + return id == -ENOSPC ? -EBUSY : id; + } + } else { + mutex_lock(&core_lock); + id = idr_alloc(&mikrobus_port_idr, port, __mikrobus_first_dynamic_bus_num, 0, + GFP_KERNEL); + mutex_unlock(&core_lock); + if (id < 0) + return id; + port->id = id; + } + port->dev.bus = &mikrobus_bus_type; + port->dev.type = &mikrobus_port_type; + strncpy(port->name, "mikrobus-port", sizeof(port->name) - 1); + dev_set_name(&port->dev, "mikrobus-%d", port->id); + pr_info("registering port mikrobus-%d ", port->id); + retval = device_register(&port->dev); + if (retval) { + pr_err("port '%d': can't register device (%d)", port->id, retval); + put_device(&port->dev); + return retval; + } + retval = class_compat_create_link(mikrobus_port_compat_class, &port->dev, + port->dev.parent); + if (retval) + dev_warn(&port->dev, "failed to create compatibility class link\n"); + if (!port->eeprom) { + dev_info(&port->dev, "mikrobus port %d eeprom empty probing default eeprom\n", + port->id); + retval = mikrobus_port_eeprom_probe(port); + } + if (port->eeprom) { + retval = mikrobus_port_scan_eeprom(port); + if (retval) { + dev_warn(&port->dev, "failed to register board from manifest\n"); + return 0; + } + } + return retval; +} +EXPORT_SYMBOL_GPL(mikrobus_port_register); + +void mikrobus_port_delete(struct mikrobus_port *port) +{ + struct mikrobus_port *found; + + mutex_lock(&core_lock); + found = idr_find(&mikrobus_port_idr, port->id); + mutex_unlock(&core_lock); + if (found != port) { + pr_err("port [%s] not registered", port->name); + return; + } + if (port->board) { + dev_err(&port->dev, "attempting to delete port with registered boards, port [%s]\n", + port->name); + return; + } + + if (port->eeprom) { + nvmem_device_put(port->eeprom); + i2c_unregister_device(port->eeprom_client); + } + + class_compat_remove_link(mikrobus_port_compat_class, &port->dev, + port->dev.parent); + device_unregister(&port->dev); + mutex_lock(&core_lock); + idr_remove(&mikrobus_port_idr, port->id); + mutex_unlock(&core_lock); + memset(&port->dev, 0, sizeof(port->dev)); +} +EXPORT_SYMBOL_GPL(mikrobus_port_delete); + +static int mikrobus_port_probe_pinctrl_setup(struct mikrobus_port *port) +{ + struct pinctrl_state *state; + struct device *dev = port->dev.parent; + int retval, i; + + state = pinctrl_lookup_state(port->pinctrl, PINCTRL_STATE_DEFAULT); + if (!IS_ERR(state)) { + retval = pinctrl_select_state(port->pinctrl, state); + if (retval != 0) { + dev_err(dev, "Failed to select state %s\n", + PINCTRL_STATE_DEFAULT); + return retval; + } + } else { + dev_err(dev, "failed to find state %s\n", + PINCTRL_STATE_DEFAULT); + return PTR_ERR(state); + } + + for (i = 0; i < MIKROBUS_NUM_PINCTRL_STATE; i++) { + port->pinctrl_selected[i] = + kmalloc(MIKROBUS_PINCTRL_NAME_SIZE, GFP_KERNEL); + sprintf(port->pinctrl_selected[i], "%s_%s", + MIKROBUS_PINCTRL_STR[i], PINCTRL_STATE_DEFAULT); + } + + retval = mikrobus_port_pinctrl_select(port); + if (retval) + dev_err(dev, "failed to select pinctrl states [%d]", retval); + return retval; +} + +static int mikrobus_port_probe(struct platform_device *pdev) +{ + struct mikrobus_port *port; + struct device *dev = &pdev->dev; + struct device_node *i2c_adap_np; + struct device_node *uart_np; + int retval; + u32 val; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + i2c_adap_np = of_parse_phandle(dev->of_node, "i2c-adapter", 0); + if (!i2c_adap_np) { + dev_err(dev, "cannot parse i2c-adapter\n"); + retval = -ENODEV; + goto err_port; + } + port->i2c_adap = of_find_i2c_adapter_by_node(i2c_adap_np); + of_node_put(i2c_adap_np); + retval = device_property_read_u32(dev, "spi-master", &val); + if (retval) { + dev_err(dev, "failed to get spi-master [%d]\n", retval); + goto err_port; + } + port->spi_mstr = spi_busnum_to_master(val); + retval = device_property_read_u32_array(dev, "spi-cs", + port->chip_select, 2); + if (retval) { + dev_err(dev, "failed to get spi-cs [%d]\n", retval); + goto err_port; + } + uart_np = of_parse_phandle(dev->of_node, "uart", 0); + if (!uart_np) { + dev_err(dev, "cannot parse uart\n"); + retval = -ENODEV; + goto err_port; + } + port->ser_ctrl = of_find_serdev_controller_by_node(uart_np); + of_node_put(uart_np); + //port->pwm = devm_pwm_get(dev, NULL); + //if (IS_ERR(port->pwm)) { + // retval = PTR_ERR(port->pwm); + // if (retval != -EPROBE_DEFER) + // dev_err(dev, "failed to request PWM device [%d]\n", + // retval); + // goto err_port; + //} + port->gpios = gpiod_get_array(dev, "mikrobus", GPIOD_OUT_LOW); + if (IS_ERR(port->gpios)) { + retval = PTR_ERR(port->gpios); + dev_err(dev, "failed to get gpio array [%d]\n", retval); + goto err_port; + } + port->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(port->pinctrl)) { + retval = PTR_ERR(port->pinctrl); + dev_err(dev, "failed to get pinctrl [%d]\n", retval); + goto err_port; + } + port->dev.parent = dev; + port->dev.of_node = pdev->dev.of_node; + + retval = mikrobus_port_probe_pinctrl_setup(port); + if (retval) { + dev_err(dev, "failed to setup pinctrl [%d]\n", retval); + goto err_port; + } + + retval = mikrobus_port_register(port); + if (retval) { + pr_err("port : can't register port [%d]\n", retval); + goto err_port; + } + platform_set_drvdata(pdev, port); + return 0; +err_port: + kfree(port); + return retval; +} + +static int mikrobus_port_remove(struct platform_device *pdev) +{ + struct mikrobus_port *port = platform_get_drvdata(pdev); + + mikrobus_port_delete(port); + return 0; +} + +static const struct of_device_id mikrobus_port_of_match[] = { + {.compatible = "linux,mikrobus"}, + {}, +}; +MODULE_DEVICE_TABLE(of, mikrobus_port_of_match); + +static struct platform_driver mikrobus_port_driver = { + .probe = mikrobus_port_probe, + .remove = mikrobus_port_remove, + .driver = { + .name = "mikrobus", + .of_match_table = of_match_ptr(mikrobus_port_of_match), + }, +}; + +static int __init mikrobus_init(void) +{ + int retval; + + retval = bus_register(&mikrobus_bus_type); + if (retval) { + pr_err("bus_register failed (%d)\n", retval); + return retval; + } + mikrobus_port_compat_class = class_compat_register("mikrobus-port"); + if (!mikrobus_port_compat_class) { + pr_err("class_compat register failed (%d)\n", retval); + retval = -ENOMEM; + goto class_err; + } + retval = of_alias_get_highest_id("mikrobus"); + if (retval >= __mikrobus_first_dynamic_bus_num) + __mikrobus_first_dynamic_bus_num = retval + 1; + + is_registered = true; + retval = platform_driver_register(&mikrobus_port_driver); + if (retval) + pr_err("driver register failed [%d]\n", retval); + return retval; + +class_err: + bus_unregister(&mikrobus_bus_type); + idr_destroy(&mikrobus_port_idr); + is_registered = false; + return retval; +} +subsys_initcall(mikrobus_init); + +static void __exit mikrobus_exit(void) +{ + platform_driver_unregister(&mikrobus_port_driver); + bus_unregister(&mikrobus_bus_type); + class_compat_unregister(mikrobus_port_compat_class); + idr_destroy(&mikrobus_port_idr); +} +module_exit(mikrobus_exit); + +MODULE_AUTHOR("Vaishnav M A "); +MODULE_DESCRIPTION("mikroBUS main module"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/misc/mikrobus/mikrobus_core.h b/drivers/misc/mikrobus/mikrobus_core.h new file mode 100644 index 0000000..9ac1e62 --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_core.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mikroBUS Driver for instantiating add-on + * board devices with an identifier EEPROM + * + * Copyright 2020 Vaishnav M A, BeagleBoard.org Foundation. + */ + +#ifndef __MIKROBUS_H +#define __MIKROBUS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIKROBUS_VERSION_MAJOR 0x00 +#define MIKROBUS_VERSION_MINOR 0x03 + +#define MIKROBUS_NAME_SIZE 40 +#define MIKROBUS_PINCTRL_NAME_SIZE 20 + +#define MIKROBUS_NUM_PINCTRL_STATE 4 +#define MIKROBUS_NUM_CS 2 + +#define MIKROBUS_PINCTRL_PWM 0 +#define MIKROBUS_PINCTRL_UART 1 +#define MIKROBUS_PINCTRL_I2C 2 +#define MIKROBUS_PINCTRL_SPI 3 + +#define MIKROBUS_PINCTRL_STATE_GPIO "gpio" + +extern struct bus_type mikrobus_bus_type; +extern struct device_type mikrobus_port_type; +extern const char *MIKROBUS_PINCTRL_STR[]; + +enum mikrobus_property_type { + MIKROBUS_PROPERTY_TYPE_MIKROBUS = 0x00, + MIKROBUS_PROPERTY_TYPE_PROPERTY = 0x01, + MIKROBUS_PROPERTY_TYPE_GPIO = 0x02, + MIKROBUS_PROPERTY_TYPE_U8 = 0x03, + MIKROBUS_PROPERTY_TYPE_U16 = 0x04, + MIKROBUS_PROPERTY_TYPE_U32 = 0x05, + MIKROBUS_PROPERTY_TYPE_U64 = 0x06, + MIKROBUS_PROPERTY_TYPE_REGULATOR = 0x07, + MIKROBUS_PROPERTY_TYPE_CLOCK = 0x08, +}; + +enum mikrobus_pin { + MIKROBUS_PIN_PWM = 0x00, + MIKROBUS_PIN_INT = 0x01, + MIKROBUS_PIN_RX = 0x02, + MIKROBUS_PIN_TX = 0x03, + MIKROBUS_PIN_SCL = 0x04, + MIKROBUS_PIN_SDA = 0x05, + MIKROBUS_PIN_MOSI = 0x06, + MIKROBUS_PIN_MISO = 0x07, + MIKROBUS_PIN_SCK = 0x08, + MIKROBUS_PIN_CS = 0x09, + MIKROBUS_PIN_RST = 0x0A, + MIKROBUS_PIN_AN = 0x0B, + MIKROBUS_PORT_PIN_COUNT = 0x0C, +}; + +enum mikrobus_pin_state { + MIKROBUS_STATE_INPUT = 0x01, + MIKROBUS_STATE_OUTPUT_HIGH = 0x02, + MIKROBUS_STATE_OUTPUT_LOW = 0x03, + MIKROBUS_STATE_PWM = 0x04, + MIKROBUS_STATE_SPI = 0x05, + MIKROBUS_STATE_I2C = 0x06, + MIKROBUS_STATE_UART = 0x07, +}; + +/* + * board_device_info describes a single device on a mikrobus add-on + * board, an add-on board can present one or more device to the host + * + * @gpio_lookup: used to provide the GPIO lookup table for + * passing the named GPIOs to device drivers. + * @properties: used to provide the property_entry to pass named + * properties to device drivers, applicable only when driver uses + * device_property_read_* calls to fetch the properties. + * @num_gpio_resources: number of named gpio resources for the device, + * used mainly for gpiod_lookup_table memory allocation. + * @num_properties: number of custom properties for the device, + * used mainly for property_entry memory allocation. + * @protocol: used to know the type of the device and it should + * contain one of the values defined under 'enum greybus_class_type' + * under linux/greybus/greybus_manifest.h + * @reg: I2C address for the device, for devices on the SPI bus + * this field is the chip select address relative to the mikrobus + * port:0->device chip select connected to CS pin on mikroBUS port + * 1->device chip select connected to RST Pin on mikroBUS port + * @mode: SPI mode + * @max_speed_hz: SPI max speed(Hz) + * @drv_name: device_id to match with the driver + * @irq_type: type of IRQ trigger , match with defines in linux/interrupt.h + * @irq: irq number relative to the mikrobus port should contain one of the + * values defined under 'enum mikrobus_pin' + * @id: device id starting from 1 + */ +struct board_device_info { + struct gpiod_lookup_table *gpio_lookup; + struct property_entry *properties; + struct property_entry *regulators; + struct property_entry *clocks; + struct list_head links; + unsigned short num_gpio_resources; + unsigned short num_properties; + unsigned short num_regulators; + unsigned short num_clocks; + unsigned short protocol; + unsigned short reg; + unsigned int mode; + void *dev_client; + u32 max_speed_hz; + char *drv_name; + int irq_type; + int irq; + int id; +}; + +/* + * addon_board_info describes a mikrobus add-on device the add-on + * board, an add-on board can present one or more device to the host + * + * @manifest_descs: list of manifest descriptors + * @devices: list of devices on the board + * @pin_state: the state of each pin on the mikrobus port required + * for the add-on board should contain one of the values defined under + * 'enum mikrobus_pin_state' restrictions are as per mikrobus standard + * specifications. + * @name: add-on board name + */ +struct addon_board_info { + struct list_head manifest_descs; + struct list_head devices; + u8 pin_state[MIKROBUS_PORT_PIN_COUNT]; + char *name; +}; + +/* + * mikrobus_port describes the peripherals mapped to a + * mikrobus port. + * + * @eeprom_client: i2c_client corresponding to the eeprom + * on the add-on board. + * @board: pointer to the attached add-on board. + * @i2c_adap: I2C adapter attached to the mikrobus port. + * @spi_mstr: SPI master attached to the mikrobus port. + * @eeprom: nvmem_device for the eeprom on the add-on board. + * @pwm: pwm_device attached to the mikrobus port PWM pin. + * @pinctrl_selected: current pinctrl_selected state. + * @chip_select: chip select number mapped to the SPI + * CS pin on the mikrobus port and the RST pin on the mikrobus + * port + * @id: port id starting from 1 + */ +struct mikrobus_port { + struct i2c_client *eeprom_client; + struct addon_board_info *board; + struct i2c_adapter *i2c_adap; + struct spi_master *spi_mstr; + struct serdev_controller *ser_ctrl; + struct nvmem_device *eeprom; + struct gpio_descs *gpios; + struct pwm_device *pwm; + struct pinctrl *pinctrl; + struct module *owner; + struct device dev; + char name[MIKROBUS_NAME_SIZE]; + char *pinctrl_selected[MIKROBUS_NUM_PINCTRL_STATE]; + unsigned int chip_select[MIKROBUS_NUM_CS]; + int id; +}; +#define to_mikrobus_port(d) container_of(d, struct mikrobus_port, dev) + +void mikrobus_board_unregister(struct mikrobus_port *port, + struct addon_board_info *board); +int mikrobus_board_register(struct mikrobus_port *port, + struct addon_board_info *board); +int mikrobus_port_register(struct mikrobus_port *port); +int mikrobus_port_pinctrl_select(struct mikrobus_port *port); +void mikrobus_port_delete(struct mikrobus_port *port); + +#endif /* __MIKROBUS_H */ diff --git b/drivers/misc/mikrobus/mikrobus_manifest.c b/drivers/misc/mikrobus/mikrobus_manifest.c new file mode 100644 index 0000000..9cacc3d --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_manifest.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mikroBUS manifest parsing, an + * extension to Greybus Manifest Parsing + * under drivers/greybus/manifest.c + * + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. + */ + +#define pr_fmt(fmt) "mikrobus_manifest:%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "mikrobus_manifest.h" + +struct manifest_desc { + struct list_head links; + size_t size; + void *data; + enum greybus_descriptor_type type; +}; + +static void manifest_descriptor_release_all(struct addon_board_info *board) +{ + struct manifest_desc *descriptor; + struct manifest_desc *next; + + list_for_each_entry_safe(descriptor, next, &board->manifest_descs, links) { + list_del(&descriptor->links); + kfree(descriptor); + } +} + +static int board_descriptor_add(struct addon_board_info *board, struct greybus_descriptor *desc, + size_t size) +{ + struct greybus_descriptor_header *desc_header = &desc->header; + struct manifest_desc *descriptor; + size_t desc_size; + size_t expected_size; + + if (size < sizeof(*desc_header)) { + pr_err("short descriptor (%zu < %zu)", size, sizeof(*desc_header)); + return -EINVAL; + } + desc_size = le16_to_cpu(desc_header->size); + if (desc_size > size) { + pr_err("incorrect descriptor size (%zu != %zu)", size, desc_size); + return -EINVAL; + } + expected_size = sizeof(*desc_header); + switch (desc_header->type) { + case GREYBUS_TYPE_STRING: + expected_size += sizeof(struct greybus_descriptor_string); + expected_size += desc->string.length; + expected_size = ALIGN(expected_size, 4); + break; + case GREYBUS_TYPE_PROPERTY: + expected_size += sizeof(struct greybus_descriptor_property); + expected_size += desc->property.length; + expected_size = ALIGN(expected_size, 4); + break; + case GREYBUS_TYPE_DEVICE: + expected_size += sizeof(struct greybus_descriptor_device); + break; + case GREYBUS_TYPE_MIKROBUS: + expected_size += sizeof(struct greybus_descriptor_mikrobus); + break; + case GREYBUS_TYPE_INTERFACE: + expected_size += sizeof(struct greybus_descriptor_interface); + break; + case GREYBUS_TYPE_CPORT: + expected_size += sizeof(struct greybus_descriptor_cport); + break; + case GREYBUS_TYPE_BUNDLE: + expected_size += sizeof(struct greybus_descriptor_bundle); + break; + case GREYBUS_TYPE_INVALID: + default: + pr_err("invalid descriptor type %d", desc_header->type); + return -EINVAL; + } + + descriptor = kzalloc(sizeof(*descriptor), GFP_KERNEL); + if (!descriptor) + return -ENOMEM; + descriptor->size = desc_size; + descriptor->data = (char *)desc + sizeof(*desc_header); + descriptor->type = desc_header->type; + list_add_tail(&descriptor->links, &board->manifest_descs); + return desc_size; +} + +static char *mikrobus_string_get(struct addon_board_info *board, u8 string_id) +{ + struct greybus_descriptor_string *desc_string; + struct manifest_desc *descriptor; + bool found = false; + char *string; + + if (!string_id) + return NULL; + + list_for_each_entry(descriptor, &board->manifest_descs, links) { + if (descriptor->type != GREYBUS_TYPE_STRING) + continue; + desc_string = descriptor->data; + if (desc_string->id == string_id) { + found = true; + break; + } + } + if (!found) + return ERR_PTR(-ENOENT); + string = kmemdup(&desc_string->string, desc_string->length + 1, GFP_KERNEL); + if (!string) + return ERR_PTR(-ENOMEM); + string[desc_string->length] = '\0'; + return string; +} + +static void mikrobus_state_get(struct addon_board_info *board) +{ + struct greybus_descriptor_mikrobus *mikrobus; + struct greybus_descriptor_interface *interface; + struct manifest_desc *descriptor; + bool found = false; + int i; + + list_for_each_entry(descriptor, &board->manifest_descs, links) { + if (descriptor->type == GREYBUS_TYPE_MIKROBUS) { + mikrobus = descriptor->data; + found = true; + break; + } + } + if (!found) { + pr_err("mikrobus descriptor not found"); + return; + } + for (i = 0; i < MIKROBUS_PORT_PIN_COUNT; i++) + board->pin_state[i] = mikrobus->pin_state[i]; + + found = false; + list_for_each_entry(descriptor, &board->manifest_descs, links) { + if (descriptor->type == GREYBUS_TYPE_INTERFACE) { + interface = descriptor->data; + found = true; + break; + } + } + if (!found) { + pr_err("interface descriptor not found"); + return; + } + board->name = mikrobus_string_get(board, interface->product_stringid); +} + +static struct property_entry * +mikrobus_property_entry_get(struct addon_board_info *board, u8 *prop_link, + int num_properties) +{ + struct greybus_descriptor_property *desc_property; + struct manifest_desc *descriptor; + struct property_entry *properties; + bool found = false; + char *prop_name; + u64 *val_u64; + u32 *val_u32; + u16 *val_u16; + u8 *val_u8; + int i; + + properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL); + if (!properties) + return ERR_PTR(-ENOMEM); + for (i = 0; i < num_properties; i++) { + list_for_each_entry(descriptor, &board->manifest_descs, links) { + if (descriptor->type != GREYBUS_TYPE_PROPERTY) + continue; + desc_property = descriptor->data; + if (desc_property->id == prop_link[i]) { + found = true; + break; + } + } + if (!found) { + kfree(properties); + return ERR_PTR(-ENOENT); + } + prop_name = mikrobus_string_get(board, desc_property->propname_stringid); + if (!prop_name) { + kfree(properties); + return ERR_PTR(-ENOENT); + } + switch (desc_property->type) { + case MIKROBUS_PROPERTY_TYPE_U8: + val_u8 = kmemdup(&desc_property->value, + (desc_property->length) * sizeof(u8), + GFP_KERNEL); + if (desc_property->length == 1) + properties[i] = PROPERTY_ENTRY_U8(prop_name, *val_u8); + else + properties[i] = PROPERTY_ENTRY_U8_ARRAY_LEN(prop_name, + (void *)desc_property->value, desc_property->length); + break; + case MIKROBUS_PROPERTY_TYPE_U16: + val_u16 = kmemdup(&desc_property->value, + (desc_property->length) * sizeof(u16), GFP_KERNEL); + if (desc_property->length == 1) + properties[i] = PROPERTY_ENTRY_U16(prop_name, *val_u16); + else + properties[i] = PROPERTY_ENTRY_U16_ARRAY_LEN(prop_name, + (void *)desc_property->value, desc_property->length); + break; + case MIKROBUS_PROPERTY_TYPE_U32: + val_u32 = kmemdup(&desc_property->value, + (desc_property->length) * sizeof(u32), GFP_KERNEL); + if (desc_property->length == 1) + properties[i] = PROPERTY_ENTRY_U32(prop_name, *val_u32); + else + properties[i] = PROPERTY_ENTRY_U32_ARRAY_LEN(prop_name, + (void *)desc_property->value, desc_property->length); + break; + case MIKROBUS_PROPERTY_TYPE_U64: + val_u64 = kmemdup(&desc_property->value, + (desc_property->length) * sizeof(u64), GFP_KERNEL); + if (desc_property->length == 1) + properties[i] = PROPERTY_ENTRY_U64(prop_name, *val_u64); + else + properties[i] = PROPERTY_ENTRY_U64_ARRAY_LEN(prop_name, + (void *)desc_property->value, desc_property->length); + break; + default: + kfree(properties); + return ERR_PTR(-EINVAL); + } + } + return properties; +} + +static u8 *mikrobus_property_link_get(struct addon_board_info *board, u8 prop_id, + struct board_device_info *board_dev, u8 prop_type) +{ + struct greybus_descriptor_property *desc_property; + struct manifest_desc *descriptor; + bool found = false; + u8 *val_u8; + + if (!prop_id) + return NULL; + list_for_each_entry(descriptor, &board->manifest_descs, links) { + if (descriptor->type != GREYBUS_TYPE_PROPERTY) + continue; + desc_property = descriptor->data; + if (desc_property->id == prop_id && desc_property->type == prop_type) { + found = true; + break; + } + } + if (!found) + return ERR_PTR(-ENOENT); + val_u8 = kmemdup(&desc_property->value, desc_property->length, GFP_KERNEL); + if (prop_type == MIKROBUS_PROPERTY_TYPE_GPIO) + board_dev->num_gpio_resources = desc_property->length; + else if (prop_type == MIKROBUS_PROPERTY_TYPE_PROPERTY) + board_dev->num_properties = desc_property->length; + else if (prop_type == MIKROBUS_PROPERTY_TYPE_REGULATOR) + board_dev->num_regulators = desc_property->length; + else if (prop_type == MIKROBUS_PROPERTY_TYPE_CLOCK) + board_dev->num_clocks = desc_property->length; + return val_u8; +} + +static int mikrobus_manifest_attach_device(struct addon_board_info *board, + struct greybus_descriptor_device *dev_desc) +{ + struct board_device_info *board_dev; + struct gpiod_lookup_table *lookup; + struct greybus_descriptor_property *desc_property; + struct manifest_desc *descriptor; + u8 *gpio_desc_link; + u8 *prop_link; + u8 *reg_link; + u8 *clock_link; + u8 *gpioval; + int retval; + int i; + + board_dev = kzalloc(sizeof(*board_dev), GFP_KERNEL); + if (!board_dev) + return -ENOMEM; + board_dev->id = dev_desc->id; + board_dev->drv_name = mikrobus_string_get(board, dev_desc->driver_stringid); + if (!board_dev->drv_name) { + retval = -ENOENT; + goto err_free_board_dev; + } + board_dev->protocol = dev_desc->protocol; + board_dev->reg = dev_desc->reg; + board_dev->irq = dev_desc->irq; + board_dev->irq_type = dev_desc->irq_type; + board_dev->max_speed_hz = le32_to_cpu(dev_desc->max_speed_hz); + board_dev->mode = dev_desc->mode; + pr_info("parsed device %d, driver=%s", board_dev->id, board_dev->drv_name); + + if (dev_desc->prop_link > 0) { + prop_link = mikrobus_property_link_get(board, dev_desc->prop_link, + board_dev, MIKROBUS_PROPERTY_TYPE_PROPERTY); + if (!prop_link) { + retval = -ENOENT; + goto err_free_board_dev; + } + pr_info("device %d, number of properties=%d", board_dev->id, + board_dev->num_properties); + board_dev->properties = mikrobus_property_entry_get(board, prop_link, + board_dev->num_properties); + } + + if (dev_desc->gpio_link > 0) { + gpio_desc_link = mikrobus_property_link_get(board, dev_desc->gpio_link, board_dev, + MIKROBUS_PROPERTY_TYPE_GPIO); + if (!gpio_desc_link) { + retval = -ENOENT; + goto err_free_board_dev; + } + pr_info("device %d, number of gpio resource=%d", board_dev->id, + board_dev->num_gpio_resources); + lookup = kzalloc(struct_size(lookup, table, board_dev->num_gpio_resources), + GFP_KERNEL); + if (!lookup) { + retval = -ENOMEM; + goto err_free_board_dev; + } + for (i = 0; i < board_dev->num_gpio_resources; i++) { + list_for_each_entry(descriptor, &board->manifest_descs, links) { + if (descriptor->type != GREYBUS_TYPE_PROPERTY) + continue; + desc_property = descriptor->data; + if (desc_property->id == gpio_desc_link[i]) { + gpioval = desc_property->value; + lookup->table[i].chip_hwnum = gpioval[0]; + lookup->table[i].flags = gpioval[1]; + lookup->table[i].con_id = + mikrobus_string_get(board, + desc_property->propname_stringid); + break; + } + } + } + board_dev->gpio_lookup = lookup; + } + + if (dev_desc->reg_link > 0) { + reg_link = mikrobus_property_link_get(board, dev_desc->reg_link, + board_dev, MIKROBUS_PROPERTY_TYPE_REGULATOR); + if (!reg_link) { + retval = -ENOENT; + goto err_free_board_dev; + } + pr_info("device %d, number of regulators=%d", board_dev->id, + board_dev->num_regulators); + board_dev->regulators = mikrobus_property_entry_get(board, reg_link, + board_dev->num_regulators); + } + + if (dev_desc->clock_link > 0) { + clock_link = mikrobus_property_link_get(board, dev_desc->clock_link, + board_dev, MIKROBUS_PROPERTY_TYPE_CLOCK); + if (!clock_link) { + retval = -ENOENT; + goto err_free_board_dev; + } + pr_info("device %d, number of clocks=%d", board_dev->id, + board_dev->num_clocks); + board_dev->clocks = mikrobus_property_entry_get(board, clock_link, + board_dev->num_clocks); + } + list_add_tail(&board_dev->links, &board->devices); + return 0; +err_free_board_dev: + kfree(board_dev); + return retval; +} + +static int mikrobus_manifest_parse_devices(struct addon_board_info *board) +{ + struct greybus_descriptor_device *desc_device; + struct manifest_desc *desc, *next; + int retval; + int devcount = 0; + + list_for_each_entry_safe(desc, next, &board->manifest_descs, links) { + if (desc->type != GREYBUS_TYPE_DEVICE) + continue; + desc_device = desc->data; + retval = mikrobus_manifest_attach_device(board, desc_device); + devcount++; + } + return devcount; +} + +int mikrobus_manifest_parse(struct addon_board_info *board, void *data, + size_t size) +{ + struct greybus_manifest_header *header; + struct greybus_manifest *manifest; + struct greybus_descriptor *desc; + u16 manifest_size; + int dev_count; + int desc_size; + + if (size < sizeof(*header)) { + pr_err("short manifest (%zu < %zu)", size, sizeof(*header)); + return -EINVAL; + } + + manifest = data; + header = &manifest->header; + manifest_size = le16_to_cpu(header->size); + + if (manifest_size != size) { + pr_err("invalid manifest size(%zu < %u)", size, manifest_size); + return -EINVAL; + } + + if (header->version_major > MIKROBUS_VERSION_MAJOR) { + pr_err("manifest version too new (%u.%u > %u.%u)", + header->version_major, header->version_minor, + MIKROBUS_VERSION_MAJOR, MIKROBUS_VERSION_MINOR); + return -EINVAL; + } + + desc = manifest->descriptors; + size -= sizeof(*header); + while (size) { + desc_size = board_descriptor_add(board, desc, size); + if (desc_size < 0) { + pr_err("invalid manifest descriptor, size: %u", desc_size); + return -EINVAL; + } + desc = (void *)desc + desc_size; + size -= desc_size; + } + mikrobus_state_get(board); + dev_count = mikrobus_manifest_parse_devices(board); + pr_info(" %s manifest parsed with %d devices", board->name, dev_count); + manifest_descriptor_release_all(board); + return true; +} + +size_t mikrobus_manifest_header_validate(void *data, size_t size) +{ + struct greybus_manifest_header *header; + u16 manifest_size; + + if (size < sizeof(*header)) { + pr_err("short manifest (%zu < %zu)", size, sizeof(*header)); + return -EINVAL; + } + header = data; + manifest_size = le16_to_cpu(header->size); + if (header->version_major > MIKROBUS_VERSION_MAJOR) { + pr_err("manifest version too new (%u.%u > %u.%u)", + header->version_major, header->version_minor, + MIKROBUS_VERSION_MAJOR, MIKROBUS_VERSION_MINOR); + return -EINVAL; + } + return manifest_size; +} + diff --git b/drivers/misc/mikrobus/mikrobus_manifest.h b/drivers/misc/mikrobus/mikrobus_manifest.h new file mode 100644 index 0000000..36b64b2 --- /dev/null +++ b/drivers/misc/mikrobus/mikrobus_manifest.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mikroBUS manifest definition + * extension to Greybus Manifest Definition + * + * Copyright 2014-2015 Google Inc. + * Copyright 2014-2015 Linaro Ltd. + * + * Released under the GPLv2 and BSD licenses. + */ + +#ifndef __MIKROBUS_MANIFEST_H +#define __MIKROBUS_MANIFEST_H + +#include "mikrobus_core.h" + +int mikrobus_manifest_parse(struct addon_board_info *info, void *data, size_t size); +size_t mikrobus_manifest_header_validate(void *data, size_t size); + +#endif /* __MIKROBUS_MANIFEST_H */ diff --git b/drivers/misc/udoo_ard.c b/drivers/misc/udoo_ard.c new file mode 100755 index 0000000..2210738 --- /dev/null +++ b/drivers/misc/udoo_ard.c @@ -0,0 +1,417 @@ +/* + * udoo_ard.c + * UDOO quad/dual Arduino flash erase / CPU resetter + * + * Copyright (C) 2013-2015 Aidilab srl + * Author: UDOO Team + * Author: Giuseppe Pagano + * Author: Francesco Montefoschi + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "udoo_ard" +#define PINCTRL_DEFAULT "default" +#define AUTH_TOKEN 0x5A5A +#define MAX_MSEC_SINCE_LAST_IRQ 400 +#define GRAY_TIME_BETWEEN_RESET 10000 // In this time we can't accept new erase/reset code + +static struct workqueue_struct *erase_reset_wq; +typedef struct { + struct work_struct erase_reset_work; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + int step; + int cmdcode; + int erase_reset_lock; + int gpio_bossac_clk; + int gpio_bossac_dat; + int gpio_ard_erase; + int gpio_ard_reset; + unsigned long last_int_time_in_ns; + unsigned long last_int_time_in_sec; +} erase_reset_work_t; + +erase_reset_work_t *work; +static u32 origTX, origRX; // original UART4 TX/RX pad control registers +static int major; // for /dev/udoo_ard +static struct class *udoo_class; + +static struct platform_device_id udoo_ard_devtype[] = { + { + /* keep it for coldfire */ + .name = DRIVER_NAME, + .driver_data = 0, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, udoo_ard_devtype); + +static const struct of_device_id udoo_ard_dt_ids[] = { + { .compatible = "udoo,imx6q-udoo-ard", .data = &udoo_ard_devtype[0], }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, udoo_ard_dt_ids); + +static void disable_serial(void) +{ + u32 addrTX; + void __iomem *_addrTX; + + printk("[bossac] Disable UART4 serial port.\n"); + + addrTX = 0x20E01F8; + _addrTX = ioremap(addrTX, 8); + + origTX = __raw_readl(_addrTX); + origRX = __raw_readl(_addrTX + 0x4); + + __raw_writel(0x15, _addrTX); + __raw_writel(0x15, _addrTX + 0x4); + + iounmap(_addrTX); +} + +static void enable_serial(void) +{ + u32 addrTX; + void __iomem *_addrTX; + + printk("[bossac] Enable UART4 serial port.\n"); + + addrTX = 0x20E01F8; + _addrTX = ioremap(addrTX, 8); + + __raw_writel(origTX, _addrTX); + __raw_writel(origRX, _addrTX + 0x4); + + iounmap(_addrTX); +} + +static void erase_reset(void) +{ + printk("[bossac] UDOO ERASE and RESET on Sam3x started.\n"); + + gpio_direction_input(work->gpio_ard_erase); + gpio_set_value(work->gpio_ard_reset, 1); + msleep(1); + + gpio_direction_output(work->gpio_ard_erase, 1); + msleep(300); + gpio_direction_input(work->gpio_ard_erase); + + msleep(10); + gpio_set_value(work->gpio_ard_reset, 0); + + msleep(80); + gpio_set_value(work->gpio_ard_reset, 1); + + printk("[bossac] UDOO ERASE and RESET on Sam3x EXECUTED.\n"); +} + +static void shutdown_sam3x(void) +{ + printk("[bossac] RESET on Sam3x.\n"); + + gpio_set_value(work->gpio_ard_reset, 0); +} + +static void erase_reset_wq_function( struct work_struct *work2) +{ + disable_serial(); + erase_reset(); + msleep(GRAY_TIME_BETWEEN_RESET); + + work->erase_reset_lock = 0; +} + +/* + * Called everytime the gpio_bossac_clk signal toggles. + * If the auth token (16 bit) is found, we look for the command code (4 bit). + * The code 0x0F is sent by Bossac to trigger an erase/reset (to achieve this, + * erase_reset_wq is scheduled). Before starting to program the flash, we disable + * the UART4 serial port, otherwise there is too noise on the serial lines (the + * programming port and UART4 port are connected together, see hw schematics). + * When Bossac finishes to flash/verify, the code 0x00 is sent which re-enables + * the UART4 port. + */ +static irqreturn_t udoo_bossac_req(int irq, void *dev_id) +{ + int retval, auth_bit, expected_bit, msec_since_last_irq; + u64 nowsec; + unsigned long rem_nsec; + erase_reset_work_t *erase_reset_work; + + auth_bit = 0; + if (gpio_get_value(work->gpio_bossac_dat) != 0x0) { + auth_bit = 1; + } + + erase_reset_work = (erase_reset_work_t *)work; + + nowsec = local_clock(); + rem_nsec = do_div(nowsec, 1000000000) ; + msec_since_last_irq = (((unsigned long)nowsec * 1000) + rem_nsec/1000000 ) - (((unsigned long)erase_reset_work->last_int_time_in_sec * 1000) + erase_reset_work->last_int_time_in_ns/1000000); + + if (msec_since_last_irq > MAX_MSEC_SINCE_LAST_IRQ) { + erase_reset_work->step = 0; +#ifdef DEBUG + printk("[bossac] Reset authentication timeout!\n"); +#endif + } + +#ifdef DEBUG + printk("[bossac] STEP %d -> 0x%d \n", erase_reset_work->step, auth_bit); +#endif + erase_reset_work->last_int_time_in_ns = rem_nsec; + erase_reset_work->last_int_time_in_sec = nowsec; + + if ( erase_reset_work->step < 16 ) { // Authenticating received token bit. + expected_bit = (( AUTH_TOKEN >> erase_reset_work->step ) & 0x01 ); + if ( auth_bit == expected_bit ) { + erase_reset_work->step = erase_reset_work->step + 1; + } else { + erase_reset_work->step = 0; + } + } else { // Passed all authentication step. Receiving command code. + erase_reset_work->cmdcode = erase_reset_work->cmdcode | (auth_bit << (erase_reset_work->step - 16)); + erase_reset_work->step = erase_reset_work->step + 1; + } + +#ifdef DEBUG + printk("erase_reset_work->erase_reset_lock = %d \n", erase_reset_work->erase_reset_lock); +#endif + if ( erase_reset_work->step == 20 ) { // Passed authentication and code acquiring step. +#ifdef DEBUG + printk("[bossac] Received code = 0x%04x \n", erase_reset_work->cmdcode); +#endif + if (erase_reset_work->cmdcode == 0xF) { + if (erase_reset_work->erase_reset_lock == 0) { + erase_reset_work->erase_reset_lock = 1; + retval = queue_work( erase_reset_wq, (struct work_struct *)work ); + } else { +#ifdef DEBUG + printk("Erase and reset operation already in progress. Do nothing.\n"); +#endif + } + } else { + enable_serial(); + } + erase_reset_work->step = 0; + erase_reset_work->cmdcode = 0; + } + + return IRQ_HANDLED; +} + +/* + * Takes control of clock, data, erase, reset GPIOs. + */ +static int gpio_setup(void) +{ + int ret; + + ret = gpio_request(work->gpio_bossac_clk, "BOSSA_CLK"); + if (ret) { + printk(KERN_ERR "request BOSSA_CLK IRQ\n"); + return -1; + } else { + gpio_direction_input(work->gpio_bossac_clk); + } + + ret = gpio_request(work->gpio_bossac_dat, "BOSSA_DAT"); + if (ret) { + printk(KERN_ERR "request BOSSA_DAT IRQ\n"); + return -1; + } else { + gpio_direction_input(work->gpio_bossac_dat); + } + + ret = gpio_request(work->gpio_ard_erase, "BOSSAC"); + if (ret) { + printk(KERN_ERR "request GPIO FOR ARDUINO ERASE\n"); + return -1; + } else { + gpio_direction_input(work->gpio_ard_erase); + } + + ret = gpio_request(work->gpio_ard_reset, "BOSSAC"); + if (ret) { + printk(KERN_ERR "request GPIO FOR ARDUINO RESET\n"); + return -1; + } else { + gpio_direction_output(work->gpio_ard_reset, 1); + } + + return 0; +} + +static ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t *off) +{ + char msg[10]; + long res; + + if (len > 10) + return -EINVAL; + + + res = copy_from_user(msg, buff, len); + if (res) { + return -EFAULT; + } + msg[len] = '\0'; + + if (strcmp(msg, "erase")==0) { + erase_reset(); + } else if (strcmp(msg, "shutdown")==0) { + shutdown_sam3x(); + } else if (strcmp(msg, "uartoff")==0) { + disable_serial(); + } else if (strcmp(msg, "uarton")==0) { + enable_serial(); + } else { + printk("[bossac] udoo_ard invalid operation! %s", msg); + } + + return len; +} + +static struct file_operations fops = { + .write = device_write, +}; + +/* + * If a fdt udoo_ard entry is found, we register an IRQ on bossac clock line + * and we create /dev/udoo_ard + */ +static int udoo_ard_probe(struct platform_device *pdev) +{ + int retval; + struct device *temp_class; + struct platform_device *bdev; + struct device_node *np; + + bdev = kzalloc(sizeof(*bdev), GFP_KERNEL); + np = pdev->dev.of_node; + + if (!np) + return -ENODEV; + + work = (erase_reset_work_t *)kmalloc(sizeof(erase_reset_work_t), GFP_KERNEL); + if (work) { + work->gpio_ard_reset = of_get_named_gpio(np, "bossac-reset-gpio", 0); + work->gpio_ard_erase = of_get_named_gpio(np, "bossac-erase-gpio", 0); + work->gpio_bossac_clk = of_get_named_gpio(np, "bossac-clk-gpio", 0); + work->gpio_bossac_dat = of_get_named_gpio(np, "bossac-dat-gpio", 0); + work->pinctrl = devm_pinctrl_get(&pdev->dev); + work->pins_default = pinctrl_lookup_state(work->pinctrl, PINCTRL_DEFAULT); + } else { + printk("[bossac] Failed to allocate data structure."); + return -ENOMEM; + } + + pinctrl_select_state(work->pinctrl, work->pins_default); + gpio_setup(); + + printk("[bossac] Registering IRQ %d for BOSSAC Arduino erase/reset operation\n", gpio_to_irq(work->gpio_bossac_clk)); + retval = request_irq(gpio_to_irq(work->gpio_bossac_clk), udoo_bossac_req, IRQF_TRIGGER_FALLING, "UDOO", bdev); + + major = register_chrdev(major, "udoo_ard", &fops); + if (major < 0) { + printk(KERN_ERR "[bossac] Cannot get major for UDOO Ard\n"); + return -EBUSY; + } + + udoo_class = class_create(THIS_MODULE, "udoo_ard"); + if (IS_ERR(udoo_class)) { + return PTR_ERR(udoo_class); + } + + temp_class = device_create(udoo_class, NULL, MKDEV(major, 0), NULL, "udoo_ard"); + if (IS_ERR(temp_class)) { + return PTR_ERR(temp_class); + } + + printk("[bossac] Created device file /dev/udoo_ard\n"); + + erase_reset_wq = create_workqueue("erase_reset_queue"); + if (erase_reset_wq) { + + /* Queue some work (item 1) */ + if (work) { + INIT_WORK( (struct work_struct *)work, erase_reset_wq_function ); + work->step = 1; + work->cmdcode = 0; + work->last_int_time_in_ns = 0; + work->last_int_time_in_sec = 0; + work->erase_reset_lock = 0; + // retval = queue_work( erase_reset_wq, (struct work_struct *)work ); + } + } + return 0; +} + +static int udoo_ard_remove(struct platform_device *pdev) +{ + printk("[bossac] Unloading UDOO ard driver.\n"); + free_irq(gpio_to_irq(work->gpio_bossac_clk), NULL); + + gpio_free(work->gpio_ard_reset); + gpio_free(work->gpio_ard_erase); + gpio_free(work->gpio_bossac_clk); + gpio_free(work->gpio_bossac_dat); + + device_destroy(udoo_class, MKDEV(major, 0)); + class_destroy(udoo_class); + unregister_chrdev(major, "udoo_ard"); + + return 0; +} + +static struct platform_driver udoo_ard_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = udoo_ard_dt_ids, + }, + .id_table = udoo_ard_devtype, + .probe = udoo_ard_probe, + .remove = udoo_ard_remove, +}; + +module_platform_driver(udoo_ard_driver); + +MODULE_ALIAS("platform:"DRIVER_NAME); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/ti/davinci_mdio.c b/drivers/net/ethernet/ti/davinci_mdio.c index 702fdc3..e201a20 100644 --- a/drivers/net/ethernet/ti/davinci_mdio.c +++ b/drivers/net/ethernet/ti/davinci_mdio.c @@ -92,6 +92,10 @@ struct davinci_mdio_data { u32 clk_div; }; +#if IS_ENABLED(CONFIG_OF) +static void davinci_mdio_update_dt_from_phymask(u32 phy_mask); +#endif + static void davinci_mdio_init_clk(struct davinci_mdio_data *data) { u32 mdio_in, div, mdio_out_khz, access_time; @@ -159,6 +163,12 @@ static int davinci_mdio_reset(struct mii_bus *bus) /* restrict mdio bus to live phys only */ dev_info(data->dev, "detected phy mask %x\n", ~phy_mask); phy_mask = ~phy_mask; + + #if IS_ENABLED(CONFIG_OF) + if (of_machine_is_compatible("ti,am335x-bone")) + davinci_mdio_update_dt_from_phymask(phy_mask); + #endif + } else { /* desperately scan all phys */ dev_warn(data->dev, "no live phy, scanning all\n"); @@ -474,6 +484,93 @@ static int davinci_mdio_runtime_resume(struct device *dev) davinci_mdio_enable(data); return 0; } +static void davinci_mdio_update_dt_from_phymask(u32 phy_mask) +{ + int i, len, skip; + u32 addr; + __be32 *old_phy_p, *phy_id_p; + struct property *phy_id_property = NULL; + struct device_node *node_p, *slave_p; + + addr = 0; + + for (i = 0; i < PHY_MAX_ADDR; i++) { + if ((phy_mask & (1 << i)) == 0) { + addr = (u32) i; + break; + } + } + + for_each_compatible_node(node_p, NULL, "ti,cpsw") { + for_each_node_by_name(slave_p, "slave") { + +#if IS_ENABLED(CONFIG_OF_OVERLAY) + skip = 1; + // Hack, the overlay fixup "slave" doesn't have phy-mode... + old_phy_p = (__be32 *) of_get_property(slave_p, "phy-mode", &len); + + if (len != (sizeof(__be32 *) * 1)) + { + skip = 0; + } + + if (skip) { +#endif + + old_phy_p = (__be32 *) of_get_property(slave_p, "phy_id", &len); + + if (len != (sizeof(__be32 *) * 2)) + goto err_out; + + if (old_phy_p) { + + phy_id_property = kzalloc(sizeof(*phy_id_property), GFP_KERNEL); + + if (! phy_id_property) + goto err_out; + + phy_id_property->length = len; + phy_id_property->name = kstrdup("phy_id", GFP_KERNEL); + phy_id_property->value = kzalloc(len, GFP_KERNEL); + + if (! phy_id_property->name) + goto err_out; + + if (! phy_id_property->value) + goto err_out; + + memcpy(phy_id_property->value, old_phy_p, len); + + phy_id_p = (__be32 *) phy_id_property->value + 1; + + *phy_id_p = cpu_to_be32(addr); + + of_update_property(slave_p, phy_id_property); + pr_info("davinci_mdio: dt: updated phy_id[%d] from phy_mask[%x]\n", addr, phy_mask); + + ++addr; + } +#if IS_ENABLED(CONFIG_OF_OVERLAY) + } +#endif + } + } + + return; + +err_out: + + if (phy_id_property) { + if (phy_id_property->name) + kfree(phy_id_property->name); + + if (phy_id_property->value) + kfree(phy_id_property->value); + + if (phy_id_property) + kfree(phy_id_property); + } +} #endif #ifdef CONFIG_PM_SLEEP diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index ff0350c..78b6fa2 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -2,3 +2,4 @@ source "drivers/power/avs/Kconfig" source "drivers/power/reset/Kconfig" source "drivers/power/supply/Kconfig" +source "drivers/power/pwrseq/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b7c2e37..13046c7 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_POWER_RESET) += reset/ obj-$(CONFIG_POWER_SUPPLY) += supply/ +obj-$(CONFIG_POWER_SEQUENCE) += pwrseq/ diff --git b/drivers/power/pwrseq/Kconfig b/drivers/power/pwrseq/Kconfig new file mode 100644 index 0000000..c6b3569 --- /dev/null +++ b/drivers/power/pwrseq/Kconfig @@ -0,0 +1,20 @@ +# +# Power Sequence library +# + +menuconfig POWER_SEQUENCE + bool "Power sequence control" + help + It is used for drivers which needs to do power sequence + (eg, turn on clock, toggle reset gpio) before the related + devices can be found by hardware, eg, USB bus. + +if POWER_SEQUENCE + +config PWRSEQ_GENERIC + bool "Generic power sequence control" + depends on OF + help + This is the generic power sequence control library, and is + supposed to support common power sequence usage. +endif diff --git b/drivers/power/pwrseq/Makefile b/drivers/power/pwrseq/Makefile new file mode 100644 index 0000000..ad82389 --- /dev/null +++ b/drivers/power/pwrseq/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_POWER_SEQUENCE) += core.o +obj-$(CONFIG_PWRSEQ_GENERIC) += pwrseq_generic.o diff --git b/drivers/power/pwrseq/core.c b/drivers/power/pwrseq/core.c new file mode 100644 index 0000000..3d19e62 --- /dev/null +++ b/drivers/power/pwrseq/core.c @@ -0,0 +1,335 @@ +/* + * core.c power sequence core file + * + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Author: Peter Chen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. + */ + +#include +#include +#include +#include +#include + +static DEFINE_MUTEX(pwrseq_list_mutex); +static LIST_HEAD(pwrseq_list); + +static int pwrseq_get(struct device_node *np, struct pwrseq *p) +{ + if (p && p->get) + return p->get(np, p); + + return -ENOTSUPP; +} + +static int pwrseq_on(struct pwrseq *p) +{ + if (p && p->on) + return p->on(p); + + return -ENOTSUPP; +} + +static void pwrseq_off(struct pwrseq *p) +{ + if (p && p->off) + p->off(p); +} + +static void pwrseq_put(struct pwrseq *p) +{ + if (p && p->put) + p->put(p); +} + +/** + * pwrseq_register - Add pwrseq instance to global pwrseq list + * + * @pwrseq: the pwrseq instance + */ +void pwrseq_register(struct pwrseq *pwrseq) +{ + mutex_lock(&pwrseq_list_mutex); + list_add(&pwrseq->node, &pwrseq_list); + mutex_unlock(&pwrseq_list_mutex); +} +EXPORT_SYMBOL_GPL(pwrseq_register); + +/** + * pwrseq_unregister - Remove pwrseq instance from global pwrseq list + * + * @pwrseq: the pwrseq instance + */ +void pwrseq_unregister(struct pwrseq *pwrseq) +{ + mutex_lock(&pwrseq_list_mutex); + list_del(&pwrseq->node); + mutex_unlock(&pwrseq_list_mutex); +} +EXPORT_SYMBOL_GPL(pwrseq_unregister); + +static struct pwrseq *pwrseq_find_available_instance(struct device_node *np) +{ + struct pwrseq *pwrseq; + + mutex_lock(&pwrseq_list_mutex); + list_for_each_entry(pwrseq, &pwrseq_list, node) { + if (pwrseq->used) + continue; + + /* compare compatible string for pwrseq node */ + if (of_match_node(pwrseq->pwrseq_of_match_table, np)) { + pwrseq->used = true; + mutex_unlock(&pwrseq_list_mutex); + return pwrseq; + } + + /* return generic pwrseq instance */ + if (!strcmp(pwrseq->pwrseq_of_match_table->compatible, + "generic")) { + pr_debug("using generic pwrseq instance for %s\n", + np->full_name); + pwrseq->used = true; + mutex_unlock(&pwrseq_list_mutex); + return pwrseq; + } + } + mutex_unlock(&pwrseq_list_mutex); + pr_debug("Can't find any pwrseq instances for %s\n", np->full_name); + + return NULL; +} + +/** + * of_pwrseq_on - Carry out power sequence on for device node + * + * @np: the device node would like to power on + * + * Carry out a single device power on. If multiple devices + * need to be handled, use of_pwrseq_on_list() instead. + * + * Return a pointer to the power sequence instance on success, + * or an error code otherwise. + */ +struct pwrseq *of_pwrseq_on(struct device_node *np) +{ + struct pwrseq *pwrseq; + int ret; + + pwrseq = pwrseq_find_available_instance(np); + if (!pwrseq) + return ERR_PTR(-ENOENT); + + ret = pwrseq_get(np, pwrseq); + if (ret) { + /* Mark current pwrseq as unused */ + pwrseq->used = false; + return ERR_PTR(ret); + } + + ret = pwrseq_on(pwrseq); + if (ret) + goto pwr_put; + + return pwrseq; + +pwr_put: + pwrseq_put(pwrseq); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(of_pwrseq_on); + +/** + * of_pwrseq_off - Carry out power sequence off for this pwrseq instance + * + * @pwrseq: the pwrseq instance which related device would like to be off + * + * This API is used to power off single device, it is the opposite + * operation for of_pwrseq_on. + */ +void of_pwrseq_off(struct pwrseq *pwrseq) +{ + pwrseq_off(pwrseq); + pwrseq_put(pwrseq); +} +EXPORT_SYMBOL_GPL(of_pwrseq_off); + +/** + * of_pwrseq_on_list - Carry out power sequence on for list + * + * @np: the device node would like to power on + * @head: the list head for pwrseq list on this bus + * + * This API is used to power on multiple devices at single bus. + * If there are several devices on bus (eg, USB bus), uses this + * this API. Otherwise, use of_pwrseq_on instead. After the device + * is powered on successfully, it will be added to pwrseq list for + * this bus. The caller needs to use mutex_lock for concurrent. + * + * Return 0 on success, or an error value otherwise. + */ +int of_pwrseq_on_list(struct device_node *np, struct list_head *head) +{ + struct pwrseq *pwrseq; + struct pwrseq_list_per_dev *pwrseq_list_node; + + pwrseq_list_node = kzalloc(sizeof(*pwrseq_list_node), GFP_KERNEL); + if (!pwrseq_list_node) + return -ENOMEM; + + pwrseq = of_pwrseq_on(np); + if (IS_ERR(pwrseq)) { + kfree(pwrseq_list_node); + return PTR_ERR(pwrseq); + } + + pwrseq_list_node->pwrseq = pwrseq; + list_add(&pwrseq_list_node->list, head); + + return 0; +} +EXPORT_SYMBOL_GPL(of_pwrseq_on_list); + +/** + * of_pwrseq_off_list - Carry out power sequence off for the list + * + * @head: the list head for pwrseq instance list on this bus + * + * This API is used to power off all devices on this bus, it is + * the opposite operation for of_pwrseq_on_list. + * The caller needs to use mutex_lock for concurrent. + */ +void of_pwrseq_off_list(struct list_head *head) +{ + struct pwrseq *pwrseq; + struct pwrseq_list_per_dev *pwrseq_list_node, *tmp_node; + + list_for_each_entry_safe(pwrseq_list_node, tmp_node, head, list) { + pwrseq = pwrseq_list_node->pwrseq; + of_pwrseq_off(pwrseq); + list_del(&pwrseq_list_node->list); + kfree(pwrseq_list_node); + } +} +EXPORT_SYMBOL_GPL(of_pwrseq_off_list); + +/** + * pwrseq_suspend - Carry out power sequence suspend for this pwrseq instance + * + * @pwrseq: the pwrseq instance + * + * This API is used to do suspend operation on pwrseq instance. + * + * Return 0 on success, or an error value otherwise. + */ +int pwrseq_suspend(struct pwrseq *p) +{ + int ret = 0; + + if (p && p->suspend) + ret = p->suspend(p); + else + return ret; + + if (!ret) + p->suspended = true; + else + pr_err("%s failed\n", __func__); + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_suspend); + +/** + * pwrseq_resume - Carry out power sequence resume for this pwrseq instance + * + * @pwrseq: the pwrseq instance + * + * This API is used to do resume operation on pwrseq instance. + * + * Return 0 on success, or an error value otherwise. + */ +int pwrseq_resume(struct pwrseq *p) +{ + int ret = 0; + + if (p && p->resume) + ret = p->resume(p); + else + return ret; + + if (!ret) + p->suspended = false; + else + pr_err("%s failed\n", __func__); + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_resume); + +/** + * pwrseq_suspend_list - Carry out power sequence suspend for list + * + * @head: the list head for pwrseq instance list on this bus + * + * This API is used to do suspend on all power sequence instances on this bus. + * The caller needs to use mutex_lock for concurrent. + */ +int pwrseq_suspend_list(struct list_head *head) +{ + struct pwrseq *pwrseq; + struct pwrseq_list_per_dev *pwrseq_list_node; + int ret = 0; + + list_for_each_entry(pwrseq_list_node, head, list) { + ret = pwrseq_suspend(pwrseq_list_node->pwrseq); + if (ret) + break; + } + + if (ret) { + list_for_each_entry(pwrseq_list_node, head, list) { + pwrseq = pwrseq_list_node->pwrseq; + if (pwrseq->suspended) + pwrseq_resume(pwrseq); + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_suspend_list); + +/** + * pwrseq_resume_list - Carry out power sequence resume for the list + * + * @head: the list head for pwrseq instance list on this bus + * + * This API is used to do resume on all power sequence instances on this bus. + * The caller needs to use mutex_lock for concurrent. + */ +int pwrseq_resume_list(struct list_head *head) +{ + struct pwrseq_list_per_dev *pwrseq_list_node; + int ret = 0; + + list_for_each_entry(pwrseq_list_node, head, list) { + ret = pwrseq_resume(pwrseq_list_node->pwrseq); + if (ret) + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(pwrseq_resume_list); diff --git b/drivers/power/pwrseq/pwrseq_generic.c b/drivers/power/pwrseq/pwrseq_generic.c new file mode 100644 index 0000000..4e7c090 --- /dev/null +++ b/drivers/power/pwrseq/pwrseq_generic.c @@ -0,0 +1,234 @@ +/* + * pwrseq_generic.c Generic power sequence handling + * + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Author: Peter Chen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct pwrseq_generic { + struct pwrseq pwrseq; + struct gpio_desc *gpiod_reset; + struct clk *clks[PWRSEQ_MAX_CLKS]; + u32 duration_us; + bool suspended; +}; + +#define to_generic_pwrseq(p) container_of(p, struct pwrseq_generic, pwrseq) + +static int pwrseq_generic_alloc_instance(void); +static const struct of_device_id generic_id_table[] = { + { .compatible = "generic",}, + { /* sentinel */ } +}; + +static int pwrseq_generic_suspend(struct pwrseq *pwrseq) +{ + struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq); + int clk; + + for (clk = PWRSEQ_MAX_CLKS - 1; clk >= 0; clk--) + clk_disable_unprepare(pwrseq_gen->clks[clk]); + + pwrseq_gen->suspended = true; + return 0; +} + +static int pwrseq_generic_resume(struct pwrseq *pwrseq) +{ + struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq); + int clk, ret = 0; + + for (clk = 0; clk < PWRSEQ_MAX_CLKS && pwrseq_gen->clks[clk]; clk++) { + ret = clk_prepare_enable(pwrseq_gen->clks[clk]); + if (ret) { + pr_err("Can't enable clock, ret=%d\n", ret); + goto err_disable_clks; + } + } + + pwrseq_gen->suspended = false; + return ret; + +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(pwrseq_gen->clks[clk]); + + return ret; +} + +static void pwrseq_generic_put(struct pwrseq *pwrseq) +{ + struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq); + int clk; + + if (pwrseq_gen->gpiod_reset) + gpiod_put(pwrseq_gen->gpiod_reset); + + for (clk = 0; clk < PWRSEQ_MAX_CLKS; clk++) + clk_put(pwrseq_gen->clks[clk]); + + pwrseq_unregister(&pwrseq_gen->pwrseq); + kfree(pwrseq_gen); +} + +static void pwrseq_generic_off(struct pwrseq *pwrseq) +{ + struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq); + int clk; + + if (pwrseq_gen->suspended) + return; + + for (clk = PWRSEQ_MAX_CLKS - 1; clk >= 0; clk--) + clk_disable_unprepare(pwrseq_gen->clks[clk]); +} + +static int pwrseq_generic_on(struct pwrseq *pwrseq) +{ + struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq); + int clk, ret = 0; + struct gpio_desc *gpiod_reset = pwrseq_gen->gpiod_reset; + + for (clk = 0; clk < PWRSEQ_MAX_CLKS && pwrseq_gen->clks[clk]; clk++) { + ret = clk_prepare_enable(pwrseq_gen->clks[clk]); + if (ret) { + pr_err("Can't enable clock, ret=%d\n", ret); + goto err_disable_clks; + } + } + + if (gpiod_reset) { + u32 duration_us = pwrseq_gen->duration_us; + + if (duration_us <= 10) + udelay(10); + else + usleep_range(duration_us, duration_us + 100); + gpiod_set_value(gpiod_reset, 0); + } + + return ret; + +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(pwrseq_gen->clks[clk]); + + return ret; +} + +static int pwrseq_generic_get(struct device_node *np, struct pwrseq *pwrseq) +{ + struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq); + enum of_gpio_flags flags; + int reset_gpio, clk, ret = 0; + + for (clk = 0; clk < PWRSEQ_MAX_CLKS; clk++) { + pwrseq_gen->clks[clk] = of_clk_get(np, clk); + if (IS_ERR(pwrseq_gen->clks[clk])) { + ret = PTR_ERR(pwrseq_gen->clks[clk]); + if (ret != -ENOENT) + goto err_put_clks; + pwrseq_gen->clks[clk] = NULL; + break; + } + } + + reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &flags); + if (gpio_is_valid(reset_gpio)) { + unsigned long gpio_flags; + + if (flags & OF_GPIO_ACTIVE_LOW) + gpio_flags = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_LOW; + else + gpio_flags = GPIOF_OUT_INIT_HIGH; + + ret = gpio_request_one(reset_gpio, gpio_flags, + "pwrseq-reset-gpios"); + if (ret) + goto err_put_clks; + + pwrseq_gen->gpiod_reset = gpio_to_desc(reset_gpio); + of_property_read_u32(np, "reset-duration-us", + &pwrseq_gen->duration_us); + } else if (reset_gpio == -ENOENT) { + ; /* no such gpio */ + } else { + ret = reset_gpio; + pr_err("Failed to get reset gpio on %s, err = %d\n", + np->full_name, reset_gpio); + goto err_put_clks; + } + + /* allocate new one for later pwrseq instance request */ + ret = pwrseq_generic_alloc_instance(); + if (ret) + goto err_put_gpio; + + return 0; + +err_put_gpio: + if (pwrseq_gen->gpiod_reset) + gpiod_put(pwrseq_gen->gpiod_reset); +err_put_clks: + while (--clk >= 0) + clk_put(pwrseq_gen->clks[clk]); + return ret; +} + +/** + * pwrseq_generic_alloc_instance - power sequence instance allocation + * + * This function is used to allocate one generic power sequence instance, + * it is called when the system boots up and after one power sequence + * instance is got successfully. + * + * Return zero on success or an error code otherwise. + */ +static int pwrseq_generic_alloc_instance(void) +{ + struct pwrseq_generic *pwrseq_gen; + + pwrseq_gen = kzalloc(sizeof(*pwrseq_gen), GFP_KERNEL); + if (!pwrseq_gen) + return -ENOMEM; + + pwrseq_gen->pwrseq.pwrseq_of_match_table = generic_id_table; + pwrseq_gen->pwrseq.get = pwrseq_generic_get; + pwrseq_gen->pwrseq.on = pwrseq_generic_on; + pwrseq_gen->pwrseq.off = pwrseq_generic_off; + pwrseq_gen->pwrseq.put = pwrseq_generic_put; + pwrseq_gen->pwrseq.suspend = pwrseq_generic_suspend; + pwrseq_gen->pwrseq.resume = pwrseq_generic_resume; + + pwrseq_register(&pwrseq_gen->pwrseq); + return 0; +} + +/* Allocate one pwrseq instance during boots up */ +static int __init pwrseq_generic_register(void) +{ + return pwrseq_generic_alloc_instance(); +} +postcore_initcall(pwrseq_generic_register) diff --git a/drivers/regulator/twl6030-regulator.c b/drivers/regulator/twl6030-regulator.c index 430265c..99f7eb8 100644 --- a/drivers/regulator/twl6030-regulator.c +++ b/drivers/regulator/twl6030-regulator.c @@ -299,6 +299,13 @@ static const struct regulator_ops twl6030fixed_ops = { .get_status = twl6030reg_get_status, }; +static struct regulator_ops twl6030_fixed_resource = { + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, + .get_status = twl6030reg_get_status, +}; + /* * SMPS status and control */ @@ -572,6 +579,19 @@ static const struct twlreg_info TWLSMPS_INFO_##label = { \ }, \ } +#define TWL6030_FIXED_RESOURCE(label, offset, turnon_delay) \ +static struct twlreg_info TWLRES_INFO_##label = { \ + .base = offset, \ + .desc = { \ + .name = #label, \ + .id = TWL6030_REG_##label, \ + .ops = &twl6030_fixed_resource, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .enable_time = turnon_delay, \ + }, \ + } + /* VUSBCP is managed *only* by the USB subchip */ /* 6030 REG with base as PMC Slave Misc : 0x0030 */ /* Turnon-delay and remap configuration values for 6030 are not @@ -601,6 +621,7 @@ TWL6030_FIXED_LDO(VDAC, 0x64, 1800, 0); TWL6030_FIXED_LDO(VUSB, 0x70, 3300, 0); TWL6030_FIXED_LDO(V1V8, 0x16, 1800, 0); TWL6030_FIXED_LDO(V2V1, 0x1c, 2100, 0); +TWL6030_FIXED_RESOURCE(CLK32KG, 0x8C, 0); TWL6032_ADJUSTABLE_SMPS(SMPS3, 0x34); TWL6032_ADJUSTABLE_SMPS(SMPS4, 0x10); TWL6032_ADJUSTABLE_SMPS(VIO, 0x16); @@ -632,6 +653,7 @@ static u8 twl_get_smps_mult(void) #define TWL6030_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6030, label) #define TWL6032_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWL6032, label) #define TWLFIXED_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLFIXED, label) +#define TWLRES_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLRES, label) #define TWLSMPS_OF_MATCH(comp, label) TWL_OF_MATCH(comp, TWLSMPS, label) static const struct of_device_id twl_of_match[] = { @@ -659,6 +681,7 @@ static const struct of_device_id twl_of_match[] = { TWLFIXED_OF_MATCH("ti,twl6030-vusb", VUSB), TWLFIXED_OF_MATCH("ti,twl6030-v1v8", V1V8), TWLFIXED_OF_MATCH("ti,twl6030-v2v1", V2V1), + TWLRES_OF_MATCH("ti,twl6030-clk32kg", CLK32KG), TWLSMPS_OF_MATCH("ti,twl6032-smps3", SMPS3), TWLSMPS_OF_MATCH("ti,twl6032-smps4", SMPS4), TWLSMPS_OF_MATCH("ti,twl6032-vio", VIO), diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 455e99c..4997ada 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -737,9 +737,9 @@ static int spidev_probe(struct spi_device *spi) * compatible string, it is a Linux implementation thing * rather than a description of the hardware. */ - WARN(spi->dev.of_node && - of_device_is_compatible(spi->dev.of_node, "spidev"), - "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node); +// WARN(spi->dev.of_node && +// of_device_is_compatible(spi->dev.of_node, "spidev"), +// "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node); spidev_probe_acpi(spi); diff --git a/drivers/staging/fbtft/fb_ssd1306.c b/drivers/staging/fbtft/fb_ssd1306.c index 6cf9df5..4cd6161 100644 --- a/drivers/staging/fbtft/fb_ssd1306.c +++ b/drivers/staging/fbtft/fb_ssd1306.c @@ -55,6 +55,9 @@ static int init_display(struct fbtft_par *par) write_reg(par, 0x3F); else if (par->info->var.yres == 48) write_reg(par, 0x2F); + else if (par->info->var.yres == 39) + /* https://libstock.mikroe.com/projects/download/1111/2577/1411057038_oled_b_click___e_mikroc_arm.zip */ + write_reg(par, 0x27); else write_reg(par, 0x1F); @@ -76,19 +79,27 @@ static int init_display(struct fbtft_par *par) write_reg(par, 0x01); /* Set Segment Re-map */ - /* column address 127 is mapped to SEG0 */ - write_reg(par, 0xA0 | 0x1); + if (par->info->var.yres == 39) + /* no segment re-map */ + write_reg(par, 0xA0 | 0x0); + else + /* column address 127 is mapped to SEG0 */ + write_reg(par, 0xA0 | 0x1); /* Set COM Output Scan Direction */ - /* remapped mode. Scan from COM[N-1] to COM0 */ - write_reg(par, 0xC8); + if (par->info->var.yres == 39) + /* no columnt re-map mode. Scan from COM0 to COM[N-1] */ + write_reg(par, 0xC0); + else + /* remapped mode. Scan from COM[N-1] to COM0 */ + write_reg(par, 0xC8); /* Set COM Pins Hardware Configuration */ write_reg(par, 0xDA); if (par->info->var.yres == 64) /* A[4]=1b, Alternative COM pin configuration */ write_reg(par, 0x12); - else if (par->info->var.yres == 48) + else if (par->info->var.yres == 48 || par->info->var.yres == 39) /* A[4]=1b, Alternative COM pin configuration */ write_reg(par, 0x12); else @@ -97,12 +108,18 @@ static int init_display(struct fbtft_par *par) /* Set Pre-charge Period */ write_reg(par, 0xD9); - write_reg(par, 0xF1); + if (par->info->var.yres == 39) + write_reg(par, 0x25); + else + write_reg(par, 0xF1); /* Set VCOMH Deselect Level */ write_reg(par, 0xDB); - /* according to the datasheet, this value is out of bounds */ - write_reg(par, 0x40); + if (par->info->var.yres == 39) + write_reg(par, 0x20); + else + /* according to the datasheet, this value is out of bounds */ + write_reg(par, 0x40); /* Entire Display ON */ /* Resume to RAM content display. Output follows RAM content */ @@ -133,6 +150,20 @@ static void set_addr_win_64x48(struct fbtft_par *par) write_reg(par, 0x5); } +static void set_addr_win_96x39(struct fbtft_par *par) +{ + /* Set Page Address */ + write_reg(par, 0xB0); + /* Set Column Address */ + write_reg(par, 0x21); + write_reg(par, 0x00); + write_reg(par, 0x5F); + /* Set Page Address Range */ + write_reg(par, 0x22); + write_reg(par, 0x0); + write_reg(par, 0x4); +} + static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) { /* Set Lower Column Start Address for Page Addressing Mode */ @@ -144,6 +175,8 @@ static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) if (par->info->var.xres == 64 && par->info->var.yres == 48) set_addr_win_64x48(par); + else if (par->info->var.xres == 96 && par->info->var.yres == 39) + set_addr_win_96x39(par); } static int blank(struct fbtft_par *par, bool on) @@ -188,11 +221,19 @@ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) *buf |= BIT(i); buf++; } + if (yres % 8) { + *buf = 0x00; + for (i = 0; i < (yres - (y * 8)); i++) + if (vmem16[(y * 8 + i) * xres + x]) + *buf |= BIT(i); + buf++; + y++; + } } /* Write data */ gpiod_set_value(par->gpio.dc, 1); - ret = par->fbtftops.write(par, par->txbuf.buf, xres * yres / 8); + ret = par->fbtftops.write(par, par->txbuf.buf, xres * (yres / 8 + (yres % 8 != 0))); if (ret < 0) dev_err(par->info->device, "write failed and returned: %d\n", ret); diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index c5f0d93..85977b3 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -27,12 +27,17 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { int len; + struct serdev_device *serdev = to_serdev_device(dev); len = acpi_device_modalias(dev, buf, PAGE_SIZE - 1); if (len != -ENODEV) return len; - return of_device_modalias(dev, buf, PAGE_SIZE); + len = of_device_modalias(dev, buf, PAGE_SIZE); + if (len != -ENODEV) + return len; + + return sprintf(buf, "%s%s\n", SERDEV_MODULE_PREFIX, serdev->modalias); } static DEVICE_ATTR_RO(modalias); @@ -45,14 +50,18 @@ ATTRIBUTE_GROUPS(serdev_device); static int serdev_device_uevent(struct device *dev, struct kobj_uevent_env *env) { int rc; - - /* TODO: platform modalias */ + struct serdev_device *serdev = to_serdev_device(dev); rc = acpi_device_uevent_modalias(dev, env); if (rc != -ENODEV) return rc; - return of_device_uevent_modalias(dev, env); + rc = of_device_uevent_modalias(dev, env); + if (rc != -ENODEV) + return rc; + + return add_uevent_var(env, "MODALIAS=%s%s", SERDEV_MODULE_PREFIX, + serdev->modalias); } static void serdev_device_release(struct device *dev) @@ -83,16 +92,36 @@ static const struct device_type serdev_ctrl_type = { .release = serdev_ctrl_release, }; +static int serdev_match_id(const struct serdev_device_id *id, + const struct serdev_device *sdev) +{ + while (id->name[0]) { + if (!strcmp(sdev->modalias, id->name)) + return 1; + id++; + } + + return 0; +} + static int serdev_device_match(struct device *dev, struct device_driver *drv) { + const struct serdev_device *sdev = to_serdev_device(dev); + const struct serdev_device_driver *sdrv = to_serdev_device_driver(drv); + if (!is_serdev_device(dev)) return 0; - /* TODO: platform matching */ if (acpi_driver_match_device(dev, drv)) return 1; - return of_driver_match_device(dev, drv); + if (of_driver_match_device(dev, drv)) + return 1; + + if (sdrv->id_table) + return serdev_match_id(sdrv->id_table, sdev); + + return strcmp(sdev->modalias, drv->name) == 0; } /** @@ -553,6 +582,17 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) return 0; } +struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node) +{ + struct device *dev = bus_find_device_by_of_node(&serdev_bus_type, node); + + if (!dev) + return NULL; + + return (dev->type == &serdev_ctrl_type) ? to_serdev_controller(dev) : NULL; +} +EXPORT_SYMBOL_GPL(of_find_serdev_controller_by_node); + #ifdef CONFIG_ACPI #define SERDEV_ACPI_MAX_SCAN_DEPTH 32 @@ -750,6 +790,12 @@ int serdev_controller_add(struct serdev_controller *ctrl) pm_runtime_enable(&ctrl->dev); + /* provide option to not delete a serdev controller without devices + * if property is present + */ + if (device_property_present(&ctrl->dev, "force-empty-serdev-controller")) + return 0; + ret_of = of_serdev_register_devices(ctrl); ret_acpi = acpi_serdev_register_devices(ctrl); if (ret_of && ret_acpi) { diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 562087d..e746dab 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -1609,10 +1609,10 @@ static int __init omap8250_console_fixup(void) } add_preferred_console("ttyS", idx, options); - pr_err("WARNING: Your 'console=ttyO%d' has been replaced by 'ttyS%d'\n", + pr_info("WARNING: Your 'console=ttyO%d' has been replaced by 'ttyS%d'\n", idx, idx); - pr_err("This ensures that you still see kernel messages. Please\n"); - pr_err("update your kernel commandline.\n"); + pr_info("This ensures that you still see kernel messages. Please\n"); + pr_info("update your kernel commandline.\n"); return 0; } console_initcall(omap8250_console_fixup); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 26475b4..994037e 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -46,6 +46,7 @@ config USB depends on USB_ARCH_HAS_HCD select GENERIC_ALLOCATOR select USB_COMMON + select POWER_SEQUENCE select NLS # for UTF-8 strings help Universal Serial Bus (USB) is a specification for a serial bus diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5b768b8..aec2392 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -1714,6 +1715,7 @@ static void hub_disconnect(struct usb_interface *intf) hub->error = 0; hub_quiesce(hub, HUB_DISCONNECT); + of_pwrseq_off_list(&hub->pwrseq_on_list); mutex_lock(&usb_port_peer_mutex); /* Avoid races with recursively_mark_NOTATTACHED() */ @@ -1764,11 +1766,41 @@ static bool hub_descriptor_is_sane(struct usb_host_interface *desc) return true; } +#ifdef CONFIG_OF +static int hub_of_pwrseq_on(struct usb_hub *hub) +{ + struct device *parent; + struct usb_device *hdev = hub->hdev; + struct device_node *np; + int ret; + + if (hdev->parent) + parent = &hdev->dev; + else + parent = bus_to_hcd(hdev->bus)->self.sysdev; + + for_each_child_of_node(parent->of_node, np) { + ret = of_pwrseq_on_list(np, &hub->pwrseq_on_list); + /* Maybe no power sequence library is chosen */ + if (ret && ret != -ENOENT) + return ret; + } + + return 0; +} +#else +static int hub_of_pwrseq_on(struct usb_hub *hub) +{ + return 0; +} +#endif + static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_host_interface *desc; struct usb_device *hdev; struct usb_hub *hub; + int ret = -ENODEV; desc = intf->cur_altsetting; hdev = interface_to_usbdev(intf); @@ -1859,6 +1891,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) INIT_DELAYED_WORK(&hub->leds, led_work); INIT_DELAYED_WORK(&hub->init_work, NULL); INIT_WORK(&hub->events, hub_event); + INIT_LIST_HEAD(&hub->pwrseq_on_list); spin_lock_init(&hub->irq_urb_lock); timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0); usb_get_intf(intf); @@ -1879,11 +1912,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) usb_autopm_get_interface_no_resume(intf); } - if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) - return 0; + if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) { + ret = hub_of_pwrseq_on(hub); + if (!ret) + return 0; + } hub_disconnect(intf); - return -ENODEV; + return ret; } static int @@ -3745,7 +3781,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) /* stop hub_wq and related activity */ hub_quiesce(hub, HUB_SUSPEND); - return 0; + return pwrseq_suspend_list(&hub->pwrseq_on_list); } /* Report wakeup requests from the ports of a resuming root hub */ @@ -3785,8 +3821,13 @@ static void report_wakeup_requests(struct usb_hub *hub) static int hub_resume(struct usb_interface *intf) { struct usb_hub *hub = usb_get_intfdata(intf); + int ret; dev_dbg(&intf->dev, "%s\n", __func__); + ret = pwrseq_resume_list(&hub->pwrseq_on_list); + if (ret) + return ret; + hub_activate(hub, HUB_RESUME); /* diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 73f4482..dc4fd59 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -73,6 +73,7 @@ struct usb_hub { spinlock_t irq_urb_lock; struct timer_list irq_urb_retry; struct usb_port **ports; + struct list_head pwrseq_on_list; /* powered pwrseq node list */ }; /** diff --git a/include/linux/greybus/greybus_manifest.h b/include/linux/greybus/greybus_manifest.h index 6e62fe4..50d5a5f 100644 --- a/include/linux/greybus/greybus_manifest.h +++ b/include/linux/greybus/greybus_manifest.h @@ -23,6 +23,9 @@ enum greybus_descriptor_type { GREYBUS_TYPE_STRING = 0x02, GREYBUS_TYPE_BUNDLE = 0x03, GREYBUS_TYPE_CPORT = 0x04, + GREYBUS_TYPE_MIKROBUS = 0x05, + GREYBUS_TYPE_PROPERTY = 0x06, + GREYBUS_TYPE_DEVICE = 0x07, }; enum greybus_protocol { @@ -151,6 +154,49 @@ struct greybus_descriptor_cport { __u8 protocol_id; /* enum greybus_protocol */ } __packed; +/* + * A mikrobus descriptor is used to describe the details + * about the bus ocnfiguration for the add-on board + * connected to the mikrobus port. + */ +struct greybus_descriptor_mikrobus { + __u8 pin_state[12]; +} __packed; + +/* + * A property descriptor is used to pass named properties + * to device drivers through the unified device properties + * interface under linux/property.h + */ +struct greybus_descriptor_property { + __u8 length; + __u8 id; + __u8 propname_stringid; + __u8 type; + __u8 value[0]; +} __packed; + +/* + * A device descriptor is used to describe the + * details required by a add-on board device + * driver. + */ +struct greybus_descriptor_device { + __u8 id; + __u8 driver_stringid; + __u8 protocol; + __u8 reg; + __le32 max_speed_hz; + __u8 irq; + __u8 irq_type; + __u8 mode; + __u8 prop_link; + __u8 gpio_link; + __u8 reg_link; + __u8 clock_link; + __u8 pad[1]; +} __packed; + struct greybus_descriptor_header { __le16 size; __u8 type; /* enum greybus_descriptor_type */ @@ -164,6 +210,9 @@ struct greybus_descriptor { struct greybus_descriptor_interface interface; struct greybus_descriptor_bundle bundle; struct greybus_descriptor_cport cport; + struct greybus_descriptor_mikrobus mikrobus; + struct greybus_descriptor_property property; + struct greybus_descriptor_device device; }; } __packed; diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 5b08a47..6563539 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -486,6 +486,16 @@ struct i3c_device_id { const void *data; }; +/* serdev */ + +#define SERDEV_NAME_SIZE 32 +#define SERDEV_MODULE_PREFIX "serdev:" + +struct serdev_device_id { + char name[SERDEV_NAME_SIZE]; + kernel_ulong_t driver_data; +}; + /* spi */ #define SPI_NAME_SIZE 32 diff --git b/include/linux/power/pwrseq.h b/include/linux/power/pwrseq.h new file mode 100644 index 0000000..cbc344c --- /dev/null +++ b/include/linux/power/pwrseq.h @@ -0,0 +1,81 @@ +#ifndef __LINUX_PWRSEQ_H +#define __LINUX_PWRSEQ_H + +#include + +#define PWRSEQ_MAX_CLKS 3 + +/** + * struct pwrseq - the power sequence structure + * @pwrseq_of_match_table: the OF device id table this pwrseq library supports + * @node: the list pointer to be added to pwrseq list + * @get: the API is used to get pwrseq instance from the device node + * @on: do power on for this pwrseq instance + * @off: do power off for this pwrseq instance + * @put: release the resources on this pwrseq instance + * @suspend: do suspend operation on this pwrseq instance + * @resume: do resume operation on this pwrseq instance + * @used: this pwrseq instance is used by device + */ +struct pwrseq { + const struct of_device_id *pwrseq_of_match_table; + struct list_head node; + int (*get)(struct device_node *np, struct pwrseq *p); + int (*on)(struct pwrseq *p); + void (*off)(struct pwrseq *p); + void (*put)(struct pwrseq *p); + int (*suspend)(struct pwrseq *p); + int (*resume)(struct pwrseq *p); + bool used; + bool suspended; +}; + +/* used for power sequence instance list in one driver */ +struct pwrseq_list_per_dev { + struct pwrseq *pwrseq; + struct list_head list; +}; + +#if IS_ENABLED(CONFIG_POWER_SEQUENCE) +void pwrseq_register(struct pwrseq *pwrseq); +void pwrseq_unregister(struct pwrseq *pwrseq); +struct pwrseq *of_pwrseq_on(struct device_node *np); +void of_pwrseq_off(struct pwrseq *pwrseq); +int of_pwrseq_on_list(struct device_node *np, struct list_head *head); +void of_pwrseq_off_list(struct list_head *head); +int pwrseq_suspend(struct pwrseq *p); +int pwrseq_resume(struct pwrseq *p); +int pwrseq_suspend_list(struct list_head *head); +int pwrseq_resume_list(struct list_head *head); +#else +static inline void pwrseq_register(struct pwrseq *pwrseq) {} +static inline void pwrseq_unregister(struct pwrseq *pwrseq) {} +static inline struct pwrseq *of_pwrseq_on(struct device_node *np) +{ + return NULL; +} +static void of_pwrseq_off(struct pwrseq *pwrseq) {} +static int of_pwrseq_on_list(struct device_node *np, struct list_head *head) +{ + return 0; +} +static void of_pwrseq_off_list(struct list_head *head) {} +static int pwrseq_suspend(struct pwrseq *p) +{ + return 0; +} +static int pwrseq_resume(struct pwrseq *p) +{ + return 0; +} +static int pwrseq_suspend_list(struct list_head *head) +{ + return 0; +} +static int pwrseq_resume_list(struct list_head *head) +{ + return 0; +} +#endif /* CONFIG_POWER_SEQUENCE */ + +#endif /* __LINUX_PWRSEQ_H */ diff --git a/include/linux/serdev.h b/include/linux/serdev.h index 9f14f9c..2e1eb4d 100644 --- a/include/linux/serdev.h +++ b/include/linux/serdev.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -45,6 +46,7 @@ struct serdev_device { const struct serdev_device_ops *ops; struct completion write_comp; struct mutex write_lock; + char modalias[SERDEV_NAME_SIZE]; }; static inline struct serdev_device *to_serdev_device(struct device *d) @@ -63,6 +65,7 @@ struct serdev_device_driver { struct device_driver driver; int (*probe)(struct serdev_device *); void (*remove)(struct serdev_device *); + const struct serdev_device_id *id_table; }; static inline struct serdev_device_driver *to_serdev_device_driver(struct device_driver *d) @@ -112,6 +115,8 @@ static inline struct serdev_controller *to_serdev_controller(struct device *d) return container_of(d, struct serdev_controller, dev); } +struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node); + static inline void *serdev_device_get_drvdata(const struct serdev_device *serdev) { return dev_get_drvdata(&serdev->dev); diff --git b/include/uapi/linux/can/isotp.h b/include/uapi/linux/can/isotp.h new file mode 100644 index 0000000..e6f312e --- /dev/null +++ b/include/uapi/linux/can/isotp.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* + * socketcan/can/isotp.h + * + * Definitions for isotp CAN sockets + * + * Author: Oliver Hartkopp + * Copyright (c) 2008 Volkswagen Group Electronic Research + * All rights reserved. + * + * Send feedback to + * + */ + +#ifndef CAN_ISOTP_H +#define CAN_ISOTP_H + +#include + +#define SOL_CAN_ISOTP (SOL_CAN_BASE + CAN_ISOTP) + +/* for socket options affecting the socket (not the global system) */ + +#define CAN_ISOTP_OPTS 1 /* pass struct can_isotp_options */ + +#define CAN_ISOTP_RECV_FC 2 /* pass struct can_isotp_fc_options */ + +/* sockopts to force stmin timer values for protocol regression tests */ + +#define CAN_ISOTP_TX_STMIN 3 /* pass __u32 value in nano secs */ + /* use this time instead of value */ + /* provided in FC from the receiver */ + +#define CAN_ISOTP_RX_STMIN 4 /* pass __u32 value in nano secs */ + /* ignore received CF frames which */ + /* timestamps differ less than val */ + +#define CAN_ISOTP_LL_OPTS 5 /* pass struct can_isotp_ll_options */ + +struct can_isotp_options { + + __u32 flags; /* set flags for isotp behaviour. */ + /* __u32 value : flags see below */ + + __u32 frame_txtime; /* frame transmission time (N_As/N_Ar) */ + /* __u32 value : time in nano secs */ + + __u8 ext_address; /* set address for extended addressing */ + /* __u8 value : extended address */ + + __u8 txpad_content; /* set content of padding byte (tx) */ + /* __u8 value : content on tx path */ + + __u8 rxpad_content; /* set content of padding byte (rx) */ + /* __u8 value : content on rx path */ + + __u8 rx_ext_address; /* set address for extended addressing */ + /* __u8 value : extended address (rx) */ +}; + +struct can_isotp_fc_options { + + __u8 bs; /* blocksize provided in FC frame */ + /* __u8 value : blocksize. 0 = off */ + + __u8 stmin; /* separation time provided in FC frame */ + /* __u8 value : */ + /* 0x00 - 0x7F : 0 - 127 ms */ + /* 0x80 - 0xF0 : reserved */ + /* 0xF1 - 0xF9 : 100 us - 900 us */ + /* 0xFA - 0xFF : reserved */ + + __u8 wftmax; /* max. number of wait frame transmiss. */ + /* __u8 value : 0 = omit FC N_PDU WT */ +}; + +struct can_isotp_ll_options { + + __u8 mtu; /* generated & accepted CAN frame type */ + /* __u8 value : */ + /* CAN_MTU (16) -> standard CAN 2.0 */ + /* CANFD_MTU (72) -> CAN FD frame */ + + __u8 tx_dl; /* tx link layer data length in bytes */ + /* (configured maximum payload length) */ + /* __u8 value : 8,12,16,20,24,32,48,64 */ + /* => rx path supports all LL_DL values */ + + __u8 tx_flags; /* set into struct canfd_frame.flags */ + /* at frame creation: e.g. CANFD_BRS */ + /* Obsolete when the BRS flag is fixed */ + /* by the CAN netdriver configuration */ +}; + +/* flags for isotp behaviour */ + +#define CAN_ISOTP_LISTEN_MODE 0x001 /* listen only (do not send FC) */ +#define CAN_ISOTP_EXTEND_ADDR 0x002 /* enable extended addressing */ +#define CAN_ISOTP_TX_PADDING 0x004 /* enable CAN frame padding tx path */ +#define CAN_ISOTP_RX_PADDING 0x008 /* enable CAN frame padding rx path */ +#define CAN_ISOTP_CHK_PAD_LEN 0x010 /* check received CAN frame padding */ +#define CAN_ISOTP_CHK_PAD_DATA 0x020 /* check received CAN frame padding */ +#define CAN_ISOTP_HALF_DUPLEX 0x040 /* half duplex error state handling */ +#define CAN_ISOTP_FORCE_TXSTMIN 0x080 /* ignore stmin from received FC */ +#define CAN_ISOTP_FORCE_RXSTMIN 0x100 /* ignore CFs depending on rx stmin */ +#define CAN_ISOTP_RX_EXT_ADDR 0x200 /* different rx extended addressing */ +#define CAN_ISOTP_WAIT_TX_DONE 0x400 /* wait for tx completion */ + + +/* default values */ + +#define CAN_ISOTP_DEFAULT_FLAGS 0 +#define CAN_ISOTP_DEFAULT_EXT_ADDRESS 0x00 +#define CAN_ISOTP_DEFAULT_PAD_CONTENT 0xCC /* prevent bit-stuffing */ +#define CAN_ISOTP_DEFAULT_FRAME_TXTIME 0 +#define CAN_ISOTP_DEFAULT_RECV_BS 0 +#define CAN_ISOTP_DEFAULT_RECV_STMIN 0x00 +#define CAN_ISOTP_DEFAULT_RECV_WFTMAX 0 + +#define CAN_ISOTP_DEFAULT_LL_MTU CAN_MTU +#define CAN_ISOTP_DEFAULT_LL_TX_DL CAN_MAX_DLEN +#define CAN_ISOTP_DEFAULT_LL_TX_FLAGS 0 + +/* + * Remark on CAN_ISOTP_DEFAULT_RECV_* values: + * + * We can strongly assume, that the Linux Kernel implementation of + * CAN_ISOTP is capable to run with BS=0, STmin=0 and WFTmax=0. + * But as we like to be able to behave as a commonly available ECU, + * these default settings can be changed via sockopts. + * For that reason the STmin value is intentionally _not_ checked for + * consistency and copied directly into the flow control (FC) frame. + * + */ + +#endif diff --git a/net/can/Kconfig b/net/can/Kconfig index 25436a7..80a64aa 100644 --- a/net/can/Kconfig +++ b/net/can/Kconfig @@ -55,6 +55,16 @@ config CAN_GW source "net/can/j1939/Kconfig" +config CAN_ISOTP + tristate "ISO 15765-2:2016 CAN transport protocol" + default y + help + ISO 15765-2 CAN transport protocol for protocol family CAN + + This implementation is already widely used in automotive use-cases, e.g. + for UDS based OBD diagnosis. Although some small adaptions may be applied + to make it ready for Linux Mainline. Feedback is welcome. + source "drivers/net/can/Kconfig" endif diff --git a/net/can/Makefile b/net/can/Makefile index 08bd217..cfa1024 100644 --- a/net/can/Makefile +++ b/net/can/Makefile @@ -16,4 +16,7 @@ can-bcm-y := bcm.o obj-$(CONFIG_CAN_GW) += can-gw.o can-gw-y := gw.o +obj-$(CONFIG_CAN_ISOTP) += can-isotp.o +can-isotp-y := isotp.o + obj-$(CONFIG_CAN_J1939) += j1939/ diff --git b/net/can/isotp.c b/net/can/isotp.c new file mode 100644 index 0000000..eb23843 --- /dev/null +++ b/net/can/isotp.c @@ -0,0 +1,1535 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +/* isotp.c - ISO 15765-2 CAN transport protocol for protocol family CAN + * + * WARNING: This is ALPHA code for discussions and first tests that should + * not be used in production environments. + * + * In the discussion the Socket-API to the userspace or the ISO-TP socket + * options or the return values we may change! Current behaviour: + * + * - no ISO-TP specific return values are provided to the userspace + * - when a transfer (tx) is on the run the next write() blocks until it's done + * - no support for sending wait frames to the data source in the rx path + * + * Copyright (c) 2020 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0) +#include +#define CAN_SKBRES sizeof(struct can_skb_priv) +#else +#define CAN_SKBRES 0 +#endif +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +#include +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) +#include "compat.h" +#endif + +#define CAN_ISOTP_VERSION "20201014 - out-of-tree" + +MODULE_DESCRIPTION("PF_CAN isotp 15765-2:2016 protocol"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Oliver Hartkopp "); +MODULE_ALIAS("can-proto-6"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) +#error This modules needs hrtimers (available since Kernel 2.6.22) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0) +#error No need to compile this out-of-tree driver! ISO-TP is part of Linux Mainline kernel since Linux 5.10. +#endif + +#define DBG(fmt, args...) (printk( KERN_DEBUG "can-isotp: %s: " fmt, \ + __func__, ##args)) +#undef DBG +#define DBG(fmt, args...) + +#define SINGLE_MASK(id) (((id) & CAN_EFF_FLAG) ? \ + (CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG) : \ + (CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG)) + +/* ISO 15765-2:2016 supports more than 4095 byte per ISO PDU as the FF_DL can + * take full 32 bit values (4 Gbyte). We would need some good concept to handle + * this between user space and kernel space. For now increase the static buffer + * to something about 8 kbyte to be able to test this new functionality. + */ +#define MAX_MSG_LENGTH 8200 + +/* N_PCI type values in bits 7-4 of N_PCI bytes */ +#define N_PCI_SF 0x00 /* single frame */ +#define N_PCI_FF 0x10 /* first frame */ +#define N_PCI_CF 0x20 /* consecutive frame */ +#define N_PCI_FC 0x30 /* flow control */ + +#define N_PCI_SZ 1 /* size of the PCI byte #1 */ +#define SF_PCI_SZ4 1 /* size of SingleFrame PCI including 4 bit SF_DL */ +#define SF_PCI_SZ8 2 /* size of SingleFrame PCI including 8 bit SF_DL */ +#define FF_PCI_SZ12 2 /* size of FirstFrame PCI including 12 bit FF_DL */ +#define FF_PCI_SZ32 6 /* size of FirstFrame PCI including 32 bit FF_DL */ +#define FC_CONTENT_SZ 3 /* flow control content size in byte (FS/BS/STmin) */ + +#define ISOTP_CHECK_PADDING (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA) + +/* Flow Status given in FC frame */ +#define ISOTP_FC_CTS 0 /* clear to send */ +#define ISOTP_FC_WT 1 /* wait */ +#define ISOTP_FC_OVFLW 2 /* overflow */ + +enum { + ISOTP_IDLE = 0, + ISOTP_WAIT_FIRST_FC, + ISOTP_WAIT_FC, + ISOTP_WAIT_DATA, + ISOTP_SENDING +}; + +struct tpcon { + int idx; + int len; + u8 state; + u8 bs; + u8 sn; + u8 ll_dl; + u8 buf[MAX_MSG_LENGTH + 1]; +}; + +struct isotp_sock { + struct sock sk; + int bound; + int ifindex; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; + struct hrtimer rxtimer, txtimer; + struct tasklet_struct txtsklet; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; + u32 force_tx_stmin; + u32 force_rx_stmin; + struct tpcon rx, tx; + struct notifier_block notifier; + wait_queue_head_t wait; +}; + +static inline struct isotp_sock *isotp_sk(const struct sock *sk) +{ + return (struct isotp_sock *)sk; +} + +static enum hrtimer_restart isotp_rx_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + rxtimer); + if (so->rx.state == ISOTP_WAIT_DATA) { + DBG("we did not get new data frames in time.\n"); + + /* reset tx state */ + so->rx.state = ISOTP_IDLE; + } + + return HRTIMER_NORESTART; +} + +static void isotp_skb_reserve(struct sk_buff *skb, struct net_device *dev) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,0) + can_skb_reserve(skb); + can_skb_prv(skb)->ifindex = dev->ifindex; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,5) + can_skb_prv(skb)->skbcnt = 0; +#endif +} + +static void isotp_skb_destructor(struct sk_buff *skb) +{ + sock_put(skb->sk); +} + +static inline void isotp_skb_set_owner(struct sk_buff *skb, struct sock *sk) +{ + if (sk) { + sock_hold(sk); + skb->destructor = isotp_skb_destructor; + skb->sk = sk; + } +} + +static int isotp_send_fc(struct sock *sk, int ae, u8 flowstatus) +{ + struct net_device *dev; + struct sk_buff *nskb; + struct canfd_frame *ncf; + struct isotp_sock *so = isotp_sk(sk); + + nskb = alloc_skb(so->ll.mtu + CAN_SKBRES, gfp_any()); + if (!nskb) + return 1; + + dev = dev_get_by_index(sock_net(sk), so->ifindex); + if (!dev) { + kfree_skb(nskb); + return 1; + } + isotp_skb_reserve(nskb, dev); + nskb->dev = dev; + isotp_skb_set_owner(nskb, sk); + ncf = (struct canfd_frame *) nskb->data; + skb_put(nskb, so->ll.mtu); + + /* create & send flow control reply */ + ncf->can_id = so->txid; + + if (so->opt.flags & CAN_ISOTP_TX_PADDING) { + memset(ncf->data, so->opt.txpad_content, CAN_MAX_DLEN); + ncf->len = CAN_MAX_DLEN; + } else { + ncf->len = ae + FC_CONTENT_SZ; + } + + ncf->data[ae] = N_PCI_FC | flowstatus; + ncf->data[ae + 1] = so->rxfc.bs; + ncf->data[ae + 2] = so->rxfc.stmin; + + if (ae) + ncf->data[0] = so->opt.ext_address; + + if (so->ll.mtu == CANFD_MTU) + ncf->flags = so->ll.tx_flags; + + can_send(nskb, 1); + dev_put(dev); + + /* reset blocksize counter */ + so->rx.bs = 0; + + /* reset last CF frame rx timestamp for rx stmin enforcement */ + so->lastrxcf_tstamp = ktime_set(0, 0); + + /* start rx timeout watchdog */ + hrtimer_start(&so->rxtimer, ktime_set(1, 0), HRTIMER_MODE_REL); + return 0; +} + +static void isotp_rcv_skb(struct sk_buff *skb, struct sock *sk) +{ + struct sockaddr_can *addr = (struct sockaddr_can *)skb->cb; + + BUILD_BUG_ON(sizeof(skb->cb) < sizeof(struct sockaddr_can)); + + memset(addr, 0, sizeof(*addr)); + addr->can_family = AF_CAN; + addr->can_ifindex = skb->dev->ifindex; + + if (sock_queue_rcv_skb(sk, skb) < 0) + kfree_skb(skb); +} + +static u8 padlen(u8 datalen) +{ + const u8 plen[] = {8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0 - 8 */ + 12, 12, 12, 12, /* 9 - 12 */ + 16, 16, 16, 16, /* 13 - 16 */ + 20, 20, 20, 20, /* 17 - 20 */ + 24, 24, 24, 24, /* 21 - 24 */ + 32, 32, 32, 32, 32, 32, 32, 32, /* 25 - 32 */ + 48, 48, 48, 48, 48, 48, 48, 48, /* 33 - 40 */ + 48, 48, 48, 48, 48, 48, 48, 48}; /* 41 - 48 */ + + if (datalen > 48) + return 64; + + return plen[datalen]; +} + +/* check for length optimization and return 1/true when the check fails */ +static int check_optimized(struct canfd_frame *cf, int start_index) +{ + /* for CAN_DL <= 8 the start_index is equal to the CAN_DL as the + * padding would start at this point. E.g. if the padding would + * start at cf.data[7] cf->len has to be 7 to be optimal. + * Note: The data[] index starts with zero. + */ + if (cf->len <= CAN_MAX_DLEN) + return (cf->len != start_index); + + /* This relation is also valid in the non-linear DLC range, where + * we need to take care of the minimal next possible CAN_DL. + * The correct check would be (padlen(cf->len) != padlen(start_index)). + * But as cf->len can only take discrete values from 12, .., 64 at this + * point the padlen(cf->len) is always equal to cf->len. + */ + return (cf->len != padlen(start_index)); +} + +/* check padding and return 1/true when the check fails */ +static int check_pad(struct isotp_sock *so, struct canfd_frame *cf, + int start_index, u8 content) +{ + int i; + + /* no RX_PADDING value => check length of optimized frame length */ + if (!(so->opt.flags & CAN_ISOTP_RX_PADDING)) { + if (so->opt.flags & CAN_ISOTP_CHK_PAD_LEN) + return check_optimized(cf, start_index); + + /* no valid test against empty value => ignore frame */ + return 1; + } + + /* check datalength of correctly padded CAN frame */ + if ((so->opt.flags & CAN_ISOTP_CHK_PAD_LEN) && + cf->len != padlen(cf->len)) + return 1; + + /* check padding content */ + if (so->opt.flags & CAN_ISOTP_CHK_PAD_DATA) { + for (i = start_index; i < cf->len; i++) + if (cf->data[i] != content) + return 1; + } + return 0; +} + +static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) +{ + if (so->tx.state != ISOTP_WAIT_FC && + so->tx.state != ISOTP_WAIT_FIRST_FC) + return 0; + + hrtimer_cancel(&so->txtimer); + + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + return 1; + } + + /* get communication parameters only from the first FC frame */ + if (so->tx.state == ISOTP_WAIT_FIRST_FC) { + so->txfc.bs = cf->data[ae + 1]; + so->txfc.stmin = cf->data[ae + 2]; + + /* fix wrong STmin values according spec */ + if (so->txfc.stmin > 0x7F && + (so->txfc.stmin < 0xF1 || so->txfc.stmin > 0xF9)) + so->txfc.stmin = 0x7F; + + so->tx_gap = ktime_set(0, 0); + /* add transmission time for CAN frame N_As */ + so->tx_gap = ktime_add_ns(so->tx_gap, so->opt.frame_txtime); + /* add waiting time for consecutive frames N_Cs */ + if (so->opt.flags & CAN_ISOTP_FORCE_TXSTMIN) + so->tx_gap = ktime_add_ns(so->tx_gap, + so->force_tx_stmin); + else if (so->txfc.stmin < 0x80) + so->tx_gap = ktime_add_ns(so->tx_gap, + so->txfc.stmin * 1000000); + else + so->tx_gap = ktime_add_ns(so->tx_gap, + (so->txfc.stmin - 0xF0) + * 100000); + so->tx.state = ISOTP_WAIT_FC; + } + + DBG("FC frame: FS %d, BS %d, STmin 0x%02X, tx_gap %lld\n", + cf->data[ae] & 0x0F & 0x0F, so->txfc.bs, so->txfc.stmin, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + (long long)so->tx_gap); +#else + (long long)so->tx_gap.tv64); +#endif + + switch (cf->data[ae] & 0x0F) { + case ISOTP_FC_CTS: + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + DBG("starting txtimer for sending\n"); + /* start cyclic timer for sending CF frame */ + hrtimer_start(&so->txtimer, so->tx_gap, + HRTIMER_MODE_REL); + break; + + case ISOTP_FC_WT: + DBG("starting waiting for next FC\n"); + /* start timer to wait for next FC frame */ + hrtimer_start(&so->txtimer, ktime_set(1,0), + HRTIMER_MODE_REL); + break; + + case ISOTP_FC_OVFLW: + DBG("overflow in receiver side\n"); + + default: + /* stop this tx job */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + } + return 0; +} + +static int isotp_rcv_sf(struct sock *sk, struct canfd_frame *cf, int pcilen, + struct sk_buff *skb, int len) +{ + struct isotp_sock *so = isotp_sk(sk); + struct sk_buff *nskb; + + hrtimer_cancel(&so->rxtimer); + so->rx.state = ISOTP_IDLE; + + if (!len || len > cf->len - pcilen) + return 1; + + if ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, pcilen + len, so->opt.rxpad_content)) { + return 1; + } + + nskb = alloc_skb(len, gfp_any()); + if (!nskb) + return 1; + + memcpy(skb_put(nskb, len), &cf->data[pcilen], len); + + nskb->tstamp = skb->tstamp; + nskb->dev = skb->dev; + isotp_rcv_skb(nskb, sk); + return 0; +} + +static int isotp_rcv_ff(struct sock *sk, struct canfd_frame *cf, int ae) +{ + struct isotp_sock *so = isotp_sk(sk); + int i; + int off; + int ff_pci_sz; + + hrtimer_cancel(&so->rxtimer); + so->rx.state = ISOTP_IDLE; + + /* get the used sender LL_DL from the (first) CAN frame data length */ + so->rx.ll_dl = padlen(cf->len); + + /* the first frame has to use the entire frame up to LL_DL length */ + if (cf->len != so->rx.ll_dl) + return 1; + + /* get the FF_DL */ + so->rx.len = (cf->data[ae] & 0x0F) << 8; + so->rx.len += cf->data[ae + 1]; + + /* Check for FF_DL escape sequence supporting 32 bit PDU length */ + if (so->rx.len) { + ff_pci_sz = FF_PCI_SZ12; + } else { + /* FF_DL = 0 => get real length from next 4 bytes */ + so->rx.len = cf->data[ae + 2] << 24; + so->rx.len += cf->data[ae + 3] << 16; + so->rx.len += cf->data[ae + 4] << 8; + so->rx.len += cf->data[ae + 5]; + ff_pci_sz = FF_PCI_SZ32; + } + + /* take care of a potential SF_DL ESC offset for TX_DL > 8 */ + off = (so->rx.ll_dl > CAN_MAX_DLEN) ? 1 : 0; + + if (so->rx.len + ae + off + ff_pci_sz < so->rx.ll_dl) + return 1; + + if (so->rx.len > MAX_MSG_LENGTH) { + /* send FC frame with overflow status */ + isotp_send_fc(sk, ae, ISOTP_FC_OVFLW); + return 1; + } + + /* copy the first received data bytes */ + so->rx.idx = 0; + for (i = ae + ff_pci_sz; i < so->rx.ll_dl; i++) + so->rx.buf[so->rx.idx++] = cf->data[i]; + + /* initial setup for this pdu reception */ + so->rx.sn = 1; + so->rx.state = ISOTP_WAIT_DATA; + + /* no creation of flow control frames */ + if (so->opt.flags & CAN_ISOTP_LISTEN_MODE) + return 0; + + /* send our first FC frame */ + isotp_send_fc(sk, ae, ISOTP_FC_CTS); + return 0; +} + +static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, + struct sk_buff *skb) +{ + struct isotp_sock *so = isotp_sk(sk); + struct sk_buff *nskb; + int i; + + if (so->rx.state != ISOTP_WAIT_DATA) + return 0; + + /* drop if timestamp gap is less than force_rx_stmin nano secs */ + if (so->opt.flags & CAN_ISOTP_FORCE_RXSTMIN) { + if (ktime_to_ns(ktime_sub(skb->tstamp, so->lastrxcf_tstamp)) < + so->force_rx_stmin) + return 0; + + so->lastrxcf_tstamp = skb->tstamp; + } + + hrtimer_cancel(&so->rxtimer); + + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; + + /* CFs have usually the LL_DL length */ + if (cf->len < so->rx.ll_dl) { + /* this is only allowed for the last CF */ + if (so->rx.len - so->rx.idx > so->rx.ll_dl - ae - N_PCI_SZ) + return 1; + } + + if ((cf->data[ae] & 0x0F) != so->rx.sn) { + DBG("wrong sn %d. expected %d.\n", + cf->data[ae] & 0x0F, so->rx.sn); + /* some error reporting? */ + so->rx.state = ISOTP_IDLE; + return 1; + } + so->rx.sn++; + so->rx.sn %= 16; + + for (i = ae + N_PCI_SZ; i < cf->len; i++) { + so->rx.buf[so->rx.idx++] = cf->data[i]; + if (so->rx.idx >= so->rx.len) + break; + } + + if (so->rx.idx >= so->rx.len) { + /* we are done */ + so->rx.state = ISOTP_IDLE; + + if ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, i + 1, so->opt.rxpad_content)) { + return 1; + } + + nskb = alloc_skb(so->rx.len, gfp_any()); + if (!nskb) + return 1; + + memcpy(skb_put(nskb, so->rx.len), so->rx.buf, + so->rx.len); + + nskb->tstamp = skb->tstamp; + nskb->dev = skb->dev; + isotp_rcv_skb(nskb, sk); + return 0; + } + + /* perform blocksize handling, if enabled */ + if (!so->rxfc.bs || ++so->rx.bs < so->rxfc.bs) { + /* start rx timeout watchdog */ + hrtimer_start(&so->rxtimer, ktime_set(1, 0), + HRTIMER_MODE_REL); + return 0; + } + + /* no creation of flow control frames */ + if (so->opt.flags & CAN_ISOTP_LISTEN_MODE) + return 0; + + /* we reached the specified blocksize so->rxfc.bs */ + isotp_send_fc(sk, ae, ISOTP_FC_CTS); + return 0; +} + +static void isotp_rcv(struct sk_buff *skb, void *data) +{ + struct sock *sk = (struct sock *)data; + struct isotp_sock *so = isotp_sk(sk); + struct canfd_frame *cf; + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + u8 n_pci_type, sf_dl; + + /* Strictly receive only frames with the configured MTU size + * => clear separation of CAN2.0 / CAN FD transport channels + */ + if (skb->len != so->ll.mtu) + return; + + cf = (struct canfd_frame *)skb->data; + + /* if enabled: check reception of my configured extended address */ + if (ae && cf->data[0] != so->opt.rx_ext_address) + return; + + n_pci_type = cf->data[ae] & 0xF0; + + if (so->opt.flags & CAN_ISOTP_HALF_DUPLEX) { + /* check rx/tx path half duplex expectations */ + if ((so->tx.state != ISOTP_IDLE && n_pci_type != N_PCI_FC) || + (so->rx.state != ISOTP_IDLE && n_pci_type == N_PCI_FC)) + return; + } + + switch (n_pci_type) { + case N_PCI_FC: + /* tx path: flow control frame containing the FC parameters */ + isotp_rcv_fc(so, cf, ae); + break; + + case N_PCI_SF: + /* rx path: single frame + * + * As we do not have a rx.ll_dl configuration, we can only test + * if the CAN frames payload length matches the LL_DL == 8 + * requirements - no matter if it's CAN 2.0 or CAN FD + */ + + /* get the SF_DL from the N_PCI byte */ + sf_dl = cf->data[ae] & 0x0F; + + if (cf->len <= CAN_MAX_DLEN) { + isotp_rcv_sf(sk, cf, SF_PCI_SZ4 + ae, skb, sf_dl); + } else { + if (skb->len == CANFD_MTU) { + /* We have a CAN FD frame and CAN_DL is greater than 8: + * Only frames with the SF_DL == 0 ESC value are valid. + * + * If so take care of the increased SF PCI size + * (SF_PCI_SZ8) to point to the message content behind + * the extended SF PCI info and get the real SF_DL + * length value from the formerly first data byte. + */ + if (sf_dl == 0) + isotp_rcv_sf(sk, cf, SF_PCI_SZ8 + ae, skb, + cf->data[SF_PCI_SZ4 + ae]); + } + } + break; + + case N_PCI_FF: + /* rx path: first frame */ + isotp_rcv_ff(sk, cf, ae); + break; + + case N_PCI_CF: + /* rx path: consecutive frame */ + isotp_rcv_cf(sk, cf, ae, skb); + break; + } +} + +static void isotp_fill_dataframe(struct canfd_frame *cf, struct isotp_sock *so, + int ae, int off) +{ + int pcilen = N_PCI_SZ + ae + off; + int space = so->tx.ll_dl - pcilen; + int num = min_t(int, so->tx.len - so->tx.idx, space); + int i; + + cf->can_id = so->txid; + cf->len = num + pcilen; + + if (num < space) { + if (so->opt.flags & CAN_ISOTP_TX_PADDING) { + /* user requested padding */ + cf->len = padlen(cf->len); + memset(cf->data, so->opt.txpad_content, cf->len); + } else if (cf->len > CAN_MAX_DLEN) { + /* mandatory padding for CAN FD frames */ + cf->len = padlen(cf->len); + memset(cf->data, CAN_ISOTP_DEFAULT_PAD_CONTENT, + cf->len); + } + } + + for (i = 0; i < num; i++) + cf->data[pcilen + i] = so->tx.buf[so->tx.idx++]; + + if (ae) + cf->data[0] = so->opt.ext_address; +} + +static void isotp_create_fframe(struct canfd_frame *cf, struct isotp_sock *so, + int ae) +{ + int i; + int ff_pci_sz; + + cf->can_id = so->txid; + cf->len = so->tx.ll_dl; + if (ae) + cf->data[0] = so->opt.ext_address; + + /* create N_PCI bytes with 12/32 bit FF_DL data length */ + if (so->tx.len > 4095) { + /* use 32 bit FF_DL notation */ + cf->data[ae] = N_PCI_FF; + cf->data[ae + 1] = 0; + cf->data[ae + 2] = (u8)(so->tx.len >> 24) & 0xFFU; + cf->data[ae + 3] = (u8)(so->tx.len >> 16) & 0xFFU; + cf->data[ae + 4] = (u8)(so->tx.len >> 8) & 0xFFU; + cf->data[ae + 5] = (u8)so->tx.len & 0xFFU; + ff_pci_sz = FF_PCI_SZ32; + } else { + /* use 12 bit FF_DL notation */ + cf->data[ae] = (u8)(so->tx.len >> 8) | N_PCI_FF; + cf->data[ae + 1] = (u8)so->tx.len & 0xFFU; + ff_pci_sz = FF_PCI_SZ12; + } + + /* add first data bytes depending on ae */ + for (i = ae + ff_pci_sz; i < so->tx.ll_dl; i++) + cf->data[i] = so->tx.buf[so->tx.idx++]; + + so->tx.sn = 1; + so->tx.state = ISOTP_WAIT_FIRST_FC; +} + +static void isotp_tx_timer_tsklet(unsigned long data) +{ + struct isotp_sock *so = (struct isotp_sock *)data; + struct sock *sk = &so->sk; + struct sk_buff *skb; + struct net_device *dev; + struct canfd_frame *cf; + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + + switch (so->tx.state) { + case ISOTP_WAIT_FC: + case ISOTP_WAIT_FIRST_FC: + + /* we did not get any flow control frame in time */ + + DBG("we did not get FC frame in time.\n"); + + /* reset tx state */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + break; + + case ISOTP_SENDING: + + /* push out the next segmented pdu */ + + DBG("next pdu to send.\n"); + + dev = dev_get_by_index(sock_net(sk), so->ifindex); + if (!dev) + break; + +isotp_tx_burst: + skb = alloc_skb(so->ll.mtu + CAN_SKBRES, GFP_ATOMIC); + if (!skb) { + dev_put(dev); + break; + } + + isotp_skb_reserve(skb, dev); + cf = (struct canfd_frame *)skb->data; + skb_put(skb, so->ll.mtu); + + /* create consecutive frame */ + isotp_fill_dataframe(cf, so, ae, 0); + + /* place consecutive frame N_PCI in appropriate index */ + cf->data[ae] = N_PCI_CF | so->tx.sn++; + so->tx.sn %= 16; + so->tx.bs++; + + if (so->ll.mtu == CANFD_MTU) + cf->flags = so->ll.tx_flags; + + skb->dev = dev; + isotp_skb_set_owner(skb, sk); + can_send(skb, 1); + + if (so->tx.idx >= so->tx.len) { + /* we are done */ + DBG("we are done\n"); + so->tx.state = ISOTP_IDLE; + dev_put(dev); + wake_up_interruptible(&so->wait); + break; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { + /* stop and wait for FC */ + DBG("BS stop and wait for FC\n"); + so->tx.state = ISOTP_WAIT_FC; + dev_put(dev); + hrtimer_start(&so->txtimer, + ktime_add(ktime_get(), ktime_set(1,0)), + HRTIMER_MODE_ABS); + break; + } + + /* no gap between data frames needed => use burst mode */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0) + if (!so->tx_gap) + goto isotp_tx_burst; +#else + if (!so->tx_gap.tv64) + goto isotp_tx_burst; +#endif + + /* start timer to send next data frame with correct delay */ + dev_put(dev); + hrtimer_start(&so->txtimer, + ktime_add(ktime_get(), so->tx_gap), + HRTIMER_MODE_ABS); + break; + + default: + WARN_ON_ONCE(1); + } +} + +static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txtimer); + tasklet_schedule(&so->txtsklet); + + return HRTIMER_NORESTART; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0) +static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) +#else +static int isotp_sendmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t size) +#endif +{ + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + struct sk_buff *skb; + struct net_device *dev; + struct canfd_frame *cf; + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + int off; + int err; + + if (!so->bound) + return -EADDRNOTAVAIL; + + /* we do not support multiple buffers - for now */ + if (so->tx.state != ISOTP_IDLE) { + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; + + /* wait for complete transmission of current pdu */ + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + } + + if (!size || size > MAX_MSG_LENGTH) + return -EINVAL; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0) + err = memcpy_from_msg(so->tx.buf, msg, size); +#else + err = memcpy_fromiovec(so->tx.buf, msg->msg_iov, size); +#endif + if (err < 0) + return err; + + dev = dev_get_by_index(sock_net(sk), so->ifindex); + if (!dev) + return -ENXIO; + + skb = sock_alloc_send_skb(sk, so->ll.mtu + CAN_SKBRES, + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) { + dev_put(dev); + return err; + } + + isotp_skb_reserve(skb, dev); + + so->tx.state = ISOTP_SENDING; + so->tx.len = size; + so->tx.idx = 0; + + cf = (struct canfd_frame *)skb->data; + skb_put(skb, so->ll.mtu); + + /* take care of a potential SF_DL ESC offset for TX_DL > 8 */ + off = (so->tx.ll_dl > CAN_MAX_DLEN) ? 1 : 0; + + /* check for single frame transmission depending on TX_DL */ + if (size <= so->tx.ll_dl - SF_PCI_SZ4 - ae - off) { + /* The message size generally fits into a SingleFrame - good. + * + * SF_DL ESC offset optimization: + * + * When TX_DL is greater 8 but the message would still fit + * into a 8 byte CAN frame, we can omit the offset. + * This prevents a protocol caused length extension from + * CAN_DL = 8 to CAN_DL = 12 due to the SF_SL ESC handling. + */ + if (size <= CAN_MAX_DLEN - SF_PCI_SZ4 - ae) + off = 0; + + isotp_fill_dataframe(cf, so, ae, off); + + /* place single frame N_PCI w/o length in appropriate index */ + cf->data[ae] = N_PCI_SF; + + /* place SF_DL size value depending on the SF_DL ESC offset */ + if (off) + cf->data[SF_PCI_SZ4 + ae] = size; + else + cf->data[ae] |= size; + + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + + /* don't enable wait queue for a single frame transmission */ + wait_tx_done = 0; + } else { + /* send first frame and wait for FC */ + + isotp_create_fframe(cf, so, ae); + + DBG("starting txtimer for fc\n"); + /* start timeout for FC */ + hrtimer_start(&so->txtimer, ktime_set(1,0), HRTIMER_MODE_REL); + } + + /* send the first or only CAN frame */ + if (so->ll.mtu == CANFD_MTU) + cf->flags = so->ll.tx_flags; + + skb->dev = dev; + skb->sk = sk; + err = can_send(skb, 1); + dev_put(dev); + if (err) { + printk_once(KERN_NOTICE "can-isotp: %s: can_send_ret %d\n", + __func__, err); + return err; + } + + if (wait_tx_done) { + /* wait for complete transmission of current pdu */ + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + } + + return size; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0) +static int isotp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, + int flags) +#else +static int isotp_recvmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t size, int flags) +#endif +{ + struct sock *sk = sock->sk; + struct sk_buff *skb; + int err = 0; + int noblock; + + noblock = flags & MSG_DONTWAIT; + flags &= ~MSG_DONTWAIT; + + skb = skb_recv_datagram(sk, flags, noblock, &err); + if (!skb) + return err; + + if (size < skb->len) + msg->msg_flags |= MSG_TRUNC; + else + size = skb->len; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0) + err = memcpy_to_msg(msg, skb->data, size); +#else + err = memcpy_toiovec(msg->msg_iov, skb->data, size); +#endif + if (err < 0) { + skb_free_datagram(sk, skb); + return err; + } + + sock_recv_timestamp(msg, sk, skb); + + if (msg->msg_name) { + msg->msg_namelen = sizeof(struct sockaddr_can); + memcpy(msg->msg_name, skb->cb, msg->msg_namelen); + } + + skb_free_datagram(sk, skb); + + return size; +} + +static int isotp_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + struct isotp_sock *so; + struct net *net; + + if (!sk) + return 0; + + so = isotp_sk(sk); + net = sock_net(sk); + + /* wait for complete transmission of current pdu */ + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + + unregister_netdevice_notifier(&so->notifier); + + lock_sock(sk); + + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + tasklet_kill(&so->txtsklet); + + /* remove current filters & unregister */ + if (so->bound) { + if (so->ifindex) { + struct net_device *dev; + + dev = dev_get_by_index(net, so->ifindex); + if (dev) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) + can_rx_unregister(net, dev, so->rxid, +#else + can_rx_unregister(dev, so->rxid, +#endif + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + dev_put(dev); + } + } + } + + so->ifindex = 0; + so->bound = 0; + + sock_orphan(sk); + sock->sk = NULL; + + release_sock(sk); + sock_put(sk); + + return 0; +} + +static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) +{ + struct sockaddr_can *addr = (struct sockaddr_can *)uaddr; + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + struct net *net = sock_net(sk); + int ifindex; + struct net_device *dev; + int err = 0; + int notify_enetdown = 0; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) + if (len < CAN_REQUIRED_SIZE(struct sockaddr_can, can_addr.tp)) +#else + if (len < sizeof(*addr)) +#endif + return -EINVAL; + + if (addr->can_addr.tp.rx_id == addr->can_addr.tp.tx_id) + return -EADDRNOTAVAIL; + + if ((addr->can_addr.tp.rx_id | addr->can_addr.tp.tx_id) & + (CAN_ERR_FLAG | CAN_RTR_FLAG)) + return -EADDRNOTAVAIL; + + if (!addr->can_ifindex) + return -ENODEV; + + lock_sock(sk); + + if (so->bound && addr->can_ifindex == so->ifindex && + addr->can_addr.tp.rx_id == so->rxid && + addr->can_addr.tp.tx_id == so->txid) + goto out; + + dev = dev_get_by_index(net, addr->can_ifindex); + if (!dev) { + err = -ENODEV; + goto out; + } + if (dev->type != ARPHRD_CAN) { + dev_put(dev); + err = -ENODEV; + goto out; + } + if (dev->mtu < so->ll.mtu) { + dev_put(dev); + err = -EINVAL; + goto out; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; + + ifindex = dev->ifindex; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) + can_rx_register(net, dev, addr->can_addr.tp.rx_id, +#else + can_rx_register(dev, addr->can_addr.tp.rx_id, +#endif + SINGLE_MASK(addr->can_addr.tp.rx_id), isotp_rcv, sk, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,11)) || \ + ((LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,50)) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))) || \ + ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,49)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0))) || \ + ((LINUX_VERSION_CODE >= KERNEL_VERSION(3,16,42)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))) + "isotp", sk); +#else + "isotp"); +#endif + dev_put(dev); + + if (so->bound) { + /* unregister old filter */ + if (so->ifindex) { + dev = dev_get_by_index(net, so->ifindex); + if (dev) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) + can_rx_unregister(net, dev, so->rxid, +#else + can_rx_unregister(dev, so->rxid, +#endif + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + dev_put(dev); + } + } + } + + /* switch to new settings */ + so->ifindex = ifindex; + so->rxid = addr->can_addr.tp.rx_id; + so->txid = addr->can_addr.tp.tx_id; + so->bound = 1; + +out: + release_sock(sk); + + if (notify_enetdown) { + sk->sk_err = ENETDOWN; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + } + + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0) +static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, int peer) +#else +static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, + int *len, int peer) +#endif +{ + struct sockaddr_can *addr = (struct sockaddr_can *)uaddr; + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + + if (peer) + return -EOPNOTSUPP; + + addr->can_family = AF_CAN; + addr->can_ifindex = so->ifindex; + addr->can_addr.tp.rx_id = so->rxid; + addr->can_addr.tp.tx_id = so->txid; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0) + return sizeof(*addr); +#else + *len = sizeof(*addr); + + return 0; +#endif +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) +#define copy_from_user copy_from_sockptr +static int isotp_setsockopt(struct socket *sock, int level, int optname, + sockptr_t optval, unsigned int optlen) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +static int isotp_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, unsigned int optlen) +#else +static int isotp_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, int optlen) +#endif +{ + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + int ret = 0; + + if (level != SOL_CAN_ISOTP) + return -EINVAL; + + switch (optname) { + case CAN_ISOTP_OPTS: + if (optlen != sizeof(struct can_isotp_options)) + return -EINVAL; + + if (copy_from_user(&so->opt, optval, optlen)) + return -EFAULT; + + /* no separate rx_ext_address is given => use ext_address */ + if (!(so->opt.flags & CAN_ISOTP_RX_EXT_ADDR)) + so->opt.rx_ext_address = so->opt.ext_address; + break; + + case CAN_ISOTP_RECV_FC: + if (optlen != sizeof(struct can_isotp_fc_options)) + return -EINVAL; + + if (copy_from_user(&so->rxfc, optval, optlen)) + return -EFAULT; + break; + + case CAN_ISOTP_TX_STMIN: + if (optlen != sizeof(__u32)) + return -EINVAL; + + if (copy_from_user(&so->force_tx_stmin, optval, optlen)) + return -EFAULT; + break; + + case CAN_ISOTP_RX_STMIN: + if (optlen != sizeof(__u32)) + return -EINVAL; + + if (copy_from_user(&so->force_rx_stmin, optval, optlen)) + return -EFAULT; + break; + + case CAN_ISOTP_LL_OPTS: + if (optlen == sizeof(struct can_isotp_ll_options)) { + struct can_isotp_ll_options ll; + + if (copy_from_user(&ll, optval, optlen)) + return -EFAULT; + + /* check for correct ISO 11898-1 DLC data length */ + if (ll.tx_dl != padlen(ll.tx_dl)) + return -EINVAL; + + if (ll.mtu != CAN_MTU && ll.mtu != CANFD_MTU) + return -EINVAL; + + if (ll.mtu == CAN_MTU && ll.tx_dl > CAN_MAX_DLEN) + return -EINVAL; + + memcpy(&so->ll, &ll, sizeof(ll)); + + /* set ll_dl for tx path to similar place as for rx */ + so->tx.ll_dl = ll.tx_dl; + } else { + return -EINVAL; + } + break; + + default: + ret = -ENOPROTOOPT; + } + + return ret; +} + +static int isotp_getsockopt(struct socket *sock, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + int len; + void *val; + + if (level != SOL_CAN_ISOTP) + return -EINVAL; + if (get_user(len, optlen)) + return -EFAULT; + if (len < 0) + return -EINVAL; + + switch (optname) { + case CAN_ISOTP_OPTS: + len = min_t(int, len, sizeof(struct can_isotp_options)); + val = &so->opt; + break; + + case CAN_ISOTP_RECV_FC: + len = min_t(int, len, sizeof(struct can_isotp_fc_options)); + val = &so->rxfc; + break; + + case CAN_ISOTP_TX_STMIN: + len = min_t(int, len, sizeof(u32)); + val = &so->force_tx_stmin; + break; + + case CAN_ISOTP_RX_STMIN: + len = min_t(int, len, sizeof(u32)); + val = &so->force_rx_stmin; + break; + + case CAN_ISOTP_LL_OPTS: + len = min_t(int, len, sizeof(struct can_isotp_ll_options)); + val = &so->ll; + break; + + default: + return -ENOPROTOOPT; + } + + if (put_user(len, optlen)) + return -EFAULT; + if (copy_to_user(optval, val, len)) + return -EFAULT; + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0) +static int isotp_notifier(struct notifier_block *nb, unsigned long msg, + void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); +#else +static int isotp_notifier(struct notifier_block *nb, + unsigned long msg, void *data) +{ + struct net_device *dev = (struct net_device *)data; +#endif + struct isotp_sock *so = container_of(nb, struct isotp_sock, notifier); + struct sock *sk = &so->sk; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) + if (!net_eq(dev_net(dev), sock_net(sk))) + return NOTIFY_DONE; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) + if (dev_net(dev) != &init_net) + return NOTIFY_DONE; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if (dev->nd_net != &init_net) + return NOTIFY_DONE; +#endif + + if (dev->type != ARPHRD_CAN) + return NOTIFY_DONE; + + if (so->ifindex != dev->ifindex) + return NOTIFY_DONE; + + switch (msg) { + + case NETDEV_UNREGISTER: + lock_sock(sk); + /* remove current filters & unregister */ + if (so->bound) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) + can_rx_unregister(dev_net(dev), dev, so->rxid, +#else + can_rx_unregister(dev, so->rxid, +#endif + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + + so->ifindex = 0; + so->bound = 0; + release_sock(sk); + + sk->sk_err = ENODEV; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + break; + + case NETDEV_DOWN: + sk->sk_err = ENETDOWN; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + break; + } + + return NOTIFY_DONE; +} + +static int isotp_init(struct sock *sk) +{ + struct isotp_sock *so = isotp_sk(sk); + + so->ifindex = 0; + so->bound = 0; + + so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS; + so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; + so->opt.rx_ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; + so->opt.rxpad_content = CAN_ISOTP_DEFAULT_PAD_CONTENT; + so->opt.txpad_content = CAN_ISOTP_DEFAULT_PAD_CONTENT; + so->opt.frame_txtime = CAN_ISOTP_DEFAULT_FRAME_TXTIME; + so->rxfc.bs = CAN_ISOTP_DEFAULT_RECV_BS; + so->rxfc.stmin = CAN_ISOTP_DEFAULT_RECV_STMIN; + so->rxfc.wftmax = CAN_ISOTP_DEFAULT_RECV_WFTMAX; + so->ll.mtu = CAN_ISOTP_DEFAULT_LL_MTU; + so->ll.tx_dl = CAN_ISOTP_DEFAULT_LL_TX_DL; + so->ll.tx_flags = CAN_ISOTP_DEFAULT_LL_TX_FLAGS; + + /* set ll_dl for tx path to similar place as for rx */ + so->tx.ll_dl = so->ll.tx_dl; + + so->rx.state = ISOTP_IDLE; + so->tx.state = ISOTP_IDLE; + + hrtimer_init(&so->rxtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + so->txtimer.function = isotp_tx_timer_handler; + + tasklet_init(&so->txtsklet, isotp_tx_timer_tsklet, (unsigned long)so); + + init_waitqueue_head(&so->wait); + + so->notifier.notifier_call = isotp_notifier; + register_netdevice_notifier(&so->notifier); + + return 0; +} + +static int isotp_sock_no_ioctlcmd(struct socket *sock, unsigned int cmd, + unsigned long arg) +{ + /* no ioctls for socket layer -> hand it down to NIC layer */ + return -ENOIOCTLCMD; +} + +static const struct proto_ops isotp_ops = { + .family = PF_CAN, + .release = isotp_release, + .bind = isotp_bind, + .connect = sock_no_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = isotp_getname, + .poll = datagram_poll, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,2,0) + .ioctl = isotp_sock_no_ioctlcmd, + .gettstamp = sock_gettstamp, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39) + .ioctl = can_ioctl, /* use can_ioctl() from af_can.c */ +#else + .ioctl = NULL, /* use can_ioctl() from af_can.c */ +#endif + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = isotp_setsockopt, + .getsockopt = isotp_getsockopt, + .sendmsg = isotp_sendmsg, + .recvmsg = isotp_recvmsg, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; + +static struct proto isotp_proto __read_mostly = { + .name = "CAN_ISOTP", + .owner = THIS_MODULE, + .obj_size = sizeof(struct isotp_sock), + .init = isotp_init, +}; + +static const struct can_proto isotp_can_proto = { + .type = SOCK_DGRAM, + .protocol = CAN_ISOTP, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33) + .capability = -1, +#endif + .ops = &isotp_ops, + .prot = &isotp_proto, +}; + +static __init int isotp_module_init(void) +{ + int err; + + printk(KERN_INFO "can: isotp protocol (rev " CAN_ISOTP_VERSION ")\n"); + + err = can_proto_register(&isotp_can_proto); + if (err < 0) + printk(KERN_ERR "can: registration of isotp protocol failed\n"); + + return err; +} + +static __exit void isotp_module_exit(void) +{ + can_proto_unregister(&isotp_can_proto); +} + +module_init(isotp_module_init); +module_exit(isotp_module_exit); diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 27007c1..732cd03 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -152,6 +152,9 @@ int main(void) DEVID_FIELD(i3c_device_id, part_id); DEVID_FIELD(i3c_device_id, extra_info); + DEVID(serdev_device_id); + DEVID_FIELD(serdev_device_id, name); + DEVID(spi_device_id); DEVID_FIELD(spi_device_id, name); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 2417dd1..540fee0 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -947,6 +947,15 @@ static int do_spi_entry(const char *filename, void *symval, return 1; } +static int do_serdev_entry(const char *filename, void *symval, + char *alias) +{ + DEF_FIELD_ADDR(symval, serdev_device_id, name); + sprintf(alias, SERDEV_MODULE_PREFIX "%s", *name); + + return 1; +} + static const struct dmifield { const char *prefix; int field; @@ -1420,6 +1429,7 @@ static const struct devtable devtable[] = { {"rpmsg", SIZE_rpmsg_device_id, do_rpmsg_entry}, {"i2c", SIZE_i2c_device_id, do_i2c_entry}, {"i3c", SIZE_i3c_device_id, do_i3c_entry}, + {"serdev", SIZE_serdev_device_id, do_serdev_entry}, {"spi", SIZE_spi_device_id, do_spi_entry}, {"dmi", SIZE_dmi_system_id, do_dmi_entry}, {"platform", SIZE_platform_device_id, do_platform_entry}, diff --git a/scripts/package/builddeb b/scripts/package/builddeb index 64e80fc..5a4918e 100755 --- a/scripts/package/builddeb +++ b/scripts/package/builddeb @@ -146,6 +146,7 @@ if is_enabled CONFIG_OF_EARLY_FLATTREE; then # Only some architectures with OF support have this target if [ -d "${srctree}/arch/$SRCARCH/boot/dts" ]; then $MAKE -f $srctree/Makefile INSTALL_DTBS_PATH="$tmpdir/usr/lib/$packagename" dtbs_install + $MAKE -f $srctree/Makefile INSTALL_DTBS_PATH="$tmpdir/boot/dtbs/$version" dtbs_install fi fi