diff --git a/Documentation/devicetree/bindings/serial/bouffalolab,uart.yaml b/Documentation/devicetree/bindings/serial/bouffalolab,uart.yaml new file mode 100644 index 00000000000000..6cef956d33d25e --- /dev/null +++ b/Documentation/devicetree/bindings/serial/bouffalolab,uart.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2022 Jisheng Zhang +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/serial/bouffalolab,uart.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Bouffalolab UART Controller + +maintainers: + - Jisheng Zhang + +allOf: + - $ref: serial.yaml# + +properties: + compatible: + const: bouffalolab,uart + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + +additionalProperties: false + +examples: + - | + #include + aliases { + serial0 = &uart0; + }; + + uart0: serial@30002000 { + compatible = "bouffalolab,uart"; + reg = <0x30002000 0x1000>; + interrupts = <53 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&xtal>; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 135d93368d36ed..952099139569fb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3788,6 +3788,12 @@ S: Maintained F: Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml F: drivers/iio/accel/bma400* +BOUFFALOLAB UART DRIVER +M: Jisheng Zhang +S: Maintained +F: Documentation/devicetree/bindings/serial/bouffalolab,uart.yaml +F: drivers/tty/serial/bflb_uart.c + BPF [GENERAL] (Safe Dynamic Programs and Tools) M: Alexei Starovoitov M: Daniel Borkmann @@ -17975,6 +17981,12 @@ F: arch/riscv/ N: riscv K: riscv +RISC-V BOUFFALOLAB SOC SUPPORT +M: Jisheng Zhang +L: linux-riscv at lists.infradead.org +S: Maintained +F: arch/riscv/boot/dts/bouffalolab/ + RISC-V MICROCHIP FPGA SUPPORT M: Conor Dooley M: Daire McNamara diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index 4b6deb2715f1c4..a68ab2172230e5 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -1,5 +1,11 @@ menu "SoC selection" +config SOC_BOUFFALOLAB + bool "Bouffalolab SoCs" + select SIFIVE_PLIC + help + This enables support for Bouffalolab SoC platforms. + config SOC_MICROCHIP_POLARFIRE bool "Microchip PolarFire SoCs" select MCHP_CLK_MPFS diff --git a/arch/riscv/boot/dts/Makefile b/arch/riscv/boot/dts/Makefile index b0ff5fbabb0c9a..2d4376810bcc75 100644 --- a/arch/riscv/boot/dts/Makefile +++ b/arch/riscv/boot/dts/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +subdir-y += bouffalolab subdir-y += sifive subdir-y += starfive subdir-$(CONFIG_SOC_CANAAN_K210_DTB_BUILTIN) += canaan diff --git a/arch/riscv/boot/dts/bouffalolab/Makefile b/arch/riscv/boot/dts/bouffalolab/Makefile new file mode 100644 index 00000000000000..bc7aad3d560406 --- /dev/null +++ b/arch/riscv/boot/dts/bouffalolab/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +dtb-$(CONFIG_SOC_BOUFFALOLAB) += bl808-sipeed-m1s.dtb +dtb-$(CONFIG_SOC_BOUFFALOLAB) += bl808-pine64-ox64.dtb diff --git a/arch/riscv/boot/dts/bouffalolab/bl808-pine64-ox64.dts b/arch/riscv/boot/dts/bouffalolab/bl808-pine64-ox64.dts new file mode 100644 index 00000000000000..00375520ef0756 --- /dev/null +++ b/arch/riscv/boot/dts/bouffalolab/bl808-pine64-ox64.dts @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) +/* + * Copyright (C) 2022 Jisheng Zhang + */ + +/dts-v1/; + +#include "bl808.dtsi" + +/ { + model = "Pine64 Ox64"; + compatible = "pine64,ox64", "bflb,bl808"; + + aliases { + serial0 = &uart0; + serial1 = &uart1; + }; + + chosen { + stdout-path = "serial0:2000000n8"; + bootargs = "console=ttyS0,2000000 loglevel=8 earlycon=sbi root=/dev/mmcblk0p2 rootwait rootfstype=ext4"; + linux,initrd-start = <0x0 0x52000000>; + linux,initrd-end = <0x0 0x52941784>; + }; + + memory@50000000 { + device_type = "memory"; + reg = <0x50000000 0x04000000>; + }; + + xip_flash@58500000 { + compatible = "mtd-rom"; + reg = <0x58500000 0x400000>; + linux,mtd-name = "xip-flash.0"; + erase-size = <0x10000>; + bank-width = <4>; + #address-cells = <1>; + #size-cells = <1>; + }; +}; + +&pinctrl { + status = "okay"; +}; + +&seceng { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&sdhci0 { + status = "okay"; +}; + +&ipclic { + status = "okay"; +}; + +&ehci0 { + status = "disabled"; +}; + +&enet0 { + status = "okay"; +}; + +&rproc { + status = "okay"; +}; \ No newline at end of file diff --git a/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts new file mode 100644 index 00000000000000..8adad700d0ca55 --- /dev/null +++ b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) +/* + * Copyright (C) 2022 Jisheng Zhang + */ + +/dts-v1/; + +#include "bl808.dtsi" +#include +#include + +/ { + model = "Sipeed M1s"; + compatible = "sipeed,m1s", "bflb,bl808"; + + aliases { + serial0 = &uart0; + }; + + chosen { + stdout-path = "serial0:2000000n8"; + bootargs = "console=ttyS0,2000000 loglevel=8 earlycon=sbi root=/dev/mmcblk0p2 rootwait rootfstype=ext4"; + linux,initrd-start = <0x0 0x52000000>; + linux,initrd-end = <0x0 0x52941784>; + }; + + memory@50000000 { + device_type = "memory"; + reg = <0x50000000 0x04000000>; + }; + + xip_flash@58500000 { + compatible = "mtd-rom"; + reg = <0x58500000 0x400000>; + linux,mtd-name = "xip-flash.0"; + erase-size = <0x10000>; + bank-width = <4>; + #address-cells = <1>; + #size-cells = <1>; + rootfs@0 { + label = "rootfs"; + reg = <0x00000 0x280000>; + read-only; + }; + }; + + leds { + compatible = "gpio-leds"; + led { + gpios = <&pinctrl 8 GPIO_ACTIVE_LOW>; + }; + }; +}; + +&pinctrl { + status = "okay"; + + led { + pins = "GPIO8"; + function = "gpio"; + }; +}; + +&seceng { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&sdhci0 { + status = "okay"; +}; + +&ipclic { + status = "okay"; +}; + +&ehci0 { + status = "okay"; +}; + +&enet0 { + status = "okay"; +}; + +&rproc { + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/bouffalolab/bl808.dtsi b/arch/riscv/boot/dts/bouffalolab/bl808.dtsi new file mode 100644 index 00000000000000..1c4f47a50109f3 --- /dev/null +++ b/arch/riscv/boot/dts/bouffalolab/bl808.dtsi @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) +/* + * Copyright (C) 2022 Jisheng Zhang + */ + +#include +#include + +/ { + compatible = "bflb,bl808"; + #address-cells = <1>; + #size-cells = <1>; + + cpus { + timebase-frequency = <1000000>; + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + compatible = "thead,c906", "riscv"; + device_type = "cpu"; + reg = <0>; + d-cache-block-size = <64>; + d-cache-sets = <256>; + d-cache-size = <32768>; + i-cache-block-size = <64>; + i-cache-sets = <128>; + i-cache-size = <32768>; + mmu-type = "riscv,sv39"; + riscv,isa = "rv64imafdc"; + + cpu0_intc: interrupt-controller { + compatible = "riscv,cpu-intc"; + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <1>; + }; + }; + }; + + xtal: xtal-clk { + compatible = "fixed-clock"; + clock-frequency = <40000000>; + clock-output-names = "xtal"; + #clock-cells = <0>; + }; + + sdh: sdh-clk { + compatible = "fixed-clock"; + clock-frequency = <96000000>; + clock-output-names = "sdh"; + #clock-cells = <0>; + }; + + enet: enet-clk { + compatible = "fixed-clock"; + clock-frequency = <50000000>; + clock-output-names = "enet"; + #clock-cells = <0>; + }; + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + /* putting this at the top of uncached WRAM for the moment */ + vdev0vring0: vdev0vring0@22048000 { + compatible = "shared-dma-pool"; + reg = <0x22048000 0x4000>; + no-map; + }; + vdev0vring1: vdev0vring1@2204C000 { + compatible = "shared-dma-pool"; + reg = <0x2204C000 0x4000>; + no-map; + }; + vdev0buffer: vdev0buffer@22050000 { + compatible = "shared-dma-pool"; + reg = <0x22050000 0x8000>; + no-map; + }; + }; + + soc { + compatible = "simple-bus"; + ranges; + interrupt-parent = <&plic>; + dma-noncoherent; + #address-cells = <1>; + #size-cells = <1>; + + pinctrl: pinctrl@0x200008C4 { + compatible = "bflb,pinctrl"; + //Last register is for gpio_cfg141 at 0x20000af8 + reg = <0x200008C4 0x1000>; + //clocks = <&gpio_clk>; + + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pinctrl 0 0 46>; + bflb,npins = <46>; + + status = "disabled"; + + interrupt-controller; + #interrupt-cells = <2>; + interrupts-extended = <&ipclic BFLB_IPC_SOURCE_M0 + BFLB_IPC_DEVICE_GPIO IRQ_TYPE_EDGE_RISING>; + + sdh_pins: sdh-pins { + pins = "GPIO0", "GPIO1", "GPIO2", "GPIO3", "GPIO4", "GPIO5"; + function = "sdh"; + }; + }; + + seceng: seceng@0x20004000 { + compatible = "bflb,seceng"; + reg = <0x20004000 0x1000>; + status = "disabled"; + }; + + uart0: serial@30002000 { + compatible = "bflb,bl808-uart"; + reg = <0x30002000 0x1000>; + interrupts = <20 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&xtal>; + status = "disabled"; + }; + + uart1: serial@0x2000AA00 { + compatible = "bflb,bl808-uart"; + reg = <0x2000AA00 0x0100>; + interrupts-extended = <&ipclic BFLB_IPC_SOURCE_M0 + BFLB_IPC_DEVICE_UART2 + IRQ_TYPE_EDGE_RISING>; + clocks = <&xtal>; + status = "disabled"; + }; + + sdhci0: sdhci@20060000 { + compatible = "bflb,bl808-sdhci"; + reg = <0x20060000 0x100>; + interrupts-extended = <&ipclic BFLB_IPC_SOURCE_M0 + BFLB_IPC_DEVICE_SDHCI + IRQ_TYPE_EDGE_RISING>; + clocks = <&sdh>; + status = "disabled"; + }; + + ehci0: usb@20072000 { + compatible = "generic-ehci"; + reg = <0x20072000 0x1000>; + interrupts-extended = <&ipclic BFLB_IPC_SOURCE_M0 + BFLB_IPC_DEVICE_USB + IRQ_TYPE_EDGE_RISING>; + clocks = <&xtal>; + status = "disabled"; + }; + + enet0: emac@20070000 { + compatible = "opencores,ethoc"; + reg = <0x20070000 0x1000>; + interrupts-extended = <&ipclic BFLB_IPC_SOURCE_M0 + BFLB_IPC_DEVICE_EMAC + IRQ_TYPE_EDGE_RISING>; + clocks = <&enet>; + status = "disabled"; + }; + + ipclic: mailbox@30005000 { + compatible = "bflb,bl808-ipc"; + reg = <0x30005000 0x20>, + <0x30005020 0x20>, + <0x2000a800 0x20>, + <0x2000a820 0x20>; + interrupts = <54 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <3>; + #mbox-cells = <3>; + status = "disabled"; + }; + + plic: interrupt-controller@e0000000 { + compatible = "thead,c900-plic"; + reg = <0xe0000000 0x4000000>; + interrupts-extended = <&cpu0_intc 0xffffffff>, + <&cpu0_intc 9>; + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <2>; + riscv,ndev = <64>; + }; + + clint: timer@e4000000 { + compatible = "thead,c900-clint"; + reg = <0xe4000000 0xc000>; + interrupts-extended = <&cpu0_intc 3>, + <&cpu0_intc 7>; + }; + + rproc: rproc@22054000 { + compatible = "bflb,bflb-rproc"; + reg = <0x22054000 0x4000>; + memory-region = <&vdev0vring0>, <&vdev0vring1>, <&vdev0buffer>; + mboxes = <&ipclic BFLB_IPC_TARGET_M0 BFLB_IPC_MBOX_VIRTIO BFLB_IPC_MBOX_VIRTIO_OP_KICK>, + <&ipclic BFLB_IPC_SOURCE_M0 BFLB_IPC_MBOX_VIRTIO BFLB_IPC_MBOX_VIRTIO_OP_KICK>; + mbox-names = "virtio-tx", "virtio-rx"; + + status = "disabled"; + }; + }; +}; diff --git a/arch/riscv/configs/bl808_defconfig b/arch/riscv/configs/bl808_defconfig new file mode 100644 index 00000000000000..7fbae4f8762615 --- /dev/null +++ b/arch/riscv/configs/bl808_defconfig @@ -0,0 +1,208 @@ +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_GENERIC_IRQ_DEBUGFS=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_EXPERT=y +CONFIG_PERF_EVENTS=y +CONFIG_SOC_BOUFFALOLAB=y +CONFIG_SOC_VIRT=y +CONFIG_ERRATA_THEAD=y +CONFIG_SMP=y +CONFIG_NR_CPUS=8 +CONFIG_RISCV_SBI_V01=y +# CONFIG_COMPAT is not set +CONFIG_KPROBES=y +CONFIG_JUMP_LABEL=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_CMDLINE_PARTITION=y +CONFIG_PAGE_REPORTING=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +CONFIG_DNS_RESOLVER=y +CONFIG_NETLINK_DIAG=y +# CONFIG_WIRELESS is not set +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_MTD=y +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_BLOCK_RO=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_ADV_OPTIONS=y +CONFIG_MTD_ROM=y +CONFIG_MTD_ABSENT=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_PHYSMAP_VERSATILE=y +CONFIG_MTD_PHYSMAP_GEMINI=y +CONFIG_MTD_PLATRAM=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_VIRTIO_BLK=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_SCSI_VIRTIO=y +CONFIG_NETDEVICES=y +CONFIG_VIRTIO_NET=y +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_LITEX is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +CONFIG_ETHOC=y +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +# CONFIG_WLAN is not set +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=y +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_EARLYCON_RISCV_SBI=y +CONFIG_SERIAL_BFLB=y +CONFIG_SERIAL_BFLB_CONSOLE=y +CONFIG_SERIAL_SIFIVE=y +CONFIG_SERIAL_SIFIVE_CONSOLE=y +CONFIG_HVC_RISCV_SBI=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_VIRTIO=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_XILINX=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_I2C_DEBUG_CORE=y +CONFIG_I2C_DEBUG_ALGO=y +CONFIG_I2C_DEBUG_BUS=y +# CONFIG_PTP_1588_CLOCK is not set +CONFIG_PINCTRL=y +CONFIG_PINCTRL_BFLB_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_FB=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_VGA_CONSOLE is not set +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +CONFIG_USB=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_SERIAL=y +CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_BFLB=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_USER=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_MTD=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_ACTIVITY=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=y +CONFIG_LEDS_TRIGGER_CAMERA=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_LEDS_TRIGGER_PATTERN=y +CONFIG_LEDS_TRIGGER_AUDIO=y +CONFIG_LEDS_TRIGGER_TTY=y +CONFIG_RTC_CLASS=y +CONFIG_SYNC_FILE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_MENU is not set +CONFIG_MAILBOX=y +CONFIG_BFLB_IPC=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RPMSG_CHAR=y +CONFIG_RPMSG_VIRTIO=y +CONFIG_GENERIC_PHY=y +CONFIG_EXT4_FS=y +CONFIG_AUTOFS4_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_CONFIGFS_FS=y +# CONFIG_EFIVAR_FS is not set +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_4K_DEVBLK_SIZE=y +# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y +CONFIG_KEYS=y +CONFIG_LSM="lockdown,yama,loadpin,safesetid,integrity" +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_DEV_BFLB_SECENG=y +CONFIG_CRYPTO_DEV_VIRTIO=y +CONFIG_CRC_ITU_T=y +CONFIG_CRC7=y +CONFIG_XZ_DEC=y +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_VM_PGTABLE=y +CONFIG_FUNCTION_ERROR_INJECTION=y +# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_MEMTEST=y diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index dfb103f81a64b3..e8de3d60ffd863 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -13,6 +13,17 @@ if CRYPTO_HW source "drivers/crypto/allwinner/Kconfig" +config CRYPTO_DEV_BFLB_SECENG + tristate "Bouffalo Lab Secure Engine Driver" + depends on SOC_BOUFFALOLAB + select CRYPTO_RNG + help + This driver provides support for the Random Number + Generator hardware found on Bouffalo Lab BL808 SoCs. + + To compile this driver as a module, choose M here. The + module will be called bflb-seceng. If unsure, say N. + config CRYPTO_DEV_PADLOCK tristate "Support for VIA PadLock ACE" depends on X86 && !UML diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index fa8bf1be1a8cde..1b5c60587c436f 100644 --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_CRYPTO_DEV_ATMEL_TDES) += atmel-tdes.o obj-$(CONFIG_CRYPTO_DEV_ATMEL_I2C) += atmel-i2c.o obj-$(CONFIG_CRYPTO_DEV_ATMEL_ECC) += atmel-ecc.o obj-$(CONFIG_CRYPTO_DEV_ATMEL_SHA204A) += atmel-sha204a.o +obj-$(CONFIG_CRYPTO_DEV_BFLB_SECENG) += bflb-seceng.o obj-$(CONFIG_CRYPTO_DEV_CAVIUM_ZIP) += cavium/ obj-$(CONFIG_CRYPTO_DEV_CCP) += ccp/ obj-$(CONFIG_CRYPTO_DEV_CCREE) += ccree/ diff --git a/drivers/crypto/bflb-seceng.c b/drivers/crypto/bflb-seceng.c new file mode 100644 index 00000000000000..3e450f06ddeaea --- /dev/null +++ b/drivers/crypto/bflb-seceng.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Bouffalo Lab SoC Secure Engine driver +// +// Based on qcom-rng.c +// Copyright (c) 2017-18 Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//Register map +//se_sha_0_ctrl +#define REG_SECENG_SHA_0_CTRL 0 //Offset from base address +#define REG_SECENG_SHA_0_CTRL_MSG_LEN GENMASK(31, 16) +#define REG_SECENG_SHA_0_CTRL_LINK_MODE BIT(15) +#define REG_SECENG_SHA_0_CTRL_MODE_EXT GENMASK(13, 12) +#define REG_SECENG_SHA_0_CTRL_INT_MASK BIT(11) +#define REG_SECENG_SHA_0_CTRL_INT_SET_1T BIT(10) +#define REG_SECENG_SHA_0_CTRL_INT_CLR_1T BIT(9) +#define REG_SECENG_SHA_0_CTRL_INT BIT(8) +#define REG_SECENG_SHA_0_CTRL_HASH_SEL BIT(6) +#define REG_SECENG_SHA_0_CTRL_EN BIT(5) +#define REG_SECENG_SHA_0_CTRL_MODE GENMASK(4, 2) +#define REG_SECENG_SHA_0_CTRL_INT_TRIG_1T BIT(1) +#define REG_SECENG_SHA_0_CTRL_BUSY BIT(0) + +//se_sha_0_msa +#define REG_SECENG_SHA_0_MSA 4 + +//se_sha_0_status +#define REG_SECENG_SHA_0_STATUS 8 + +//se_sha_0_endian +#define REG_SECENG_SHA_0_ENDIAN 12 +#define REG_SECENG_SHA_0_ENDIAN_VAL BIT(0) + +//se_sha_0_hash_l_0 +#define REG_SECENG_SHA_0_HASH_L_0 16 + +//se_sha_0_hash_l_1 +#define REG_SECENG_SHA_0_HASH_L_1 20 + +//se_sha_0_hash_l_2 +#define REG_SECENG_SHA_0_HASH_L_2 24 + +//se_sha_0_hash_l_3 +#define REG_SECENG_SHA_0_HASH_L_3 28 + +//se_sha_0_hash_l_4 +#define REG_SECENG_SHA_0_HASH_L_4 32 + +//se_sha_0_hash_l_5 +#define REG_SECENG_SHA_0_HASH_L_5 36 + +//se_sha_0_hash_l_6 +#define REG_SECENG_SHA_0_HASH_L_6 40 + +//se_sha_0_hash_l_7 +#define REG_SECENG_SHA_0_HASH_L_7 44 + +//se_sha_0_hash_h_0 +#define REG_SECENG_SHA_0_HASH_H_0 48 + +//se_sha_0_hash_h_1 +#define REG_SECENG_SHA_0_HASH_H_1 52 + +//se_sha_0_hash_h_2 +#define REG_SECENG_SHA_0_HASH_H_2 56 + +//se_sha_0_hash_h_3 +#define REG_SECENG_SHA_0_HASH_H_3 60 + +//se_sha_0_hash_h_4 +#define REG_SECENG_SHA_0_HASH_H_4 64 + +//se_sha_0_hash_h_5 +#define REG_SECENG_SHA_0_HASH_H_5 68 + +//se_sha_0_hash_h_6 +#define REG_SECENG_SHA_0_HASH_H_6 72 + +//se_sha_0_hash_h_7 +#define REG_SECENG_SHA_0_HASH_H_7 76 + +//se_sha_0_link +#define REG_SECENG_SHA_0_LINK 80 + +//se_sha_0_ctrl_prot +#define REG_SECENG_SHA_0_CTRL_PROT 252 +#define REG_SECENG_SHA_0_CTRL_PROT_ID1_EN BIT(2) +#define REG_SECENG_SHA_0_CTRL_PROT_ID0_EN BIT(1) + +//se_aes_0_ctrl +#define REG_SECENG_AES_0_CTRL 256 +#define REG_SECENG_AES_0_CTRL_MSG_LEN GENMASK(31, 16) +#define REG_SECENG_AES_0_CTRL_LINK_MODE BIT(15) +#define REG_SECENG_AES_0_CTRL_IV_SEL BIT(14) +#define REG_SECENG_AES_0_CTRL_BLOCK_MODE GENMASK(13, 12) +#define REG_SECENG_AES_0_CTRL_INT_MASK BIT(11) +#define REG_SECENG_AES_0_CTRL_INT_SET_1T BIT(10) +#define REG_SECENG_AES_0_CTRL_INT_CLR_1T BIT(9) +#define REG_SECENG_AES_0_CTRL_INT BIT(8) +#define REG_SECENG_AES_0_CTRL_HW_KEY_EN BIT(7) +#define REG_SECENG_AES_0_CTRL_DEC_KEY_SEL BIT(6) +#define REG_SECENG_AES_0_CTRL_DEC_EN BIT(5) +#define REG_SECENG_AES_0_CTRL_MODE GENMASK(4, 3) +#define REG_SECENG_AES_0_CTRL_EN BIT(2) +#define REG_SECENG_AES_0_CTRL_TRIG_1T BIT(1) +#define REG_SECENG_AES_0_CTRL_BUSY BIT(0) + +//se_aes_0_msa +#define REG_SECENG_AES_0_MSA 260 + +//se_aes_0_mda +#define REG_SECENG_AES_0_MDA 264 + +//se_aes_0_status +#define REG_SECENG_AES_0_STATUS 268 + +//se_aes_0_iv_0 +#define REG_SECENG_AES_0_IV_0 272 + +//se_aes_0_iv_1 +#define REG_SECENG_AES_0_IV_1 276 + +//se_aes_0_iv_2 +#define REG_SECENG_AES_0_IV_2 280 + +//se_aes_0_iv_3 +#define REG_SECENG_AES_0_IV_3 284 + +//se_aes_0_key_0 +#define REG_SECENG_AES_0_KEY_0 288 + +//se_aes_0_key_1 +#define REG_SECENG_AES_0_KEY_1 292 + +//se_aes_0_key_2 +#define REG_SECENG_AES_0_KEY_2 296 + +//se_aes_0_key_3 +#define REG_SECENG_AES_0_KEY_3 300 + +//se_aes_0_key_4 +#define REG_SECENG_AES_0_KEY_4 304 + +//se_aes_0_key_5 +#define REG_SECENG_AES_0_KEY_5 308 + +//se_aes_0_key_6 +#define REG_SECENG_AES_0_KEY_6 312 + +//se_aes_0_key_7 +#define REG_SECENG_AES_0_KEY_7 316 + +//se_aes_0_key_sel +#define REG_SECENG_AES_0_KEY_SEL 320 +#define REG_SECENG_AES_0_KEY_SEL_VAL GENMASK(1, 0) + +//se_aes_1_key_sel +#define REG_SECENG_AES_1_KEY_SEL 324 +#define REG_SECENG_AES_01KEY_SEL_VAL GENMASK(1, 0) + +//se_aes_0_endian +#define REG_SECENG_AES_0_ENDIAN 328 +#define REG_SECENG_AES_0_ENDIAN_CTR_LEN GENMASK(31, 30) +#define REG_SECENG_AES_0_ENDIAN_TWK BIT(4) +#define REG_SECENG_AES_0_ENDIAN_IV BIT(3) +#define REG_SECENG_AES_0_ENDIAN_KEY BIT(2) +#define REG_SECENG_AES_0_ENDIAN_DIN BIT(1) +#define REG_SECENG_AES_0_ENDIAN_DOUT BIT(0) + +//se_aes_sboot +#define REG_SECENG_AES_SBOOT 332 +#define REG_SECENG_AES_SBOOT_UNI_LEN GENMASK(31, 16) +#define REG_SECENG_AES_SBOOT_XTS_MODE BIT(15) +#define REG_SECENG_AES_SBOOT_KEY_SEL BIT(0) + +//se_aes_0_link +#define REG_SECENG_AES_0_LINK 336 + +//se_aes_0_ctrl_prot +#define REG_SECENG_AES_0_CTRL_PROT 508 +#define REG_SECENG_AES_0_CTRL_PROT_ID1_EN BIT(2) +#define REG_SECENG_AES_0_CTRL_PROT_ID0_EN BIT(1) + +//se_trng_0_ctrl_0 +#define REG_SECENG_TRNG_0_CTRL_0 512 +#define REG_SECENG_TRNG_0_CTRL_0_MANUAL_EN BIT(15) +#define REG_SECENG_TRNG_0_CTRL_0_MANUAL_RESEED BIT(14) +#define REG_SECENG_TRNG_0_CTRL_0_MANUAL_FUN_SEL BIT(13) +#define REG_SECENG_TRNG_0_CTRL_0_INT_MASK BIT(11) +#define REG_SECENG_TRNG_0_CTRL_0_INT_SET_1T BIT(10) +#define REG_SECENG_TRNG_0_CTRL_0_INT_CLR_1T BIT(9) +#define REG_SECENG_TRNG_0_CTRL_0_INT BIT(8) +#define REG_SECENG_TRNG_0_CTRL_0_HT_ERROR BIT(4) +#define REG_SECENG_TRNG_0_CTRL_0_DOUT_CLR_1T BIT(3) +#define REG_SECENG_TRNG_0_CTRL_0_EN BIT(2) +#define REG_SECENG_TRNG_0_CTRL_0_TRIG_1T BIT(1) +#define REG_SECENG_TRNG_0_CTRL_0_BUSY BIT(0) + +//se_trng_0_status +#define REG_SECENG_TRNG_0_STATUS 516 + +//se_trng_0_dout_0 +#define REG_SECENG_TRNG_0_DOUT_0 520 + +//se_trng_0_dout_1 +#define REG_SECENG_TRNG_0_DOUT_1 524 + +//se_trng_0_dout_2 +#define REG_SECENG_TRNG_0_DOUT_2 528 + +//se_trng_0_dout_3 +#define REG_SECENG_TRNG_0_DOUT_3 532 + +//se_trng_0_dout_4 +#define REG_SECENG_TRNG_0_DOUT_4 536 + +//se_trng_0_dout_5 +#define REG_SECENG_TRNG_0_DOUT_5 540 + +//se_trng_0_dout_6 +#define REG_SECENG_TRNG_0_DOUT_6 544 + +//se_trng_0_dout_7 +#define REG_SECENG_TRNG_0_DOUT_7 548 + +//se_trng_0_test +#define REG_SECENG_TRNG_0_TEST 552 +#define REG_SECENG_TRNG_0_TEST_HT_ALARM_N GENMASK(11, 4) +#define REG_SECENG_TRNG_0_TEST_HT_DIS BIT(3) +#define REG_SECENG_TRNG_0_TEST_CP_BYPASS BIT(2) +#define REG_SECENG_TRNG_0_TEST_CP_TEST_EN BIT(1) +#define REG_SECENG_TRNG_0_TEST_TEST_EN BIT(0) + +//se_trng_0_ctrl_1 +#define REG_SECENG_TRNG_0_CTRL_1_RESEED_N_LSB 556 + +//se_trng_0_ctrl_2 +#define REG_SECENG_TRNG_0_CTRL_2_RESEED_N_MSB 560 +#define REG_SECENG_TRNG_0_CTRL_1_RESEED_N_MSB_VALUE GENMASK(15, 0) + +//se_trng_0_ctrl_3 +#define REG_SECENG_TRNG_0_CTRL_3 564 +#define REG_SECENG_TRNG_0_CTRL_3_ROSC_EN BIT(31) +#define REG_SECENG_TRNG_0_CTRL_3_HT_OD_EN BIT(26) +#define REG_SECENG_TRNG_0_CTRL_3_HT_APT_C GENMASK(25, 16) +#define REG_SECENG_TRNG_0_CTRL_3_HT_RCT_C GENMASK(15, 8) +#define REG_SECENG_TRNG_0_CTRL_3_CP_RATIO GENMASK(7, 0) + +//se_trng_0_test_out_0 +#define REG_SECENG_TRNG_0_TEST_OUT_0 576 + +//se_trng_0_test_out_1 +#define REG_SECENG_TRNG_0_TEST_OUT_1 580 + +//se_trng_0_test_out_2 +#define REG_SECENG_TRNG_0_TEST_OUT_2 584 + +//se_trng_0_test_out_3 +#define REG_SECENG_TRNG_0_TEST_OUT_3 588 + +//se_trng_0_ctrl_prot +#define REG_SECENG_TRNG_0_CTRL_PROT 764 +#define REG_SECENG_TRNG_0_CTRL_PROT_ID1_EN BIT(2) +#define REG_SECENG_TRNG_0_CTRL_PROT_ID0_EN BIT(1) + +//se_pka_0_ctrl_0 +#define REG_SECENG_PKA_0_CTRL_0 768 +#define REG_SECENG_PKA_0_CTRL_0_STATUS GENMASK(31, 16) +#define REG_SECENG_PKA_0_CTRL_0_STATUS_CLR_1T BIT(15) +#define REG_SECENG_PKA_0_CTRL_0_RAM_CLR_MD BIT(13) +#define REG_SECENG_PKA_0_CTRL_0_ENDIAN BIT(12) +#define REG_SECENG_PKA_0_CTRL_0_INT_MASK BIT(11) +#define REG_SECENG_PKA_0_CTRL_0_INT_SET BIT(10) +#define REG_SECENG_PKA_0_CTRL_0_INT_CLR_1T BIT(9) +#define REG_SECENG_PKA_0_CTRL_0_INT BIT(8) +#define REG_SECENG_PKA_0_CTRL_0_PROT_MD GENMASK(7, 4) +#define REG_SECENG_PKA_0_CTRL_0_EN BIT(3) +#define REG_SECENG_PKA_0_CTRL_0_BUSY BIT(2) +#define REG_SECENG_PKA_0_CTRL_0_DONE_CLR_1T BIT(1) +#define REG_SECENG_PKA_0_CTRL_0_DONE BIT(0) + +//se_pka_0_seed +#define REG_SECENG_PKA_0_SEED 780 + +//se_pka_0_ctrl_1 +#define REG_SECENG_PKA_0_CTRL_1 784 +#define REG_SECENG_PKA_0_CTRL_1_HBYPASS BIT(3) +#define REG_SECENG_PKA_0_CTRL_1_HBURST GENMASK(2, 0) + +//se_pka_0_rw +#define REG_SECENG_PKA_0_RW 832 +//This is a confusing register which I have not mapped because +//"BL808 Reference Manual 1.2 EN" seems wrong + +//se_pka_0_rw_burst +#define REG_SECENG_PKA_0_RW_BURST 864 +//This is a confusing register which I have not mapped because +//"BL808 Reference Manual 1.2 EN" seems wrong + +//se_pka_0_ctrl_prot +#define REG_SECENG_PKA_0_CTRL_PROT 1020 +#define REG_SECENG_PKA_0_CTRL_PROT_ID1_EN BIT(2) +#define REG_SECENG_PKA_0_CTRL_PROT_ID0_EN BIT(1) + +//se_cdet_0_ctrl_0 +#define REG_SECENG_CDET_0_CTRL_0 1024 +#define REG_SECENG_CDET_0_CTRL_0_G_LOOP_MIN GENMASK(31, 24) +#define REG_SECENG_CDET_0_CTRL_0_G_LOOP_MAX GENMASK(23, 16) +#define REG_SECENG_CDET_0_CTRL_0_STATUS GENMASK(15, 2) +#define REG_SECENG_CDET_0_CTRL_0_ERROR BIT(1) +#define REG_SECENG_CDET_0_CTRL_0_EN BIT(0) + +//se_cdet_0_ctrl_1 +#define REG_SECENG_CDET_0_CTRL_1 1028 +#define REG_SECENG_CDET_0_CTRL_1_G_SLP_N GENMASK(23, 16) +#define REG_SECENG_CDET_0_CTRL_1_T_DLY_N GENMASK(15, 8) +#define REG_SECENG_CDET_0_CTRL_1_T_LOOP_N GENMASK(7, 0) + +//se_cdet_0_ctrl_prot +#define REG_SECENG_CDET_0_CTRL_PROT 1276 +#define REG_SECENG_CDET_0_CTRL_PROT_ID1_EN BIT(2) +#define REG_SECENG_CDET_0_CTRL_PROT_ID0_EN BIT(1) +#define REG_SECENG_CDET_0_CTRL_PROT_PROT_EN BIT(0) + +//se_gmac_0_ctrl_0 +#define REG_SECENG_GMAC_0_CTRL_0 1280 +#define REG_SECENG_GMAC_0_CTRL_0_X_ENDIAN BIT(14) +#define REG_SECENG_GMAC_0_CTRL_0_H_ENDIAN BIT(13) +#define REG_SECENG_GMAC_0_CTRL_0_T_ENDIAN BIT(12) +#define REG_SECENG_GMAC_0_CTRL_0_INT_MASK BIT(11) +#define REG_SECENG_GMAC_0_CTRL_0_INT_SET_1T BIT(10) +#define REG_SECENG_GMAC_0_CTRL_0_INT_CLR_1T BIT(9) +#define REG_SECENG_GMAC_0_CTRL_0_INT BIT(8) +#define REG_SECENG_GMAC_0_CTRL_0_EN BIT(2) +#define REG_SECENG_GMAC_0_CTRL_0_TRIG_1T BIT(1) +#define REG_SECENG_GMAC_0_CTRL_0_BUSY BIT(0) + +//se_gmac_0_lca +#define REG_SECENG_GMAC_0_LCA 1284 + +//se_gmac_0_status +#define REG_SECENG_GMAC_0_STATUS 1288 + +//se_gmac_0_ctrl_prot +#define REG_SECENG_GMAC_0_CTRL_PROT 1532 +#define REG_SECENG_GMAC_0_CTRL_PROT_ID1_EN BIT(2) +#define REG_SECENG_GMAC_0_CTRL_PROT_ID0_EN BIT(1) + +//se_ctrl_prot_rd +#define REG_SECENG_CTRL_PROT_RD 3840 +#define REG_SECENG_CTRL_PROT_RD_DBG_DIS BIT(31) +#define REG_SECENG_CTRL_PROT_RD_GMAC_ID1_EN_RD BIT(11) +#define REG_SECENG_CTRL_PROT_RD_GMAC_ID0_EN_RD BIT(10) +#define REG_SECENG_CTRL_PROT_RD_CDET_ID1_EN_RD BIT(9) +#define REG_SECENG_CTRL_PROT_RD_CDET_ID0_EN_RD BIT(8) +#define REG_SECENG_CTRL_PROT_RD_PKA_ID1_EN_RD BIT(7) +#define REG_SECENG_CTRL_PROT_RD_PKA_ID0_EN_RD BIT(6) +#define REG_SECENG_CTRL_PROT_RD_TRNG_ID1_EN_RD BIT(5) +#define REG_SECENG_CTRL_PROT_RD_TRNG_ID0_EN_RD BIT(4) +#define REG_SECENG_CTRL_PROT_RD_AES_ID1_EN_RD BIT(3) +#define REG_SECENG_CTRL_PROT_RD_AES_ID0_EN_RD BIT(2) +#define REG_SECENG_CTRL_PROT_RD_SHA_ID1_EN_RD BIT(1) +#define REG_SECENG_CTRL_PROT_RD_SHA_ID0_EN_RD BIT(0) + +struct bflb_seceng { + struct mutex lock; + struct device *dev; + void __iomem *base; + struct regmap *map; + unsigned int initialised; + + struct hwrng hwrng; +}; + +struct bflb_seceng_ctx { + struct bflb_seceng *seceng; +}; + +struct regmap_config bflb_seceng_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .cache_type = REGCACHE_FLAT, + .max_register = 512 * sizeof(u32), + .num_reg_defaults_raw = 512, + .use_relaxed_mmio = true, + .use_raw_spinlock = true, +}; + +static struct bflb_seceng *bflb_seceng_dev; + +static void bflb_seceng_trng_wait_ready(void) +{ + while (readl_relaxed(bflb_seceng_dev->base + REG_SECENG_TRNG_0_CTRL_0) & + REG_SECENG_TRNG_0_CTRL_0_BUSY) { + dev_dbg(bflb_seceng_dev->dev, "Waiting for TRNG ready"); + schedule(); + } +} + +static void bflb_seceng_trng_init(void) +{ + regmap_update_bits(bflb_seceng_dev->map, REG_SECENG_TRNG_0_CTRL_0, + REG_SECENG_TRNG_0_CTRL_0_EN | REG_SECENG_TRNG_0_CTRL_0_INT_CLR_1T, + FIELD_PREP(REG_SECENG_TRNG_0_CTRL_0_EN, 1) | + FIELD_PREP(REG_SECENG_TRNG_0_CTRL_0_INT_CLR_1T, 1)); + + bflb_seceng_trng_wait_ready(); +} + +static void bflb_seceng_trng_refresh(void) +{ + regmap_update_bits(bflb_seceng_dev->map, REG_SECENG_TRNG_0_CTRL_0, + REG_SECENG_TRNG_0_CTRL_0_DOUT_CLR_1T, + FIELD_PREP(REG_SECENG_TRNG_0_CTRL_0_DOUT_CLR_1T, 1)); //Clear DOUT + + bflb_seceng_trng_wait_ready(); + + regmap_update_bits(bflb_seceng_dev->map, REG_SECENG_TRNG_0_CTRL_0, + REG_SECENG_TRNG_0_CTRL_0_DOUT_CLR_1T, + FIELD_PREP(REG_SECENG_TRNG_0_CTRL_0_DOUT_CLR_1T, 0)); //Reset clear DOUT + + regmap_update_bits(bflb_seceng_dev->map, + REG_SECENG_TRNG_0_CTRL_0, REG_SECENG_TRNG_0_CTRL_0_TRIG_1T, + FIELD_PREP(REG_SECENG_TRNG_0_CTRL_0_TRIG_1T, 1)); //Force TRNG refresh + + bflb_seceng_trng_wait_ready(); + + regmap_update_bits(bflb_seceng_dev->map, REG_SECENG_TRNG_0_CTRL_0, + REG_SECENG_TRNG_0_CTRL_0_INT_CLR_1T, + FIELD_PREP(REG_SECENG_TRNG_0_CTRL_0_INT_CLR_1T, 1)); //Clear INT + + dev_dbg(bflb_seceng_dev->dev, "Refreshed TRNG"); +} + +static unsigned int bflb_seceng_trng_read_dout(struct bflb_seceng *seceng, + unsigned int doutreg) +{ + return readl_relaxed(seceng->base + REG_SECENG_TRNG_0_DOUT_0 + + (doutreg * 4)); +} + +static inline unsigned int bflb_seceng_trng_read32(void) +{ + static u8 doutreg = 8; + u32 val; + + if (doutreg >= 8) { + //If we have read all available registers (of starting anew), + //refresh them and start again + doutreg = 0; + + bflb_seceng_trng_refresh(); + } + + dev_dbg(bflb_seceng_dev->dev, "Selected TRNG DOUT register %u", doutreg); + + //Read selected register + val = bflb_seceng_trng_read_dout(bflb_seceng_dev, doutreg); + doutreg++; //Move on to next register + + dev_dbg(bflb_seceng_dev->dev, "TRNG DOUT register produced %u", val); + + return val; +} + +static inline u8 bflb_seceng_trng_read8(void) +{ + static unsigned int lastread; + static u8 shift = 4; + + if (shift == 4) { + shift = 0; + + lastread = bflb_seceng_trng_read32(); + } + + return (lastread >> (shift++ * 8)) & 0xFF; +} + +static void bflb_seceng_trng_fill_buffer(u8 *buff, unsigned long bufflen) +{ + unsigned long i; + + for (i = 0; i < bufflen; i++) + buff[i] = bflb_seceng_trng_read8(); +} + +static int bflb_seceng_trng_hwrng_read(struct hwrng *rng, void *data, + size_t max, bool wait) +{ + //Currently ignoring wait + + mutex_lock(&bflb_seceng_dev->lock); + + dev_dbg(bflb_seceng_dev->dev, "Starting TRNG hwrng read for %lu bytes...", + max); + + bflb_seceng_trng_fill_buffer(data, max); + + mutex_unlock(&bflb_seceng_dev->lock); + + //We're always going to fill the buffer, so just return what was asked for + return max; +} + +static int bflb_seceng_trng_crypto_generate(struct crypto_rng *tfm, + const u8 *src, unsigned int slen, u8 *dstn, unsigned int dlen) +{ + struct bflb_seceng_ctx *ctx = crypto_rng_ctx(tfm); + struct bflb_seceng *seceng = ctx->seceng; + + mutex_lock(&seceng->lock); + + dev_dbg(seceng->dev, + "Starting TRNG crypto buffer filling read for %u bytes...", dlen); + + bflb_seceng_trng_fill_buffer(dstn, dlen); + + mutex_unlock(&seceng->lock); + + return 0; +} + +static int bflb_seceng_trng_crypto_seed(struct crypto_rng *tfm, const u8 *seed, + unsigned int slen) +{ + return 0; +} + +static int bflb_seceng_trng_crypto_init(struct crypto_tfm *tfm) +{ + struct bflb_seceng_ctx *ctx = crypto_tfm_ctx(tfm); + + ctx->seceng = bflb_seceng_dev; + + //Skip actual initialisation of the hardware if we have already done so + if (bflb_seceng_dev->initialised) + return 0; + + bflb_seceng_trng_init(); + + dev_dbg(bflb_seceng_dev->dev, "Initialised TRNG via crypto"); + + bflb_seceng_dev->initialised = 1; + return 0; +} + +static int bflb_seceng_trng_hwrng_init(struct hwrng *rng) +{ + if (bflb_seceng_dev->initialised) + return 0; + + bflb_seceng_trng_init(); + + dev_dbg(bflb_seceng_dev->dev, "Initialised TRNG via hwrng"); + + bflb_seceng_dev->initialised = 1; + return 0; +} + +static struct rng_alg bflb_seceng_trng_alg = { + .generate = bflb_seceng_trng_crypto_generate, + .seed = bflb_seceng_trng_crypto_seed, + .seedsize = 0, + .base = { + .cra_name = "stdrng", + .cra_driver_name = "bflb-seceng", + .cra_flags = CRYPTO_ALG_TYPE_RNG, + .cra_priority = 300, + .cra_ctxsize = sizeof(struct bflb_seceng_ctx), + .cra_module = THIS_MODULE, + .cra_init = bflb_seceng_trng_crypto_init, + } +}; + +static struct hwrng bflb_hwrng = { + .name = "bflb-seceng", + .init = bflb_seceng_trng_hwrng_init, + .read = bflb_seceng_trng_hwrng_read, +}; + +static int bflb_seceng_probe(struct platform_device *pdev) +{ + struct bflb_seceng *seceng; + int ret; + + seceng = devm_kzalloc(&pdev->dev, sizeof(*seceng), GFP_KERNEL); + + if (!seceng) + return -ENOMEM; + + seceng->dev = &pdev->dev; + + platform_set_drvdata(pdev, seceng); + mutex_init(&seceng->lock); + + seceng->base = devm_platform_ioremap_resource(pdev, 0); + + if (IS_ERR(seceng->base)) + return PTR_ERR(seceng->base); + + seceng->map = devm_regmap_init_mmio(&pdev->dev, seceng->base, + &bflb_seceng_regmap_config); + + if (IS_ERR(seceng->map)) + return dev_err_probe(&pdev->dev, PTR_ERR(seceng->map), + "Failed to create regmap\n"); + + bflb_seceng_dev = seceng; //Assign driver static + + ret = crypto_register_rng(&bflb_seceng_trng_alg); + + if (ret) + dev_err_probe(&pdev->dev, ret, + "Failed to register as a crypto random number generator\n"); + + seceng->hwrng = bflb_hwrng; + + ret = hwrng_register(&seceng->hwrng); + + if (ret) + dev_err_probe(&pdev->dev, ret, + "Failed to register as a hardware random number generator\n"); + + dev_info(&pdev->dev, "Bouffalo Lab Secure Engine"); + + return ret; +} + +static int bflb_seceng_remove(struct platform_device *pdev) +{ + hwrng_unregister(&bflb_seceng_dev->hwrng); + crypto_unregister_rng(&bflb_seceng_trng_alg); + + bflb_seceng_dev = NULL; + + return 0; +} + +static const struct of_device_id __maybe_unused bflb_seceng_of_match[] = { + { .compatible = "bflb,seceng", .data = (const void *)0, }, + {} +}; +MODULE_DEVICE_TABLE(of, bflb_seceng_of_match); + +static struct platform_driver bflb_seceng_driver = { + .probe = bflb_seceng_probe, + .remove = bflb_seceng_remove, + .driver = { + .name = "bflb-seceng", + .of_match_table = bflb_seceng_of_match, + .suppress_bind_attrs = true, + } +}; +module_platform_driver(bflb_seceng_driver); + +MODULE_DESCRIPTION("Bouffalo BL808 Secure Engine driver"); +MODULE_AUTHOR("Alexander Horner "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 1495965bc394c0..c215da153fd7be 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -295,4 +295,13 @@ config QCOM_IPCC acts as an interrupt controller for receiving interrupts from clients. Say Y here if you want to build this driver. +config BFLB_IPC + tristate "Bouffalo Lab IPC driver" + depends on OF + help + (IPC) driver for BL808 devices. The driver provides mailbox support for + sending interrupts to the clients. On the other hand, the driver also + acts as an interrupt controller for receiving interrupts from clients. + Say Y here if you want to build this driver. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index fc93761171113e..02bba5d03d4b2c 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -62,3 +62,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o + +obj-$(CONFIG_BFLB_IPC) += bflb-ipc.o diff --git a/drivers/mailbox/bflb-ipc.c b/drivers/mailbox/bflb-ipc.c new file mode 100644 index 00000000000000..a91622fa610e15 --- /dev/null +++ b/drivers/mailbox/bflb-ipc.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Allen Martin + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* IPC Register offsets */ +#define IPC_REG_ISWR 0x00 /* Interrupt Set Write Register */ +#define IPC_REG_IRSRR 0x04 /* Interrupt raw status Register */ +#define IPC_REG_ICR 0x08 /* Interrupt Clear Register */ +#define IPC_REG_IUSR 0x0c /* Interrupt Unmask Set Register */ +#define IPC_REG_IUCR 0x10 /* Interrupt Unmask Clear Register */ +#define IPC_REG_ILSLR 0x14 /* Interrupt Line Sel Low Register */ +#define IPC_REG_ILSHR 0x18 /* Interrupt Line Sel High Register */ +#define IPC_REG_ISR 0x1c /* Interrupt status Register */ + +/** + * struct bflb_ipc_chan_info - Per-mailbox-channel info + * @cpu_id: The cpu_id is a identifier of what CPU is being called + * @service_id: The service_id is a identifier of what service is being called on the remote CPU + * @op_id: The operation we want to execute on the remote CPU + */ +struct bflb_ipc_chan_info { + u8 cpu_id; + u16 service_id; + u16 op_id; +}; + +/** + * struct bflb_ipc - Holder for the mailbox driver + * @dev: Device associated with this instance + * @base: Base address of each IPC frame (LP, M0) + * @irq_domain: The irq_domain associated with this instance + * @chans: The mailbox channels array + * @outchat: The outbound (singal the other CPU) mailbox channel info + * @inchan: The inbound (receive the signal from the other CPU) mailbox channel info + * @mbox: The mailbox controller + * @num_chans: Number of @chans elements + * @irq: Summary irq + */ +struct bflb_ipc { + struct device *dev; + void __iomem *base[4]; + struct irq_domain *irq_domain; + struct mbox_chan *chans; + struct bflb_ipc_chan_info *mchan; + struct mbox_controller mbox; + int num_chans; + int irq; +}; + +static inline struct bflb_ipc *to_bflb_ipc(struct mbox_controller *mbox) +{ + return container_of(mbox, struct bflb_ipc, mbox); +} + +static inline u32 bflb_ipc_get_hwirq(u16 source, u16 device) +{ +// dev_dbg("%s: source: %u, device: %u\n", __func__, source, device); + + return device; +} + +#if 0 +static void bflb_ipc_dump_regs(struct bflb_ipc *ipc) +{ + int i; + + for (i = 0; i < 4; i++) { + dev_dbg(ipc->dev, "base %px %d\n", ipc->base[i], i); + dev_dbg(ipc->dev, "ISWR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ISWR)); + dev_dbg(ipc->dev, "IRSRR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_IRSRR)); + dev_dbg(ipc->dev, "ICR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ICR)); + dev_dbg(ipc->dev, "IUSR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_IUSR)); + dev_dbg(ipc->dev, "IUCR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_IUCR)); + dev_dbg(ipc->dev, "ILSLR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ILSLR)); + dev_dbg(ipc->dev, "ILSHR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ILSHR)); + dev_dbg(ipc->dev, "ISR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ISR)); + } +} +#endif + +struct mbox_chan *bflb_mbox_find_chan(struct bflb_ipc *ipc, struct bflb_ipc_chan_info *chaninfo) +{ + struct bflb_ipc_chan_info *mchan; + struct mbox_controller *mboxctlr = &ipc->mboxctlr; + struct mbox_chan *chan; + struct device *dev; + int chan_id; + + dev = ipc->dev; + + for (chan_id = 0; chan_id < mboxctlr->num_chans; chan_id++) { + chan = &ipc->chans[chan_id]; + mchan = chan->con_priv; + + if (!mchan) + break; + else if (mchan->cpu_id == chaninfo->cpu_id && + mchan->service_id == chaninfo->service_id && + mchan->op_id == chaninfo->op_id) + return chan; + } + dev_err(dev, "%s: No channel found for cpu_id %d service_id %d op_id %d", + __func__, chaninfo->cpu_id, chaninfo->service_id, chaninfo->op_id); + + return ERR_PTR(-EINVAL); + +} + +/* called when we get a RX interrupt from another processor + * this indicates there is a message waitinf for us in + * IPC_REG_ILSHR and IPC_REG_ILSLR registers to process + */ +static void bflb_mbox_rx_irq_fn(int from_cpu, struct bflb_ipc *ipc) +{ + struct mbox_chan *chan; + struct bflb_ipc_chan_info bflbchan; + struct bflb_mbox_msg msg; + u32 sig_op; + + /* update this when we support LP */ + sig_op = readl(ipc->base[1] + IPC_REG_ILSHR); + msg.param = readl(ipc->base[1] + IPC_REG_ILSLR); + + WARN_ON(sig_op == 0); + + bflbchan.cpu_id = from_cpu; + bflbchan.service_id = (sig_op >> 16) & 0xFFFF; + bflbchan.op_id = sig_op & 0xFFFF; + + chan = bflb_mbox_find_chan(ipc, &bflbchan); + if (IS_ERR(chan)) { + dev_err(ipc->dev, "no channel for signal cpu_id: %d service: %d op: %d\r\n", bflbchan.cpu_id, bflbchan.service_id, bflbchan.op_id); + return; + } + + dev_dbg(ipc->dev, "Got MBOX Signal cpu: %d service %d op %d param %x\r\n", bflbchan.cpu_id, bflbchan.service_id, bflbchan.op_id, msg.param); + + mbox_chan_received_data(chan, &msg); +} + +/* called when we get a interupt back on our TX IRQ. + * This is a EOI interupt + */ +static void bflb_mbox_tx_irq_fn(u8 from_cpu, struct bflb_ipc *ipc) +{ + struct mbox_chan *chan; + struct bflb_ipc_chan_info bflbchan; + + u32 sig_op = readl(ipc->base[2] + IPC_REG_ILSHR); + u32 param = readl(ipc->base[2] + IPC_REG_ILSLR); + + bflbchan.cpu_id = from_cpu; + bflbchan.service_id = (sig_op >> 16) & 0xFFFF; + bflbchan.op_id = sig_op & 0xFFFF; + + chan = bflb_mbox_find_chan(ipc, &bflbchan); + if (IS_ERR(chan)) { + dev_err(ipc->dev, "no channel for EOI signal cpu_id: %d service: %d op: %d Param: %d", bflbchan.cpu_id, bflbchan.service_id, bflbchan.op_id, param); + return; + } + + dev_dbg(ipc->dev, "Got MBOX EOI Signal cpu: %d service %d op %d Param: %d", bflbchan.cpu_id, bflbchan.service_id, bflbchan.op_id, param); + + /* clear the IPC_REG_ILSLR and IPC_REG_ILSHR */ + writel(0, ipc->base[2] + IPC_REG_ILSLR); + writel(0, ipc->base[2] + IPC_REG_ILSHR); + + mbox_chan_txdone(chan, 0); +} + +static irqreturn_t bflb_ipc_irq_fn(int irq, void *data) +{ + struct bflb_ipc *ipc = data; + unsigned long stat; + int pos; + + stat = readl(ipc->base[1] + IPC_REG_ISR); + + for_each_set_bit(pos, &stat, 32) { + if (pos == BFLB_IPC_DEVICE_MBOX_RX) + bflb_mbox_rx_irq_fn(BFLB_IPC_SOURCE_M0, ipc); + else if (pos == BFLB_IPC_DEVICE_MBOX_TX) + /* we use target here as its a EOI from a send */ + bflb_mbox_tx_irq_fn(BFLB_IPC_TARGET_M0, ipc); + else + generic_handle_domain_irq(ipc->irq_domain, pos); + } + writel(stat, ipc->base[1] + IPC_REG_ICR); + + /* Signal EOI to the other processes except when we recieve a EOI ourselves */ + if (stat != (1 << BFLB_IPC_DEVICE_MBOX_TX)) + writel(stat, ipc->base[2] + IPC_REG_ISWR); + + return IRQ_HANDLED; +} + +static void bflb_ipc_mask_irq(struct irq_data *irqd) +{ + struct bflb_ipc *ipc = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t hwirq = irqd_to_hwirq(irqd); + + writel(BIT(hwirq), ipc->base[1] + IPC_REG_IUCR); +} + +static void bflb_ipc_unmask_irq(struct irq_data *irqd) +{ + struct bflb_ipc *ipc = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t hwirq = irqd_to_hwirq(irqd); + + writel(BIT(hwirq), ipc->base[1] + IPC_REG_IUSR); +} + +static struct irq_chip bflb_ipc_irq_chip = { + .name = "BFLB MBOXIC", + .irq_mask = bflb_ipc_mask_irq, + .irq_unmask = bflb_ipc_unmask_irq, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +static int bflb_ipc_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct bflb_ipc *ipc = d->host_data; + + irq_set_chip_and_handler(irq, &bflb_ipc_irq_chip, handle_level_irq); + irq_set_chip_data(irq, ipc); + irq_set_noprobe(irq); + + return 0; +} + +static int bflb_ipc_domain_xlate(struct irq_domain *d, + struct device_node *node, const u32 *intspec, + unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (intsize != 3) + return -EINVAL; + + *out_hwirq = bflb_ipc_get_hwirq(intspec[0], intspec[1]); + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static const struct irq_domain_ops bflb_ipc_irq_ops = { + .map = bflb_ipc_domain_map, + .xlate = bflb_ipc_domain_xlate, +}; + +#if 0 +/* Shouldn't actually be fail as we clear the High/Low registers in a EOI + * but this protects if we screw up our mailbox handling + */ +static bool bflb_ipc_mbox_can_send(struct mbox_chan *chan) +{ + struct bflb_ipc *ipc = to_bflb_ipc(chan->mbox); + + /* check the low register first as we clear that last in our EOI, so this + * should protected to a limited extent + */ + u32 mbox_low = readl(ipc->base[2] + IPC_REG_ILSLR); + u32 mbox_high = readl(ipc->base[2] + IPC_REG_ILSHR); + + + if (mbox_low | mbox_high) + dev_warn_ratelimited(ipc->dev, "%s: low: 0x%08x high: 0x%08x\r\n", __func__, mbox_low, mbox_high); + + writel(0, ipc->base[2] + IPC_REG_ILSLR); + writel(0, ipc->base[2] + IPC_REG_ILSHR); + + return !(mbox_low | mbox_high); +} +#endif + +static int bflb_ipc_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct bflb_ipc *ipc = to_bflb_ipc(chan->mbox); + struct bflb_ipc_chan_info *mchan = chan->con_priv; + struct bflb_mbox_msg *msg = data; + u32 tmpVal = (mchan->service_id << 16) | (mchan->op_id & 0xFFFF); + +#if 0 + if (!bflb_ipc_mbox_can_send(chan)) + return -EBUSY; +#endif + + dev_dbg(ipc->dev, "%s %d: cpu: %d singal: %d op: %d (0x%x) param: %d", __func__, msg->id, mchan->cpu_id, mchan->service_id, mchan->op_id, tmpVal, msg->param); + spin_lock(&ipc->tx_lock); + // /* write our signal number to high register */ + writel(tmpVal, ipc->base[2] + IPC_REG_ILSHR); + // /* write our data to low register */ + writel(msg->param, ipc->base[2] + IPC_REG_ILSLR); + + /* and now kick the remote processor */ + writel((1 << BFLB_IPC_DEVICE_MBOX_TX), ipc->base[2] + IPC_REG_ISWR); + spin_unlock(&ipc->tx_lock); + dev_dbg(ipc->dev, "%s %d: done param: %d", __func__, msg->id, msg->param); + return 0; +} + +static void bflb_ipc_mbox_shutdown(struct mbox_chan *chan) +{ + struct bflb_ipc *ipc = to_bflb_ipc(chan->mbox); + + dev_dbg(ipc->dev, "%s\n", __func__); + chan->con_priv = NULL; +} + +static struct mbox_chan *bflb_ipc_mbox_xlate(struct mbox_controller *mboxctlr, + const struct of_phandle_args *ph) +{ + struct bflb_ipc *ipc = to_bflb_ipc(mboxctlr); + struct bflb_ipc_chan_info *mchan; + struct mbox_chan *chan; + struct device *dev = ipc->dev; + int chan_id; + + + dev_dbg(dev, "%s\n", __func__); + + if (ph->args_count != 3) { + dev_err(dev, "invalid number of arguments"); + return ERR_PTR(-EINVAL); + } + + for (chan_id = 0; chan_id < mboxctlr->num_chans; chan_id++) { + chan = &ipc->chans[chan_id]; + mchan = chan->con_priv; + + if (!mchan) + break; + else if (mchan->cpu_id == ph->args[0] && + mchan->service_id == ph->args[1] && + mchan->op_id == ph->args[2]) { + dev_err(dev, "channel already in use %d %d %d", ph->args[0], ph->args[1], ph->args[2]); + return ERR_PTR(-EBUSY); + } + } + + if (chan_id >= mboxctlr->num_chans) { + dev_err(dev, "no free channels"); + return ERR_PTR(-EBUSY); + } + + mchan = devm_kzalloc(dev, sizeof(*mchan), GFP_KERNEL); + if (!mchan) + return ERR_PTR(-ENOMEM); + + mchan->cpu_id = ph->args[0]; + mchan->service_id = ph->args[1]; + mchan->op_id = ph->args[2]; + chan->con_priv = mchan; + + dev_dbg(dev, "%s mbox %s: %d cpu: %d service: %d op: %d", __func__, ph->np->full_name, chan_id, mchan->cpu_id, mchan->service_id, mchan->op_id); + + return chan; +} + + +static const struct mbox_chan_ops ipc_mbox_chan_ops = { + .send_data = bflb_ipc_mbox_send_data, + .shutdown = bflb_ipc_mbox_shutdown, +// .last_tx_done = bflb_ipc_mbox_can_send, +}; + +static int bflb_ipc_setup_mbox(struct bflb_ipc *ipc, + struct device_node *controller_dn) +{ + struct of_phandle_args curr_ph; + struct device_node *client_dn; + struct mbox_controller *mboxctlr; + struct device *dev = ipc->dev; + int i, j, ret; + + /* + * Find out the number of clients interested in this mailbox + * and create channels accordingly. + */ + ipc->num_chans = 0; + for_each_node_with_property(client_dn, "mboxes") { + if (!of_device_is_available(client_dn)) + continue; + i = of_count_phandle_with_args(client_dn, + "mboxes", "#mbox-cells"); + for (j = 0; j < i; j++) { + ret = of_parse_phandle_with_args(client_dn, "mboxes", + "#mbox-cells", j, &curr_ph); + of_node_put(curr_ph.np); + if (!ret && curr_ph.np == controller_dn) { + ipc->num_chans++; + //break; + } + } + } + dev_dbg(dev, "%s: num_chans: %d", __func__, ipc->num_chans); + /* If no clients are found, skip registering as a mbox controller */ + if (!ipc->num_chans) + return 0; + + ipc->chans = devm_kcalloc(dev, ipc->num_chans, + sizeof(struct mbox_chan), GFP_KERNEL); + if (!ipc->chans) + return -ENOMEM; + + mboxctlr = &ipc->mboxctlr; + mboxctlr->dev = dev; + mboxctlr->num_chans = ipc->num_chans; + mboxctlr->chans = ipc->chans; + mboxctlr->ops = &ipc_mbox_chan_ops; + mboxctlr->of_xlate = bflb_ipc_mbox_xlate; + mboxctlr->txdone_irq = true; +// mboxctlr->txdone_poll = false; + + + spin_lock(&ipc->tx_lock); + + + /* clear the IPC_REG_ILSLR and IPC_REG_ILSHR */ + writel(0, ipc->base[2] + IPC_REG_ILSLR); + writel(0, ipc->base[2] + IPC_REG_ILSHR); + + /* unmask our interupt */ + writel(BIT(BFLB_IPC_DEVICE_MBOX_TX), ipc->base[1] + IPC_REG_IUSR); + writel(BIT(BFLB_IPC_DEVICE_MBOX_RX), ipc->base[1] + IPC_REG_IUSR); + + spin_unlock(&ipc->tx_lock); + + + return devm_mbox_controller_register(dev, mboxctlr); +} + +static int bflb_ipc_pm_resume(struct device *dev) +{ + return 0; +} + +static int bflb_ipc_probe(struct platform_device *pdev) +{ + struct bflb_ipc *ipc; + static int id; + int i; + char *name; + int ret; + + ipc = devm_kzalloc(&pdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + + ipc->dev = &pdev->dev; + + for (i = 0; i < 4; i++) { + ipc->base[i] = devm_platform_ioremap_resource(pdev, i); + if (IS_ERR(ipc->base[i])) + return PTR_ERR(ipc->base[i]); + } + + ipc->irq = platform_get_irq(pdev, 0); + if (ipc->irq < 0) + return ipc->irq; + + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "mboxic%d", id++); + if (!name) + return -ENOMEM; + + ipc->irq_domain = irq_domain_add_tree(pdev->dev.of_node, + &bflb_ipc_irq_ops, ipc); + if (!ipc->irq_domain) + return -ENOMEM; + + ret = bflb_ipc_setup_mbox(ipc, pdev->dev.of_node); + if (ret) + goto err_mbox; + + spin_lock_init(&ipc->tx_lock); + + ret = devm_request_irq(&pdev->dev, ipc->irq, bflb_ipc_irq_fn, + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | + IRQF_NO_THREAD, name, ipc); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); + goto err_req_irq; + } + + platform_set_drvdata(pdev, ipc); + + dev_info(&pdev->dev, "Bouffalo Lab IPC mailbox interrupt controller"); + return 0; + +err_req_irq: + if (ipc->num_chans) + mbox_controller_unregister(&ipc->mboxctlr); +err_mbox: + irq_domain_remove(ipc->irq_domain); + + return ret; +} + +static int bflb_ipc_remove(struct platform_device *pdev) +{ + struct bflb_ipc *ipc = platform_get_drvdata(pdev); + + disable_irq_wake(ipc->irq); + irq_domain_remove(ipc->irq_domain); + + return 0; +} + +static const struct of_device_id bflb_ipc_of_match[] = { + { .compatible = "bflb,bl808-ipc"}, + {} +}; +MODULE_DEVICE_TABLE(of, bflb_ipc_of_match); + +static const struct dev_pm_ops bflb_ipc_dev_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, bflb_ipc_pm_resume) +}; + +static struct platform_driver bflb_ipc_driver = { + .probe = bflb_ipc_probe, + .remove = bflb_ipc_remove, + .driver = { + .name = "bflb-ipc", + .of_match_table = bflb_ipc_of_match, + .suppress_bind_attrs = true, + .pm = pm_sleep_ptr(&bflb_ipc_dev_pm_ops), + }, +}; + +static int __init bflb_ipc_init(void) +{ + return platform_driver_register(&bflb_ipc_driver); +} +arch_initcall(bflb_ipc_init); + +MODULE_AUTHOR("Allen Martin "); +MODULE_DESCRIPTION("Bouffalo Lab IPC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 5e19a961c34d7b..f3c4654bb376e0 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -413,6 +413,20 @@ config MMC_SDHCI_F_SDH30 If unsure, say N. +config MMC_SDHCI_BFLB + tristate "SDHCI support on Bouffalo Lab BL808 SoC" + depends on MMC_SDHCI_PLTFM + depends on OF + depends on COMMON_CLK + select MMC_SDHCI_IO_ACCESSORS + help + This selects the Secure Digital Host Controller Interface in + Bouffalo Lab BL808 SoC. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_SDHCI_MILBEAUT tristate "SDHCI support for Socionext Milbeaut Serieas using F_SDH30" depends on MMC_SDHCI_PLTFM diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index ba0c6d0cd85d7a..626875dfb629a3 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -103,6 +103,7 @@ cqhci-y += cqhci-core.o cqhci-$(CONFIG_MMC_CRYPTO) += cqhci-crypto.o obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o obj-$(CONFIG_MMC_LITEX) += litex_mmc.o +obj-$(CONFIG_MMC_SDHCI_BFLB) += sdhci-bflb.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/sdhci-bflb.c b/drivers/mmc/host/sdhci-bflb.c new file mode 100644 index 00000000000000..1e590a151c04e5 --- /dev/null +++ b/drivers/mmc/host/sdhci-bflb.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "sdhci-pltfm.h" + +static u16 sdhci_bflb_readw(struct sdhci_host *host, int reg) +{ + u16 ret; + + switch (reg) { + case SDHCI_HOST_VERSION: + case SDHCI_SLOT_INT_STATUS: + /* those registers don't exist */ + return 0; + default: + ret = readw(host->ioaddr + reg); + } + return ret; +} + +static u32 sdhci_bflb_readl(struct sdhci_host *host, int reg) +{ + u32 ret; + + ret = readl(host->ioaddr + reg); + + switch (reg) { + case SDHCI_CAPABILITIES: + /* Mask the support for 3.0V */ + ret &= ~SDHCI_CAN_VDD_300; + break; + } + return ret; +} + +static const struct sdhci_ops sdhci_bflb_ops = { + .read_w = sdhci_bflb_readw, + .read_l = sdhci_bflb_readl, + .set_clock = sdhci_set_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, +}; + +static const struct sdhci_pltfm_data sdhci_bflb_pdata = { + .ops = &sdhci_bflb_ops, + .quirks = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER | + SDHCI_QUIRK_NO_BUSY_IRQ | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_INVERTED_WRITE_PROTECT, +}; + +static int sdhci_bflb_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + int ret; + + host = sdhci_pltfm_init(pdev, &sdhci_bflb_pdata, 0); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + pltfm_host->clk = devm_clk_get(&pdev->dev, NULL); + + if (!IS_ERR(pltfm_host->clk)) + clk_prepare_enable(pltfm_host->clk); + + ret = mmc_of_parse(host->mmc); + if (ret) + goto err_sdhci_add; + + ret = sdhci_add_host(host); + if (ret) + goto err_sdhci_add; + + return 0; + +err_sdhci_add: + clk_disable_unprepare(pltfm_host->clk); + sdhci_pltfm_free(pdev); + return ret; +} + +static const struct of_device_id sdhci_bflb_of_match_table[] = { + { .compatible = "bflb,bl808-sdhci", }, + {} +}; +MODULE_DEVICE_TABLE(of, sdhci_bflb_of_match_table); + +static struct platform_driver sdhci_bflb_driver = { + .driver = { + .name = "sdhci-bflb", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = &sdhci_pltfm_pmops, + .of_match_table = sdhci_bflb_of_match_table, + }, + .probe = sdhci_bflb_probe, + .remove = sdhci_pltfm_unregister, +}; + +module_platform_driver(sdhci_bflb_driver); + +MODULE_DESCRIPTION("SDHCI driver for Bflb"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 9e63b8c43f3e2e..9dce0d2a42df9f 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -416,6 +416,12 @@ config VIRTIO_NET This is the virtual network driver for virtio. It can be used with QEMU based VMMs (like KVM or Xen). Say Y or M. +config RPMSG_NET + tristate "RPMSG network driver" + depends on RPMSG + help + This is a virtual network driver for rpmsg bus. + config NLMON tristate "Virtual netlink monitoring device" help diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 6ce076462dbfd2..5cbd5ffc870637 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -89,3 +89,4 @@ thunderbolt-net-y += thunderbolt.o obj-$(CONFIG_USB4_NET) += thunderbolt-net.o obj-$(CONFIG_NETDEVSIM) += netdevsim/ obj-$(CONFIG_NET_FAILOVER) += net_failover.o +obj-$(CONFIG_RPMSG_NET) += rpmsg-net.o diff --git a/drivers/net/ethernet/ethoc.c b/drivers/net/ethernet/ethoc.c index 95cbad198b4b6e..49f2e71d9555ea 100644 --- a/drivers/net/ethernet/ethoc.c +++ b/drivers/net/ethernet/ethoc.c @@ -70,6 +70,7 @@ MODULE_PARM_DESC(buffer_size, "DMA buffer allocation size"); #define MODER_HUGE (1 << 14) /* huge packets enable */ #define MODER_PAD (1 << 15) /* padding enabled */ #define MODER_RSM (1 << 16) /* receive small packets */ +#define MODER_RMII (1 << 17) /* RMII mode enable */ /* interrupt source and mask registers */ #define INT_MASK_TXF (1 << 0) /* transmit frame */ @@ -138,6 +139,7 @@ MODULE_PARM_DESC(buffer_size, "DMA buffer allocation size"); #define TX_BD_RETRY_MASK (0x00f0) #define TX_BD_RETRY(x) (((x) & 0x00f0) >> 4) #define TX_BD_UR (1 << 8) /* transmitter underrun */ +#define TX_BD_EOF (1 << 10) /* End of Frame */ #define TX_BD_CRC (1 << 11) /* TX CRC enable */ #define TX_BD_PAD (1 << 12) /* pad enable for short packets */ #define TX_BD_WRAP (1 << 13) @@ -269,14 +271,14 @@ static inline void ethoc_write_bd(struct ethoc *dev, int index, static inline void ethoc_enable_irq(struct ethoc *dev, u32 mask) { u32 imask = ethoc_read(dev, INT_MASK); - imask |= mask; + imask &= ~mask; ethoc_write(dev, INT_MASK, imask); } static inline void ethoc_disable_irq(struct ethoc *dev, u32 mask) { u32 imask = ethoc_read(dev, INT_MASK); - imask &= ~mask; + imask |= mask; ethoc_write(dev, INT_MASK, imask); } @@ -355,7 +357,7 @@ static int ethoc_reset(struct ethoc *dev) /* enable FCS generation and automatic padding */ mode = ethoc_read(dev, MODER); - mode |= MODER_CRC | MODER_PAD; + mode |= MODER_CRC | MODER_PAD | MODER_RMII; ethoc_write(dev, MODER, mode); /* set full-duplex mode */ @@ -564,7 +566,7 @@ static irqreturn_t ethoc_interrupt(int irq, void *dev_id) */ mask = ethoc_read(priv, INT_MASK); pending = ethoc_read(priv, INT_SOURCE); - pending &= mask; + pending &= ~mask; if (unlikely(pending == 0)) return IRQ_NONE; @@ -907,8 +909,8 @@ static netdev_tx_t ethoc_start_xmit(struct sk_buff *skb, struct net_device *dev) dest = priv->vma[entry]; memcpy_toio(dest, skb->data, skb->len); - bd.stat &= ~(TX_BD_STATS | TX_BD_LEN_MASK); - bd.stat |= TX_BD_LEN(skb->len); + bd.stat &= ~(TX_BD_STATS | TX_BD_EOF | TX_BD_LEN_MASK); + bd.stat |= TX_BD_LEN(skb->len) | TX_BD_EOF; ethoc_write_bd(priv, entry, &bd); bd.stat |= TX_BD_READY; @@ -1181,7 +1183,7 @@ static int ethoc_probe(struct platform_device *pdev) } } if (eth_clkfreq) { - u32 clkdiv = MIIMODER_CLKDIV(eth_clkfreq / 2500000 + 1); + u32 clkdiv = MIIMODER_CLKDIV(eth_clkfreq / 1000000 + 1); if (!clkdiv) clkdiv = 2; diff --git a/drivers/net/rpmsg-net.c b/drivers/net/rpmsg-net.c new file mode 100644 index 00000000000000..b3d773a66a8414 --- /dev/null +++ b/drivers/net/rpmsg-net.c @@ -0,0 +1,626 @@ +/** + * @file rpmsg_net.c + * + * @brief RPMsg Network Driver + * + * This Linux kernel module exchanges Ethernet frames over the RPMsg bus, providing a virtual + * point-to-point network connection between two cores. The virtual network interface is named + * rpmsgN, where N starts at 0. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("James Tavares "); +MODULE_DESCRIPTION("point-to-point network interface over RPMsg bus"); + +// Module parameters +static int debug = 0; +module_param(debug, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(debug, "debug level, with 0 = none, 1 = rx/tx events, 2 = packet payload, 3 = insane"); + +enum DBG_LEVEL { + DBG_NONE, + DBG_RX_TX, + DBG_PACKET, + DBG_INSANE, +}; + +// Defines + +// Linux's RPMsg stack doesn't export MAX_RPMSG_BUF_SIZE or sizeof(rpmsg_hdr), +// so we need to hard code it here. Ugh. Keep this in sync with RPMsg implementation! +// NOTE: we have modified the kernel's RPMsg stack to use buffers of 2048 bytes +#define MAX_RPMSG_BUF_SIZE 2048 +#define RPMSG_HEADER_SIZE 16 +#define MAX_RPMSG_PAYLOAD_SIZE (MAX_RPMSG_BUF_SIZE-RPMSG_HEADER_SIZE) + +// The maximum number of simultaneous rpmsg_net instances this driver supports. +#define RPMSG_NET_MAX_DEVICES 8 + +// RPMsg MTU is lesser of the max RPMsg payload size, or 1500 (standard ethernet) +#define RPMSG_NET_L2_MTU (MAX_RPMSG_PAYLOAD_SIZE > 1492 ? 1492 : MAX_RPMSG_PAYLOAD_SIZE) + +// Size of the layer 2 header. For now, we just use full ethernet header. TODO: consider optimizing? +// Note: ETH_HLEN does not include room for a VLAN identifier. We do not support VLANs. +#define RPMSG_NET_L2_HEADER_SIZE ETH_HLEN + +// Checksum size +#define RPMSG_NET_L2_CHECKSUM_SIZE ETH_FCS_LEN + +// Pad the layer 2 header so that the IP header starts on a 16 byte boundary +#define RPMSG_NET_SKB_PADDING_SIZE (16 - (RPMSG_NET_L2_HEADER_SIZE & 0xf)) + +// Prototypes - RPMsg Driver API +static int rpmsg_probe(struct rpmsg_device *rpdev); +static int rpmsg_callback(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src); +static void rpmsg_remove(struct rpmsg_device *rpdev); + +// Prototypes - Network Driver API +static void net_setup(struct net_device *ndev); +static int net_open(struct net_device *ndev); +static int net_close(struct net_device *ndev); +static void net_xmit_delayed_work_handler(struct work_struct *work); +static void net_xmit_work_handler(struct work_struct *work); +static int net_xmit(struct sk_buff *skb, struct net_device *ndev); + +// Prototypes - Module API +static int __init rpmsg_net_module_init(void); +static void __exit rpmsg_net_module_exit(void); + +/** @brief rpmsg_net driver instance state */ +struct rpmsg_net_priv { + /** + * Driver index number. Index into rpmsg_net_device[] array, and basis for `rpmsgN` interface + * name. + */ + int idx; + + /** Pointer to the RPMsg stack device handle */ + struct rpmsg_device *rpdev; + + /** Pointer to the network device stack device handle */ + struct net_device *ndev; + + /** + * A work queue to process net device transmit events (Net->RPMsg) packets in a process context, + * rather than the ISR context provided by the net xmit callback. + */ + struct work_struct immediate; + + /** + * A work queue to provide for net device transmit retries in the event that the RPMsg ring + * buffer is full at the time we attempt to transmit. + */ + struct delayed_work delayed; + + /** The current skb that is being transmitted from net device to RPMsg */ + struct sk_buff *skb; + + /** A boolean indicating that we are retrying a failed skb transmission */ + bool is_delayed; + + /** A lock for the net device transmitter. Used to avoid race condition on shutdown */ + spinlock_t shutdown_lock; + + /** A flag indicating whether the interace is shutdown, or not */ + bool is_shutdown; +}; + +/** + * rpmsg_net driver instance state pointers, by device index + */ +static struct rpmsg_net_priv *rpmsg_net_device[RPMSG_NET_MAX_DEVICES]; + + +////////////////////// +// RPMsg Driver API // +////////////////////// + + +/** Registration table for RPMsg back-end */ +static struct rpmsg_device_id rpmsg_net_id_table[] = { + { .name = "rpmsg-net" }, + { } +}; + +MODULE_DEVICE_TABLE(rpmsg, rpmsg_net_id_table); + +/** RPMsg operations object */ +static struct rpmsg_driver rpmsg_net_client = { + .drv.name = KBUILD_MODNAME, + .id_table = rpmsg_net_id_table, + .probe = rpmsg_probe, + .callback = rpmsg_callback, + .remove = rpmsg_remove, +}; + +/** + * @brief Called by the RPMsg stack upon RPMsg device attachment. Creates and registers the + * corresponding `rpmsgN` virtual network device. + * + * @param rpdev RPMsg device + * + * @return 0 on success, -1 on error + */ +static int rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct net_device *ndev = NULL; + struct rpmsg_net_priv *priv = NULL; + char name[IFNAMSIZ]; + u8 addr[ETH_ALEN]; + int i; + + // find an open rpmsg_net_device slot + for (i = 0; idev, "unable to bind channel 0x%x -> 0x%x: reached maximum rpmsg_net device count of %d\n", + rpdev->src, rpdev->dst, RPMSG_NET_MAX_DEVICES); + // TODO return real error here + return -1; + } + + // allocate the network device + snprintf(name, sizeof(name), "rpmsg%d", i); + + ndev = alloc_netdev(sizeof(*priv), name, NET_NAME_UNKNOWN, net_setup); + if (ndev == NULL) { + dev_err(&rpdev->dev, "unable to bind channel 0x%x -> 0x%x: unable to allocate netdevice\n", + rpdev->src, rpdev->dst); + // TODO return real error here + return -1; + } + + // setup our private area, and let the network driver know of rpmsg driver + priv = netdev_priv(ndev); + memset(priv, 0x00, sizeof(*priv)); + priv->idx = i; + priv->ndev = ndev; + priv->rpdev = rpdev; + INIT_WORK(&priv->immediate, net_xmit_work_handler); + INIT_DELAYED_WORK(&priv->delayed, net_xmit_delayed_work_handler); + priv->skb = NULL; + priv->is_delayed = false; + spin_lock_init(&priv->shutdown_lock); + priv->is_shutdown = false; + + // let RPMsg driver know of the network driver + rpdev->ept->priv = priv; + + addr[0] = 0x00; + addr[1] = 0x01; + addr[2] = 0x00; + addr[3] = 0x01; + addr[4] = 0X08; + addr[5] = 0xff; + eth_hw_addr_set(ndev, addr); + + eth_random_addr(ndev->perm_addr); + + // register the net device + register_netdev(ndev); + + // success + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x bound to interface %s\n", + rpdev->src, rpdev->dst, ndev->name); + + rpmsg_net_device[i] = priv; + + return 0; +} + +/** + * @brief Called when an Ethernet frame has been received from the remote processor. Injects the + * frame into the virtual network interface's input queue. + * + * @param rpdev RPMsg device + * @param data RPMsg payload (incoming Ethernet frame) + * @param len Length of the payload + * @param priv_in Private data (pointer to rpmsg_net_priv instance) + * @param src Source RPMsg address + * + * @return 0 on success, -1 on error + */ +static int rpmsg_callback(struct rpmsg_device *rpdev, void *data, int len, void *priv_in, u32 src) +{ + unsigned int maxLen; + struct rpmsg_net_priv *priv; + struct sk_buff *skb; + + (void)src; + + if (debug >= DBG_INSANE) { + printk(KERN_INFO "rpmsg_callback: in_interrupt:%lu [in_irq:%lu in_softirq:%lu] in_atomic:%u in_task:%u\n", + in_interrupt(), in_irq(), in_softirq(), in_atomic(), in_task()); + } + + if (debug >= DBG_RX_TX) { + dev_info(&rpdev->dev, "recv packet on channel 0x%x -> 0x%x, len:%d\n", + rpdev->src, rpdev->dst, len); + } + + if (debug >= DBG_PACKET) { + // TODO print packet data + } + + if (priv_in == NULL) { + dev_err(&rpdev->dev, "ignoring callback on channel 0x%x -> 0x%x with NULL private pointer\n", + rpdev->src, rpdev->dst); + return -1; + } + + priv = (struct rpmsg_net_priv*)priv_in; + + if (priv->ndev == NULL) { + dev_err(&rpdev->dev, "ignoring callback on channel 0x%x -> 0x%x with no netdev interface\n", + rpdev->src, rpdev->dst); + return -1; + } + + // Sanity check RPMsg payload length vs. maximum-permitted length for our net device + maxLen = RPMSG_NET_L2_HEADER_SIZE + READ_ONCE(priv->ndev->mtu) + RPMSG_NET_L2_CHECKSUM_SIZE; + if (len > maxLen) { + dev_err(&rpdev->dev, "ignoring packet on channel 0x%x -> 0x%x with len %d > %d\n", + rpdev->src, rpdev->dst, len, maxLen); + return -1; + } + + // silenty drop packet if interface is closed + if (netif_queue_stopped(priv->ndev)) { + return 0; + } + + + skb = dev_alloc_skb(RPMSG_NET_SKB_PADDING_SIZE + RPMSG_NET_L2_HEADER_SIZE + RPMSG_NET_L2_MTU); + if (skb == NULL) { + dev_err(&rpdev->dev, "out of memory, dropping packet"); + priv->ndev->stats.rx_errors++; + // TODO stats (dropped rx packet) + return -1; + } + + skb_reserve(skb, RPMSG_NET_SKB_PADDING_SIZE); // Align IP on 16 byte boundary + + skb->dev = priv->ndev; + memcpy(skb_put(skb, len), data, len); + skb->protocol = eth_type_trans(skb, priv->ndev); + + // inject incoming RPMsg frame into the network device's input queue (non-interrupt context) + netif_rx(skb); + + priv->ndev->stats.rx_bytes += skb->len; + priv->ndev->stats.rx_packets++; + + + dev_dbg(&rpdev->dev, "rx done\r\n"); + return 0; +} + +/** + * @brief Called when an RPMsg device has been removed. Shuts down the network interface and + * cleans up resources. + * + * @param rpdev RPMsg device + */ +static void rpmsg_remove(struct rpmsg_device *rpdev) +{ + struct rpmsg_net_priv *priv = (struct rpmsg_net_priv*)rpdev->ept->priv; + unsigned long flags; + + // prevent any in-flight transmissions from being injected into the work queue + spin_lock_irqsave(&priv->shutdown_lock, flags); + priv->is_shutdown = true; + netif_stop_queue(priv->ndev); + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + + // invariant: since is_shutdown=true and we are on other side of critical section, all work has + // already been scheduled, or will not be scheduled (frame dropped). + + // cancel all outstanding scheduled work and/or wait for existing work to finish + cancel_delayed_work_sync(&priv->delayed); + cancel_work_sync(&priv->immediate); + + // invariant: there may still be a lingering net_xmit, but it won't start a work queue, and it + // won't set priv->skb because is_shutdown is true. + + // free skb, in case it was abandoned by a cancelled work queue request + if (priv->skb != NULL) { + dev_consume_skb_any(priv->skb); + priv->skb = NULL; + } + + // remove the net device + unregister_netdev(priv->ndev); + + // clear our local cache so that the index can be reused by subsequent rpmsg_probe + rpmsg_net_device[priv->idx] = NULL; + + dev_info(&rpdev->dev, "removed channel: 0x%x -> 0x%x and unbound from interface rpmsg%d\n", + rpdev->src, rpdev->dst, priv->idx); + + // cleanup + free_netdev(priv->ndev); +} + +//////////////////////// +// Network Driver API // +//////////////////////// + +/** Network device driver operations object */ +static const struct net_device_ops netdev_ops = { + .ndo_open = net_open, + .ndo_stop = net_close, + .ndo_start_xmit = net_xmit, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + //FUTURE: .ndo_get_stats64 = sl_get_stats64, + //FUTURE allow user to alter MTU within allowable range of RPMsg bus +}; + +/** Network device driver header operations object */ +const struct header_ops header_ops = { + .create = eth_header, + .parse = eth_header_parse, + .cache = eth_header_cache, + .cache_update = eth_header_cache_update, +}; + +/** + * @brief Network setup callback. Called by the network stack when we register a new `rpmsgN` + * device. Job is to initialize the network device structure. + * + * @param ndev Network device to be initialized. + */ +static void net_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &netdev_ops; + ndev->header_ops = &header_ops; + ndev->type = ARPHRD_ETHER; + ndev->hard_header_len = RPMSG_NET_L2_HEADER_SIZE; + ndev->min_header_len = RPMSG_NET_L2_HEADER_SIZE; + ndev->mtu = RPMSG_NET_L2_MTU; + //not available in 4.9: ndev->min_mtu = ndev->mtu; //ETH_MIN_MTU; + //not available in 4.9: ndev->max_mtu = ndev->mtu; //ETH_DATA_LEN; + ndev->addr_len = ETH_ALEN; + ndev->tx_queue_len = 5; //DEFAULT_TX_QUEUE_LEN; + // future: consider IFF_POINTTOPOINT and IFF_NOARP flags? + // is there a flag to signify that link is is always up? + ndev->flags = IFF_BROADCAST; + ndev->priv_flags |= IFF_TX_SKB_SHARING; + + // future: add NETIF_F_NO_CSUM feature to disable checksumming + + eth_broadcast_addr(ndev->broadcast); +} + +/** + * @brief Called when the network device is opened. Starts the transmission queue. + * + * @param ndev Network device to open + * + * @return 0 on success, -1 on error + */ +static int net_open(struct net_device *ndev) +{ + netif_start_queue(ndev); + return 0; +} + +/** + * @brief Called when the network device is closed. Stops the transmission queue. + * + * @param ndev Network device to close + * + * @return 0 on success, -1 on error + */ +static int net_close(struct net_device *ndev) +{ + netif_stop_queue(ndev); + return 0; +} + +/** + * @brief Called in workqueue context to re-try a previously failed transmission. Activates the + * `immediate` work queue to perform the actual work. + * + * @param work Work queue we were called on + */ +static void net_xmit_delayed_work_handler(struct work_struct *work) +{ + struct rpmsg_net_priv *priv = container_of(work, struct rpmsg_net_priv, delayed.work); + unsigned long flags; + + spin_lock_irqsave(&priv->shutdown_lock, flags); + if (priv->is_delayed && !priv->is_shutdown) { + schedule_work(&priv->immediate); + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + } else { + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + + dev_info(&priv->rpdev->dev, "delayed work handler skipping kick of immediate due to shutdown request\n"); + } +} + +/** + * @brief Called in workqueue context to attempt transmission of a network device skb. We attempt + * to send the packet via RPMsg bus to the other processor. On failure, we schedule one + * retransmission attempt in the future, before dropping the packet. + * + * @param work Work queue we were called on + */ +static void net_xmit_work_handler(struct work_struct *work) +{ + struct rpmsg_net_priv *priv = container_of(work, struct rpmsg_net_priv, immediate); + unsigned long flags; + int err; + + if (priv->skb == NULL) { + dev_err(&priv->rpdev->dev, "net_xmit_work_handler called with NULL skb\n"); + goto cleanup; + } + + err = rpmsg_trysend(priv->rpdev->ept, priv->skb->data, priv->skb->len); + if (err) { + priv->ndev->stats.tx_errors++; + priv->ndev->stats.tx_aborted_errors++; + if (priv->is_delayed) { + // this is already our second attempt + dev_err(&priv->rpdev->dev, "RPMsg send retry failed with error %d; dropping packet\n", err); + + // normal cleanup + goto cleanup; + } else { + // first attempt failed; attempt retry if not shutdown + spin_lock_irqsave(&priv->shutdown_lock, flags); + if (!priv->is_shutdown) { + priv->is_delayed = true; + // Our goal is to sleep long enough to free at least one (1) packet in the RPMsg + // ring buffer. When HZ is 100 (lowest setting), our minimum resolution is 10ms (1 + // jiffy). On Kestrel-M4, flood ping clocked in at ~600 1400-bytes packets per + // second on an unloaded system, and ~200 packets/sec on a loaded system. So, 10ms + // should give us at least one packet. + schedule_delayed_work(&priv->delayed, (unsigned long)(0.5 + (0.010 * HZ))); + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + + dev_err(&priv->rpdev->dev, "RPMsg send failed with error %d; will retry\n", err); + } else { + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + + dev_info(&priv->rpdev->dev, "skipping RPMsg send retry due to shutdown request\n"); + } + + // leave priv->skb populated, either for retry, or if under shutdown, to be freed by + // rpmsg_remove() + return; + } + } + + priv->ndev->stats.tx_packets++; + priv->ndev->stats.tx_bytes += priv->skb->len; + + + if (debug >= DBG_RX_TX) { + dev_info(&priv->rpdev->dev, "sent packet on channel 0x%x -> 0x%x, len:%d\n", + priv->rpdev->src, priv->rpdev->dst, priv->skb->len); + } + + if (debug >= DBG_PACKET) { + // TODO build up packet debug message + } + +cleanup: + // return the skb to the network stack + if (priv->skb != NULL) { + dev_consume_skb_any(priv->skb); + priv->skb = NULL; + } + + priv->is_delayed = false; + + // if not shutdown, re-activate the network stack xmit queue + spin_lock_irqsave(&priv->shutdown_lock, flags); + if (!priv->is_shutdown) { + netif_wake_queue(priv->ndev); + } + spin_unlock_irqrestore(&priv->shutdown_lock, flags); +} + +/** + * @brief Called in ISR context by the network stack when the network device wants to transmit a + * frame. Our goal is to send this skb (frame) on the RPMsg stack. Since RPMsg send may block + * (sleep), we funnel this request over to the `immedate` workqueue, which runs in process + * context. Since RPMsg lacks a mechanism to poll the outbound queue state (we can't know if + * our next send will succeed, or fail), we always inhibit the net device's transmitter upon + * receipt of an skb. The workqueue handler will re-enable the net device's transmitter upon + * RPMsg send success (or eventual failure). + * + * @param skb Frame to be sent + * @param ndev Network device that originated the frame + * + * @return NETDEV_TX_OK on success, NETDEV_TX_BUSY on hard (unrecoverable) error + */ +static int net_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct rpmsg_net_priv *priv = netdev_priv(ndev); + unsigned long flags; + + if (debug >= DBG_INSANE) { + dev_info(&priv->rpdev->dev, "net_xmit: in_interrupt:%lu [in_irq:%lu in_softirq:%lu] in_atomic:%u in_task:%u\n", + in_interrupt(), in_irq(), in_softirq(), in_atomic(), in_task()); + } + + if (priv->skb != NULL) { + // Hard (unrecoverable) error. When returning NETDEV_TX_BUSY, we must not keep a + // reference to the skb, and we must not free it. + + dev_err(&priv->rpdev->dev, "net_xmit: error: previous skb still in progress! this can't happen.\n"); + return NETDEV_TX_BUSY; + } + + // stop the net device transmitter. will be re-enabled by the work queue. + netif_stop_queue(priv->ndev); + + // schedule work, unless we are shutdown + spin_lock_irqsave(&priv->shutdown_lock, flags); + if (!priv->is_shutdown) { + priv->skb = skb; + schedule_work(&priv->immediate); + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + } else { + // we're shut down. drop packet. leave queue stopped. + spin_unlock_irqrestore(&priv->shutdown_lock, flags); + + dev_consume_skb_any(skb); + dev_info(&priv->rpdev->dev, "net_xmit: dropping packet due to shutdown request (race)\n"); + } + + return NETDEV_TX_OK; +} + +/////////////////////// +// Kernel Module API // +////////////////////// + +/** + * @brief Initializes the rpmsg_net kernel module, registering it with the RPMsg stack. + * + * @return 0 on success, -1 on failure + */ +static int __init rpmsg_net_module_init(void) +{ + printk(KERN_INFO "rpmsg_net: module starting\n"); + + // initialize global state + memset(rpmsg_net_device, 0x00, sizeof(rpmsg_net_device)); + + return register_rpmsg_driver(&rpmsg_net_client); +} + +/** + * @brief De-initializes the rpmsg_net kernel module, unregistering with the RPMsg stack. This, + * in turn, invokes the remove callback handler for all channels. Each remove callback will, + * in turn, unregister the associated netdevice from the network device stack. + */ +static void __exit rpmsg_net_module_exit(void) +{ + unregister_rpmsg_driver(&rpmsg_net_client); + + printk(KERN_INFO "rpmsg_net: module unloaded\n"); +} + +module_init(rpmsg_net_module_init); +module_exit(rpmsg_net_module_exit); diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 7d5f5458c72ed2..9120cd4b761f6a 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -127,6 +127,22 @@ config PINCTRL_AXP209 selected. Say Y to enable pinctrl and GPIO support for the AXP209 PMIC. +config PINCTRL_BFLB_GPIO + tristate "Bouffalo Lab SoC GPIO pin controller driver" + depends on SOC_BOUFFALOLAB + select PINMUX + select GPIOLIB + select GPIOLIB_IRQCHIP + select GENERIC_PINCONF + select GENERIC_PINCTRL_GROUPS + select GENERIC_PINMUX_FUNCTIONS + select OF_GPIO + help + This is the driver for the GPIO controller found on Bouffalo Lab RISC-V SoCs. + + This driver can also be built as a module. If so, the module + will be called pinctrl-bflb-gpio. + config PINCTRL_BM1880 bool "Bitmain BM1880 Pinctrl driver" depends on OF && (ARCH_BITMAIN || COMPILE_TEST) diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index d5939840bb2ad5..9f7c89ed4bb40f 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_PINCTRL_AS3722) += pinctrl-as3722.o obj-$(CONFIG_PINCTRL_AT91) += pinctrl-at91.o obj-$(CONFIG_PINCTRL_AT91PIO4) += pinctrl-at91-pio4.o obj-$(CONFIG_PINCTRL_AXP209) += pinctrl-axp209.o +obj-$(CONFIG_PINCTRL_BFLB_GPIO) += pinctrl-bflb.o obj-$(CONFIG_PINCTRL_BM1880) += pinctrl-bm1880.o obj-$(CONFIG_PINCTRL_CY8C95X0) += pinctrl-cy8c95x0.o obj-$(CONFIG_PINCTRL_DA850_PUPD) += pinctrl-da850-pupd.o diff --git a/drivers/pinctrl/pinctrl-bflb.c b/drivers/pinctrl/pinctrl-bflb.c new file mode 100644 index 00000000000000..4223fcc18ed591 --- /dev/null +++ b/drivers/pinctrl/pinctrl-bflb.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Bouffalo Lab SoC pinctrl+GPIO+external IRQ driver + * + * Based on: pinctrl-apple-gpio.c + * Copyright (C) The Asahi Linux Contributors + * Copyright (C) 2020 Corellium LLC + * + * Based on: pinctrl-pistachio.c + * Copyright (C) 2014 Imagination Technologies Ltd. + * Copyright (C) 2014 Google, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pinctrl-utils.h" +#include "core.h" +#include "pinmux.h" + +struct bflb_gpio_pinctrl { + struct device *dev; + struct pinctrl_dev *pctldev; + + void __iomem *base; + struct regmap *map; + + struct pinctrl_desc pinctrl_desc; + struct gpio_chip gpio_chip; + + void *irqsunmasked; + u8 irqgrps[]; +}; + +//Register indexing +#define REG_GPIO(x) (4 * (x)) + +//Register map +#define REG_GPIOx_MODE GENMASK(31, 30) +#define REG_GPIOx_I BIT(28) +#define REG_GPIOx_CLR BIT(26) +#define REG_GPIOx_SET BIT(25) +#define REG_GPIOx_O BIT(24) +#define REG_GPIOx_INT_MASK BIT(22) +#define REG_GPIOx_INT_STAT BIT(21) +#define REG_GPIOx_INT_CLR BIT(20) +#define REG_GPIOx_INT_MODE_SET GENMASK(29, 16) +#define REG_GPIOx_FUNC_SEL GENMASK(12, 8) +#define REG_GPIOx_OE BIT(6) +#define REG_GPIOx_PD BIT(5) +#define REG_GPIOx_PU BIT(4) +#define REG_GPIOx_DRV GENMASK(3, 2) +#define REG_GPIOx_SMT BIT(1) +#define REG_GPIOx_IE BIT(0) + +//Interrupt trigger modes +#define BFLB_IRQ_MODE_SYNC_EDGE_FALLING 0 +#define BFLB_IRQ_MODE_SYNC_EDGE_RISING 1 +#define BFLB_IRQ_MODE_SYNC_LEVEL_LOW 2 +#define BFLB_IRQ_MODE_SYNC_LEVEL_HIGH 3 +#define BFLB_IRQ_MODE_SYNC_EDGE_BOTH 4 + +#define BFLB_IRQ_MODE_ASYNC_EDGE_FALLING 8 +#define BFLB_IRQ_MODE_ASYNC_EDGE_RISING 9 +#define BFLB_IRQ_MODE_ASYNC_LEVEL_LOW 10 +#define BFLB_IRQ_MODE_ASYNC_LEVEL_HIGH 11 + +static const char * const pinmux_functions[] = { + //AH: As taken from smaeul's pinctrl-bflb.c for U-Boot + [0] = "sdh", + [1] = "spi0", + [2] = "flash", + [3] = "i2s", + [4] = "pdm", + [5] = "i2c0", + [6] = "i2c1", + [7] = "uart", + [8] = "emac", + [9] = "cam", + [10] = "analog", + [11] = "gpio", + [16] = "pwm0", + [17] = "pwm1", + [18] = "spi1", // mm_spi + [19] = "i2c2", // mm_i2c0 + [20] = "i2c3", // mm_i2c1 + [21] = "mm_uart", + [22] = "dbi_b", + [23] = "dbi_c", + [24] = "dpi", + [25] = "jtag_lp", + [26] = "jtag_m0", + [27] = "jtag_d0", + [31] = "clock", +}; + +struct regmap_config regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .cache_type = REGCACHE_FLAT, + .max_register = 512 * sizeof(u32), + .num_reg_defaults_raw = 512, + .use_relaxed_mmio = true, + .use_raw_spinlock = true, +}; + +//AH: Set raw gpio config register bits based on mask +static void bflb_gpio_set_reg(struct bflb_gpio_pinctrl *pctl, unsigned int pin, + u32 mask, u32 value) +{ + regmap_update_bits(pctl->map, REG_GPIO(pin), mask, value); +} + +//AH: Get raw gpio config register bits +static u32 bflb_gpio_get_reg(struct bflb_gpio_pinctrl *pctl, unsigned int pin) +{ + int ret; + u32 val; + + ret = regmap_read(pctl->map, REG_GPIO(pin), &val); + + if (ret) + return 0; + + return val; +} + +/* Pin controller functions */ + +static const struct pinctrl_ops bflb_gpio_pinctrl_ops = { + .get_groups_count = pinctrl_generic_get_group_count, + .get_group_name = pinctrl_generic_get_group_name, + .get_group_pins = pinctrl_generic_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, + .dt_free_map = pinconf_generic_dt_free_map, +}; + +/* Pin multiplexer functions */ + +//AH: Configure gpio modes and features +static int bflb_gpio_pinmux_set(struct pinctrl_dev *pctldev, unsigned int func, + unsigned int group) +{ + struct bflb_gpio_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + + bflb_gpio_set_reg(pctl, group, REG_GPIOx_FUNC_SEL, + FIELD_PREP(REG_GPIOx_FUNC_SEL, func)); + + dev_dbg(pctl->dev, "Pin %u set to function %u", group, func); + + return 0; +} + +static const struct pinmux_ops bflb_gpio_pinmux_ops = { + .get_functions_count = pinmux_generic_get_function_count, + .get_function_name = pinmux_generic_get_function_name, + .get_function_groups = pinmux_generic_get_function_groups, + .set_mux = bflb_gpio_pinmux_set, + .strict = true, +}; + +/* GPIO chip functions */ + +//AH: Get the current gpio direction +static int bflb_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + unsigned int reg = bflb_gpio_get_reg(pctl, offset); + + if (FIELD_GET(REG_GPIOx_OE, reg) == 1 && + FIELD_GET(REG_GPIOx_IE, reg) == 0) { + return GPIO_LINE_DIRECTION_OUT; + } else if (FIELD_GET(REG_GPIOx_IE, reg) == 1 && + FIELD_GET(REG_GPIOx_OE, reg) == 0) { + return GPIO_LINE_DIRECTION_IN; + } + + return -EIO; +} + +//AH: Get the incoming input or outgoing output value for the specified GPIO +static int bflb_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + unsigned int reg = bflb_gpio_get_reg(pctl, offset); + + if (FIELD_GET(REG_GPIOx_OE, reg) == 1 && + FIELD_GET(REG_GPIOx_IE, reg) == 0) { + reg = readl_relaxed(pctl->base + REG_GPIO(offset)); + return !!(reg & REG_GPIOx_O); + } else if (FIELD_GET(REG_GPIOx_IE, reg) == 1 && + FIELD_GET(REG_GPIOx_OE, reg) == 0) { + reg = readl_relaxed(pctl->base + REG_GPIO(offset)); + return !!(reg & REG_GPIOx_I); + } + + return -EIO; +} + +//AH: Set the specified GPIO's output state +static void bflb_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_O, value ? + FIELD_PREP(REG_GPIOx_O, 1) : 0); + + dev_dbg(pctl->dev, "Pin %u set to value %u", offset, value); +} + +//AH: Set the specified gpio direction to input +static int bflb_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_OE | REG_GPIOx_IE | REG_GPIOx_SMT, + FIELD_PREP(REG_GPIOx_OE, 0) | FIELD_PREP(REG_GPIOx_IE, 1) | + FIELD_PREP(REG_GPIOx_SMT, 1)); + + dev_dbg(pctl->dev, "Pin %u set to direction input", offset); + + return 0; +} + +//AH: Set the specified gpio direction to output +static int bflb_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_PD | REG_GPIOx_PU | REG_GPIOx_OE | + REG_GPIOx_IE | REG_GPIOx_SMT | REG_GPIOx_MODE, + FIELD_PREP(REG_GPIOx_PD, 0) | FIELD_PREP(REG_GPIOx_PU, 0) | + FIELD_PREP(REG_GPIOx_OE, 1) | FIELD_PREP(REG_GPIOx_IE, 0) | + FIELD_PREP(REG_GPIOx_SMT, 1) | FIELD_PREP(REG_GPIOx_MODE, 0)); + + dev_dbg(pctl->dev, "Pin %u set to direction output", offset); + + bflb_gpio_set(chip, offset, value); //Set the initially passed value + + return 0; +} + +//AH: Configure pin electrical characteristics +static int bflb_gpio_set_config(struct gpio_chip *chip, unsigned int offset, + unsigned long config) +{ + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + enum pin_config_param param = pinconf_to_config_param(config); + unsigned int arg = pinconf_to_config_argument(config); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_PD | REG_GPIOx_PU, + FIELD_PREP(REG_GPIOx_PD, 0) | FIELD_PREP(REG_GPIOx_PU, 0)); + break; + + case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: + if (arg) { + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_PD | REG_GPIOx_PU, + FIELD_PREP(REG_GPIOx_PD, 0) | FIELD_PREP(REG_GPIOx_PU, 0)); + } + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + if (arg) { + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_PD | REG_GPIOx_PU, + FIELD_PREP(REG_GPIOx_PD, 1) | FIELD_PREP(REG_GPIOx_PU, 0)); + } + break; + + case PIN_CONFIG_BIAS_PULL_UP: + if (arg) { + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_PD | REG_GPIOx_PU, + FIELD_PREP(REG_GPIOx_PD, 0) | FIELD_PREP(REG_GPIOx_PU, 1)); + } + break; + + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_PD | REG_GPIOx_PU | + REG_GPIOx_IE | REG_GPIOx_OE, FIELD_PREP(REG_GPIOx_PD, 0) | + FIELD_PREP(REG_GPIOx_PU, 0) | FIELD_PREP(REG_GPIOx_IE, 0) | + FIELD_PREP(REG_GPIOx_OE, 0)); + break; + + case PIN_CONFIG_INPUT_ENABLE: + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_IE, + FIELD_PREP(REG_GPIOx_IE, !!arg)); + break; + + case PIN_CONFIG_INPUT_SCHMITT_ENABLE: + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_SMT, + FIELD_PREP(REG_GPIOx_SMT, !!arg)); + break; + + case PIN_CONFIG_OUTPUT: + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_OE | REG_GPIOx_O, + FIELD_PREP(REG_GPIOx_OE, 1) | FIELD_PREP(REG_GPIOx_O, !!arg)); + break; + + case PIN_CONFIG_OUTPUT_ENABLE: + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_OE, + FIELD_PREP(REG_GPIOx_OE, 1)); + break; + + default: return -ENOTSUPP; + } + + dev_dbg(pctl->dev, "Pin %u config set to %lu (param %u, arg %u)", offset, + config, param, arg); + + return 0; +} + +/* IRQ chip functions */ + +//AH: Clear the interrupt for the specified GPIO +static void bflb_gpio_irq_ack(struct irq_data *data) +{ + struct bflb_gpio_pinctrl *pctl = + gpiochip_get_data(irq_data_get_irq_chip_data(data)); + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_CLR, + FIELD_PREP(REG_GPIOx_INT_CLR, 1)); + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_CLR, + FIELD_PREP(REG_GPIOx_INT_CLR, 0)); + + dev_dbg(pctl->dev, "Pin %lu IRQ ACK", data->hwirq); +} + +//AH: Find the correct value for the type of interrupts we want to receive +//for a GPIO +static unsigned int bflb_gpio_irq_type(unsigned int type) +{ + unsigned int selected; + + switch (type & IRQ_TYPE_SENSE_MASK) { + + case IRQ_TYPE_EDGE_RISING: + selected = BFLB_IRQ_MODE_SYNC_EDGE_RISING; + break; + + case IRQ_TYPE_EDGE_FALLING: + selected = BFLB_IRQ_MODE_SYNC_EDGE_FALLING; + break; + + case IRQ_TYPE_EDGE_BOTH: + selected = BFLB_IRQ_MODE_SYNC_EDGE_BOTH; + break; + + case IRQ_TYPE_LEVEL_HIGH: + selected = BFLB_IRQ_MODE_SYNC_LEVEL_HIGH; + break; + + case IRQ_TYPE_LEVEL_LOW: + selected = BFLB_IRQ_MODE_SYNC_LEVEL_LOW; + break; + + //No "off" available on BL808, set to default IRQ_TYPE_EDGE_FALLING and + //then we'll need to mask + default: + selected = BFLB_IRQ_MODE_SYNC_EDGE_FALLING; + break; + } + + return selected; +} + +//AH: Disable the specified GPIO's interrupt +static void bflb_gpio_irq_mask(struct irq_data *data) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(gc); + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_MASK, + FIELD_PREP(REG_GPIOx_INT_MASK, 1)); + + clear_bit(data->hwirq, pctl->irqsunmasked); + gpiochip_disable_irq(gc, data->hwirq); + + dev_dbg(pctl->dev, "Pin %lu IRQ Mask", data->hwirq); +} + +//AH: Enable the specified GPIO's interrupt +static void bflb_gpio_irq_unmask(struct irq_data *data) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(gc); + unsigned int irqtype = bflb_gpio_irq_type(irqd_get_trigger_type(data)); + + gpiochip_enable_irq(gc, data->hwirq); + set_bit(data->hwirq, pctl->irqsunmasked); + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_MASK | + REG_GPIOx_INT_MODE_SET, FIELD_PREP(REG_GPIOx_INT_MASK, 0) | + FIELD_PREP(REG_GPIOx_INT_MODE_SET, irqtype)); + + dev_dbg(pctl->dev, "Pin %lu IRQ Unmask", data->hwirq); +} + +//AH: Initialise the specified GPIO's interrupt +static unsigned int bflb_gpio_irq_startup(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_CLR, + FIELD_PREP(REG_GPIOx_INT_CLR, 1)); + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_CLR, + FIELD_PREP(REG_GPIOx_INT_CLR, 0)); + + bflb_gpio_irq_unmask(data); + + dev_dbg(pctl->dev, "Pin %lu IRQ Started", data->hwirq); + + return 0; +} + +//AH: Set the specified GPIO's interrupt mode +static int bflb_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct bflb_gpio_pinctrl *pctl = + gpiochip_get_data(irq_data_get_irq_chip_data(data)); + + unsigned int irqtype = bflb_gpio_irq_type(type); + + if (irqtype == 0) + return -EINVAL; + + bflb_gpio_set_reg(pctl, data->hwirq, REG_GPIOx_INT_MODE_SET, + FIELD_PREP(REG_GPIOx_INT_MODE_SET, irqtype)); + + if (type & IRQ_TYPE_LEVEL_MASK) + irq_set_handler_locked(data, handle_level_irq); + else + irq_set_handler_locked(data, handle_edge_irq); + + dev_dbg(pctl->dev, "Pin %lu IRQ type set to %u", data->hwirq, irqtype); + + return 0; +} + +//AH: Handle GPIO interrupts on this controller +static void bflb_gpio_irq_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + u8 *grpp = irq_desc_get_handler_data(desc); + struct bflb_gpio_pinctrl *pctl; + unsigned int pinh; + unsigned long reg; + struct gpio_chip *gc; + + pctl = container_of(grpp - *grpp, typeof(*pctl), irqgrps[0]); + gc = &pctl->gpio_chip; + + chained_irq_enter(chip, desc); + + //We must go through each individual GPIO register to read its interrupt + //status. There is no gpio_cfg128+ helper register for interrupts + //(looking at BL808 RM) + for (pinh = 0; pinh < gc->ngpio; pinh += 1) { + if (test_bit(pinh, pctl->irqsunmasked)) { + dev_dbg(pctl->dev, "Reading IRQ status of pin %u", pinh); + + reg = readl_relaxed(pctl->base + REG_GPIO(pinh)); + + if (reg & REG_GPIOx_INT_STAT) { + generic_handle_domain_irq(gc->irq.domain, pinh); + dev_dbg(pctl->dev, "Pin %u IRQ Fire", pinh); + } + } else { + dev_dbg(pctl->dev, "Ignoring IRQ status of masked pin %u", pinh); + } + } + + chained_irq_exit(chip, desc); +} + +static const struct irq_chip bflb_gpio_irqchip = { + .name = "bflb-gpio", + .irq_startup = bflb_gpio_irq_startup, + .irq_ack = bflb_gpio_irq_ack, + .irq_mask = bflb_gpio_irq_mask, + .irq_unmask = bflb_gpio_irq_unmask, + .irq_set_type = bflb_gpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int bflb_gpio_request(struct gpio_chip *chip, unsigned int offset) +{ + int ret; + struct bflb_gpio_pinctrl *pctl = gpiochip_get_data(chip); + + ret = pinctrl_gpio_request(chip->base + offset); + + if (ret) + return ret; + + bflb_gpio_set_reg(pctl, offset, REG_GPIOx_FUNC_SEL, + FIELD_PREP(REG_GPIOx_FUNC_SEL, 11/*SWGPIO*/)); + + dev_dbg(pctl->dev, "Pin %u set to function GPIO as part of request", + offset); + + return 0; +} + +/* Probe & register */ + +static int bflb_gpio_register(struct bflb_gpio_pinctrl *pctl) +{ + struct gpio_irq_chip *girq = &pctl->gpio_chip.irq; + void **irq_data = NULL; + int ret; + + pctl->gpio_chip.label = dev_name(pctl->dev); + pctl->gpio_chip.request = bflb_gpio_request; + pctl->gpio_chip.free = gpiochip_generic_free; + pctl->gpio_chip.get_direction = bflb_gpio_get_direction; + pctl->gpio_chip.direction_input = bflb_gpio_direction_input; + pctl->gpio_chip.direction_output = bflb_gpio_direction_output; + pctl->gpio_chip.get = bflb_gpio_get; + pctl->gpio_chip.set = bflb_gpio_set; + pctl->gpio_chip.set_config = bflb_gpio_set_config; + pctl->gpio_chip.base = -1; + pctl->gpio_chip.ngpio = pctl->pinctrl_desc.npins; + pctl->gpio_chip.parent = pctl->dev; + + if (girq->num_parents) { + int i; + + gpio_irq_chip_set_chip(girq, &bflb_gpio_irqchip); + + girq->parent_handler = bflb_gpio_irq_handler; + + girq->parents = kmalloc_array(girq->num_parents, sizeof(*girq->parents), + GFP_KERNEL); + irq_data = kmalloc_array(girq->num_parents, sizeof(*irq_data), + GFP_KERNEL); + + if (!girq->parents || !irq_data) { + ret = -ENOMEM; + goto out_free_irq_data; + } + + for (i = 0; i < girq->num_parents; i++) { + ret = platform_get_irq(to_platform_device(pctl->dev), i); + + if (ret < 0) + goto out_free_irq_data; + + girq->parents[i] = ret; + pctl->irqgrps[i] = i; + irq_data[i] = &pctl->irqgrps[i]; + } + + girq->parent_handler_data_array = irq_data; + girq->per_parent_data = true; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_level_irq; + } + + ret = devm_gpiochip_add_data(pctl->dev, &pctl->gpio_chip, pctl); + +out_free_irq_data: + kfree(girq->parents); + kfree(irq_data); + + return ret; +} + +static int bflb_gpio_pinctrl_probe(struct platform_device *pdev) +{ + struct bflb_gpio_pinctrl *pctl; + struct pinctrl_pin_desc *pins; + + unsigned int npins; + const char **pin_names; + unsigned int *pin_nums; + unsigned int i, nirqs = 0; + int res; + + if (of_property_read_bool(pdev->dev.of_node, "interrupt-controller")) { + res = platform_irq_count(pdev); + if (res > 0) + nirqs = res; + } + + pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); + + if (!pctl) + return -ENOMEM; + + pctl->dev = &pdev->dev; + pctl->gpio_chip.irq.num_parents = nirqs; + + dev_set_drvdata(&pdev->dev, pctl); + + if (of_property_read_u32(pdev->dev.of_node, "bflb,npins", &npins)) + return dev_err_probe(&pdev->dev, -EINVAL, + "bflb,npins property not found\n"); + + pctl->irqsunmasked = devm_bitmap_zalloc(&pdev->dev, npins, GFP_KERNEL); + + pins = devm_kmalloc_array(&pdev->dev, npins, sizeof(pins[0]), GFP_KERNEL); + pin_names = devm_kmalloc_array(&pdev->dev, npins, sizeof(pin_names[0]), + GFP_KERNEL); + pin_nums = devm_kmalloc_array(&pdev->dev, npins, sizeof(pin_nums[0]), + GFP_KERNEL); + + if (!pins || !pin_names || !pin_nums) + return -ENOMEM; + + pctl->base = devm_platform_ioremap_resource(pdev, 0); + + if (IS_ERR(pctl->base)) + return PTR_ERR(pctl->base); + + pctl->map = devm_regmap_init_mmio(&pdev->dev, pctl->base, ®map_config); + + if (IS_ERR(pctl->map)) + return dev_err_probe(&pdev->dev, PTR_ERR(pctl->map), + "Failed to create regmap\n"); + + for (i = 0; i < npins; i++) { + pins[i].number = i; + pins[i].name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "GPIO%u", i); + pins[i].drv_data = pctl; + pin_names[i] = pins[i].name; + pin_nums[i] = i; + } + + pctl->pinctrl_desc.name = dev_name(pctl->dev); + pctl->pinctrl_desc.pins = pins; + pctl->pinctrl_desc.npins = npins; + pctl->pinctrl_desc.pctlops = &bflb_gpio_pinctrl_ops; + pctl->pinctrl_desc.pmxops = &bflb_gpio_pinmux_ops; + + pctl->pctldev = devm_pinctrl_register(&pdev->dev, &pctl->pinctrl_desc, + pctl); + + if (IS_ERR(pctl->pctldev)) + return dev_err_probe(&pdev->dev, PTR_ERR(pctl->pctldev), + "Failed to register pinctrl device.\n"); + + for (i = 0; i < npins; i++) { + res = pinctrl_generic_add_group(pctl->pctldev, + pins[i].name, pin_nums + i, 1, pctl); + + dev_dbg(&pdev->dev, "Registered pin %s with numeric %u", + pins[i].name, i); + + if (res < 0) + return dev_err_probe(pctl->dev, res, "Failed to register group"); + } + + for (i = 0; i < ARRAY_SIZE(pinmux_functions); ++i) { + if (pinmux_functions[i]) { + res = pinmux_generic_add_function(pctl->pctldev, + pinmux_functions[i], pin_names, npins, pctl); + + dev_dbg(&pdev->dev, "Registered function %s with numeric %u", + pinmux_functions[i], i); + + if (res < 0) + return dev_err_probe(pctl->dev, res, + "Failed to register function."); + } + } + + dev_info(&pdev->dev, "Bouffalo Lab pinctrl+GPIO(+interrupt) controller - " + "Registered %lu function(s) for %u pin(s)", + + ARRAY_SIZE(pinmux_functions), npins); + + return bflb_gpio_register(pctl); +} + +static const struct of_device_id bflb_gpio_pinctrl_of_match[] = { + { .compatible = "bflb,pinctrl", }, + { } +}; + +MODULE_DEVICE_TABLE(of, bflb_gpio_pinctrl_of_match); + +static struct platform_driver bflb_gpio_pinctrl_driver = { + .driver = { + .name = "bflb-gpio-pinctrl", + .of_match_table = bflb_gpio_pinctrl_of_match, + .suppress_bind_attrs = true, + }, + .probe = bflb_gpio_pinctrl_probe, +}; + +module_platform_driver(bflb_gpio_pinctrl_driver); + +MODULE_DESCRIPTION("Bouffalo BL808 pinctrl/GPIO driver"); +MODULE_AUTHOR("Alexander Horner "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index a850e9f486dd63..330936417029ed 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -161,6 +161,15 @@ config PRU_REMOTEPROC processors on various TI SoCs. It's safe to say N here if you're not interested in the PRU or if you are unsure. +config BFLB_REMOTEPROC + tristate "BL808 remoteproc support" + select MAILBOX + help + Support for Bouffalo Labs BL808 remote processors M0/LP + subsystem via the remote processor framework. + + Say Y or M here to enable + config QCOM_PIL_INFO tristate diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 91314a9b43cefc..6f76dac72eb9cb 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -39,3 +39,5 @@ obj-$(CONFIG_STM32_RPROC) += stm32_rproc.o obj-$(CONFIG_TI_K3_DSP_REMOTEPROC) += ti_k3_dsp_remoteproc.o obj-$(CONFIG_TI_K3_R5_REMOTEPROC) += ti_k3_r5_remoteproc.o obj-$(CONFIG_XLNX_R5_REMOTEPROC) += xlnx_r5_remoteproc.o +obj-$(CONFIG_BFLB_REMOTEPROC) += bl808_remoteproc.o + diff --git a/drivers/remoteproc/bl808_remoteproc.c b/drivers/remoteproc/bl808_remoteproc.c new file mode 100644 index 00000000000000..4670b5e1316b06 --- /dev/null +++ b/drivers/remoteproc/bl808_remoteproc.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Remote processor machine-specific module for bflb + * + * Copyright (C) 2023 Justin Hammond + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "remoteproc_internal.h" + +/** + * struct bflb_mbox - bflb mailbox instance state + * @name: the name of the mailbox + * @client: the mailbox client + * @chan: the mailbox channel + * @vq_work: the workqueue for the virtqueue + * @vq_id: the virtqueue id + */ + +struct bflb_mbox { + unsigned char name[10]; + struct mbox_client client; + struct mbox_chan *chan; + struct work_struct vq_work; + int vq_id; +}; + + +/** + * struct bflb_rproc - bflb remote processor instance state + * @rproc: rproc handle + * @mbox: the mailbox channel + * @client: the mailbox client + */ +struct bflb_rproc { + struct rproc *rproc; + struct bflb_mbox mbox_rx; + struct bflb_mbox mbox_tx; + struct workqueue_struct *workqueue; +}; + +/* We have to fundge the resource table for the M0 + * as its already running firmware by the time Linux Loads + * and its not a ELF image, so these setup the Virtqueue, + * and RPMSG structures to communicate with it + */ + +/* The feature bitmap for virtio rpmsg */ +#define VIRTIO_RPMSG_F_NS 0 /* RP supports name service notifications */ + +#define FW_RSC_U32_ADDR_ANY 0xFFFFFFFFUL + +#define RPMSG_VDEV_DFEATURES (1 << VIRTIO_RPMSG_F_NS) + +/* VirtIO rpmsg device id */ +#define VIRTIO_ID_RPMSG_ 7 + +/* Resource table entries */ +#define NUM_VRINGS 0x02 +#define VRING_ALIGN 0x1000 +#define RING_TX FW_RSC_U32_ADDR_ANY +#define RING_RX FW_RSC_U32_ADDR_ANY +/* VRING_SIZE is the number of VRINGS per TX/RX ring, so + * actual number is double this. Size of the space needed + * is VRING_SIZE * 2 * MAX_RPMSG_BUF_SIZE + */ +#define VRING_SIZE 8 +#define NUM_TABLE_ENTRIES 1 +#define NO_RESOURCE_ENTRIES 1 + +/* this is the structure of the Resource Table normally + * loaded from a ELF header. + * its setup for just 1 VDEV entry, which is the RPMSG + * structure. We manually setup the vring's and vbuffers + * in our prepre op below. + */ +struct remote_resource_table { + u32 version; + u32 num; + u32 reserved[2]; + u32 offset[NO_RESOURCE_ENTRIES]; + u32 type; /* the vdev type that follows */ + struct fw_rsc_vdev rpmsg_vdev; + struct fw_rsc_vdev_vring rpmsg_vring0; + struct fw_rsc_vdev_vring rpmsg_vring1; +} __packed; + +/* Setup one RPMSG VDEV and two VRINGs */ +struct remote_resource_table resources = { + /* Version */ + .version = 1, + + /* NUmber of table entries */ + .num = NUM_TABLE_ENTRIES, + + /* reserved fields */ + .reserved = {0, 0,}, + + /* Offsets of rsc entries */ + .offset[0] = offsetof(struct remote_resource_table, type), + + .type = RSC_VDEV, + /* Virtio device entry */ + { + VIRTIO_ID_RPMSG_, 0, RPMSG_VDEV_DFEATURES, 0, 0, 0, + NUM_VRINGS, {0, 0}, + }, + + /* Vring rsc entry - part of vdev rsc entry */ + {FW_RSC_U32_ADDR_ANY, VRING_ALIGN, VRING_SIZE, 0, 0}, + {FW_RSC_U32_ADDR_ANY, VRING_ALIGN, VRING_SIZE, 1, 0}, +}; + +/* return a pointer to our resource table */ +struct resource_table *bflb_rproc_get_loaded_rsc_table(struct rproc *rproc, size_t *size) +{ + *size = sizeof(resources); + return (struct resource_table *)&resources; +} + +/* allocate vdev0buffer */ +static int bflb_rproc_mem_alloc(struct rproc *rproc, + struct rproc_mem_entry *mem) +{ + struct device *dev = rproc->dev.parent; + void *va; + + va = ioremap_wc(mem->dma, mem->len); + if (!va) { + dev_err(dev, "Unable to map memory region: %pa+%zx\n", + &mem->dma, mem->len); + return -ENOMEM; + } + + /* Update memory entry va */ + mem->va = va; + + return 0; +} + +/* release vdev0buffer */ +static int bflb_rproc_mem_release(struct rproc *rproc, + struct rproc_mem_entry *mem) +{ + iounmap(mem->va); + + return 0; +} + +/* + * Pull the memory ranges for virtio from the device tree and register them. + * Called as prepare. + */ +static int bflb_rproc_setupmem(struct rproc *rproc) +{ + struct device *dev = rproc->dev.parent; + struct device_node *np = dev->of_node; + struct rproc_mem_entry *mem; + struct reserved_mem *rmem; + struct of_phandle_iterator it; + int index = 0; + + dev_dbg(dev, "%s %s", __func__, np->name); + + of_phandle_iterator_init(&it, np, "memory-region", NULL, 0); + while (of_phandle_iterator_next(&it) == 0) { + rmem = of_reserved_mem_lookup(it.node); + if (!rmem) { + dev_err(dev, "unable to acquire memory-region\n"); + return -EINVAL; + } + + /* No need to map vdev buffer */ + if (strcmp(it.node->name, "vdev0buffer")) { + /* Register memory region */ + mem = rproc_mem_entry_init(dev, NULL, + (dma_addr_t)rmem->base, + rmem->size, rmem->base, + bflb_rproc_mem_alloc, + bflb_rproc_mem_release, + it.node->name); + } else { + /* Register reserved memory for vdev buffer allocation */ + mem = rproc_of_resm_mem_entry_init(dev, index, + rmem->size, + rmem->base, + it.node->name); + } + + if (!mem) { + dev_err(dev, "unable to allocate memory entry %s", it.node->name); + return -ENOMEM; + } + rproc_add_carveout(rproc, mem); + index++; + } + return 0; +} + + +/* M0 is already started. Do Nothing + */ +static int bflb_rproc_start(struct rproc *rproc) +{ + struct device *dev = rproc->dev.parent; + + dev_dbg(dev, "bflb_rproc_start"); + + return 0; +} + +/* We don't want to stop M0, as it will crash. Do Nothing */ +static int bflb_rproc_stop(struct rproc *rproc) +{ + struct device *dev = rproc->dev.parent; + + dev_dbg(dev, "bflb_rproc_stop"); + + return 0; +} + +/* kick the virtqueue to let M0 know there is a update to the vring */ +static void bflb_rproc_send_kick(struct rproc *rproc, int vqid) +{ + struct device *dev = rproc->dev.parent; + struct bflb_rproc *drproc = (struct bflb_rproc *)rproc->priv; + struct bflb_mbox *mb = &drproc->mbox_tx; + struct mbox_chan *chan = mb->chan; + struct bflb_mbox_msg *msg; + int ret; + int i; + static int count; + + /* just for debugging atm */ + count++; + + msg = kzalloc(sizeof(struct bflb_mbox_msg), GFP_KERNEL); + if (!msg) + return; + + /* we need a small delay before kicking the other side + * (I assume to allow the ring to update/flush etc?) + * without this, we get lots of "empty ring" messages on the + * other side + */ + mdelay(1); + + msg->param = vqid; + msg->id = count; + /* Kick the other CPU to let it know the vrings are updated */ + dev_dbg(dev, "%s %d Mailbox: %s %d", __func__, msg->id, mb->name, msg->param); + /* we occasionally get a EOI timeout, so retry upto 3 times */ + for (i = 0; i < 3; i++) { + ret = mbox_send_message(chan, msg); + if (ret >= 0) + goto done; + dev_warn(dev, "%s %d Mailbox %s sending %d Failed: %d - Retrying %d", __func__, msg->id, mb->name, msg->param, ret, i); + } +done: + dev_dbg(dev, "%s %d Mailbox %s done %d: %d", __func__, msg->id, mb->name, msg->param, ret); + kfree(msg); +} + +static void bflb_rproc_recv_kick(struct work_struct *work) +{ + struct bflb_mbox *mb = container_of(work, struct bflb_mbox, vq_work); + struct rproc *rproc = dev_get_drvdata(mb->client.dev); + + dev_dbg(rproc->dev.parent, "%s mailbox: %s: %d", __func__, mb->name, mb->vq_id); + + /* not a bad thing if there is no messages, probably + * means that the previous ring kick processed the message + */ + if (rproc_vq_interrupt(rproc, mb->vq_id) == IRQ_NONE) + dev_dbg(&rproc->dev, "no message found in vq%d\n", mb->vq_id); +} + +/* M0 signaled us there is a update on the vring, check it + */ +static void bflb_rproc_rx_mbox_callback(struct mbox_client *client, void *data) +{ + struct device *dev = client->dev; + struct rproc *rproc = dev_get_drvdata(dev); + struct bflb_rproc *drproc = (struct bflb_rproc *)rproc->priv; + struct bflb_mbox *mb = &drproc->mbox_rx; + struct bflb_mbox_msg *msg = data; + + mb->vq_id = msg->param; + + dev_dbg(dev, "%s mailbox %s: %d", __func__, mb->name, mb->vq_id); + + queue_work(drproc->workqueue, &mb->vq_work); + mbox_chan_txdone(mb->chan, 0); +} + +/* M0 is already running when we boot + * so just attach to it. + * we also register a mailbox to get kicks from M0 when vrings are updated + */ +static int bflb_rproc_attach(struct rproc *rproc) +{ + return 0; +} + +/* Detach. Do Nothing? */ +static int bflb_rproc_detach(struct rproc *rproc) +{ + return 0; +} + +static const struct rproc_ops bflb_rproc_ops = { + .start = bflb_rproc_start, + .stop = bflb_rproc_stop, + .attach = bflb_rproc_attach, + .detach = bflb_rproc_detach, + .kick = bflb_rproc_send_kick, + .prepare = bflb_rproc_setupmem, + .get_loaded_rsc_table = bflb_rproc_get_loaded_rsc_table, +}; + +static int bflb_rproc_setup_mbox(struct rproc *rproc) +{ + struct bflb_rproc *drproc = (struct bflb_rproc *)rproc->priv; + + struct bflb_mbox *tx_bflb_mbox = &drproc->mbox_tx; + struct mbox_client *tx_mbox_cl = &tx_bflb_mbox->client; + + struct bflb_mbox *rx_bflb_mbox = &drproc->mbox_rx; + struct mbox_client *rx_mbox_cl = &rx_bflb_mbox->client; + + struct device *dev = &rproc->dev; + int ret = 0; + + dev_dbg(dev, "bflb_rpoc_setup_mbox"); + + /* request the TX mailboxs */ + tx_mbox_cl->dev = dev->parent; + tx_mbox_cl->tx_block = true; + tx_mbox_cl->tx_tout = 200; + strncpy(tx_bflb_mbox->name, "virtio-tx", sizeof(tx_bflb_mbox->name)); + tx_bflb_mbox->chan = mbox_request_channel_byname(tx_mbox_cl, "virtio-tx"); + if (IS_ERR(tx_bflb_mbox->chan)) { + ret = -EBUSY; + dev_err(dev, "mbox_request_channel tx failed: %ld\n", + PTR_ERR(tx_bflb_mbox->chan)); + goto del_rx_mbox; + } + + /* request the RX mailboxs */ + rx_mbox_cl->dev = dev->parent; + rx_mbox_cl->rx_callback = bflb_rproc_rx_mbox_callback; + rx_mbox_cl->tx_block = true; + strncpy(rx_bflb_mbox->name, "virtio-rx", sizeof(rx_bflb_mbox->name)); + + rx_bflb_mbox->chan = mbox_request_channel_byname(rx_mbox_cl, "virtio-rx"); + if (IS_ERR(rx_bflb_mbox->chan)) { + ret = -EBUSY; + dev_err(dev, "mbox_request_channel rx failed: %ld\n", + PTR_ERR(rx_bflb_mbox->chan)); + goto del_tx_mbox; + } + INIT_WORK(&rx_bflb_mbox->vq_work, bflb_rproc_recv_kick); + + dev_dbg(dev, "bflb_rpoc_setup_mbox done"); + + return ret; + +del_tx_mbox: + mbox_free_channel(tx_bflb_mbox->chan); +del_rx_mbox: + mbox_free_channel(rx_bflb_mbox->chan); + return ret; +} + +static int bflb_rproc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bflb_rproc *drproc; + struct rproc *rproc; + int ret; + + dev_dbg(dev, "bflb_rproc_probe"); + + rproc = rproc_alloc(dev, "M0", &bflb_rproc_ops, NULL, + sizeof(*drproc)); + if (!rproc) { + ret = -ENOMEM; + goto free_mem; + } + + /* error recovery is not supported at present */ + rproc->recovery_disabled = true; + + /* M0 is running when linux boots */ + //atomic_inc(&rproc->power); + rproc->state = RPROC_DETACHED; + + drproc = rproc->priv; + drproc->rproc = rproc; + rproc->has_iommu = false; + rproc->sysfs_read_only = true; + + platform_set_drvdata(pdev, rproc); + + drproc->workqueue = create_workqueue(dev_name(dev)); + if (!drproc->workqueue) { + dev_err(dev, "cannot create workqueue\n"); + ret = -ENOMEM; + goto free_wkq; + } + + ret = bflb_rproc_setup_mbox(rproc); + if (ret) { + dev_err(dev, "bflb_rpoc_setup_mbox failed: %d\n", ret); + goto free_rproc; + } + + ret = rproc_add(rproc); + if (ret) { + dev_err(dev, "rproc_add failed: %d\n", ret); + goto free_rproc; + } + + dev_info(dev, "Bouffalo Labs Remote Processor Control Driver Started"); + + return 0; + +free_rproc: + rproc_free(rproc); +free_wkq: + destroy_workqueue(drproc->workqueue); +free_mem: + if (dev->of_node) + of_reserved_mem_device_release(dev); + return ret; +} + +static int bflb_rproc_remove(struct platform_device *pdev) +{ + struct rproc *rproc = platform_get_drvdata(pdev); + struct bflb_rproc *drproc = (struct bflb_rproc *)rproc->priv; + struct bflb_mbox *tx_bflb_mbox = &drproc->mbox_tx; + struct bflb_mbox *rx_bflb_mbox = &drproc->mbox_rx; + struct device *dev = &pdev->dev; + + dev_info(dev, "Bouffalo Labs Remote Processor Control Driver Removed"); + + + mbox_free_channel(tx_bflb_mbox->chan); + mbox_free_channel(rx_bflb_mbox->chan); + destroy_workqueue(drproc->workqueue); + + rproc_del(rproc); + rproc_free(rproc); + if (dev->of_node) + of_reserved_mem_device_release(dev); + + return 0; +} + +static const struct of_device_id davinci_rproc_of_match[] __maybe_unused = { + { .compatible = "bflb,bflb-rproc", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, davinci_rproc_of_match); + +static struct platform_driver bflb_rproc_driver = { + .probe = bflb_rproc_probe, + .remove = bflb_rproc_remove, + .driver = { + .name = "bflb-rproc", + .of_match_table = of_match_ptr(davinci_rproc_of_match), + }, +}; + +module_platform_driver(bflb_rproc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("justin@dynam.ac"); +MODULE_DESCRIPTION("bflb Remote Processor control driver"); diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index 905ac7910c98f3..5444d00c9aa4b0 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -127,8 +127,11 @@ struct virtio_rpmsg_channel { * can change this without changing anything in the firmware of the remote * processor. */ +/* for BL808, lets use a large buffer size so we can do ethernet frames + * eventually + */ #define MAX_RPMSG_NUM_BUFS (512) -#define MAX_RPMSG_BUF_SIZE (512) +#define MAX_RPMSG_BUF_SIZE (2032) /* * Local addresses are dynamically allocated on-demand. @@ -422,7 +425,9 @@ static struct rpmsg_device *__rpmsg_create_channel(struct virtproc_info *vrp, * rpmsg server channels has predefined local address (for now), * and their existence needs to be announced remotely */ - rpdev->announce = rpdev->src != RPMSG_ADDR_ANY; + /* rpmsg-lite never sends a ADDR_ANY address, without this, we have to wait + * for linux to send the first message... */ + rpdev->announce = 1; strncpy(rpdev->id.name, chinfo->name, RPMSG_NAME_SIZE); diff --git a/drivers/tty/rpmsg_tty.c b/drivers/tty/rpmsg_tty.c index 29db413bbc0308..f3ec046b6203cd 100644 --- a/drivers/tty/rpmsg_tty.c +++ b/drivers/tty/rpmsg_tty.c @@ -92,7 +92,8 @@ static int rpmsg_tty_write(struct tty_struct *tty, const u8 *buf, int len) * Use rpmsg_trysend instead of rpmsg_send to send the message so the caller is not * hung until a rpmsg buffer is available. In such case rpmsg_trysend returns -ENOMEM. */ - ret = rpmsg_trysend(rpdev->ept, (void *)buf, msg_size); + /* for bl808, we only have a few small buffers, and a slow M0 CPU, so lets use rpmsg_send */ + ret = rpmsg_send(rpdev->ept, (void *)buf, msg_size); if (ret) { dev_dbg_ratelimited(&rpdev->dev, "rpmsg_send failed: %d\n", ret); return ret; diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index c55b947f3cdbb7..4b1eb61436ae76 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -179,6 +179,24 @@ config SERIAL_ATMEL_TTYAT Say Y if you have an external 8250/16C550 UART. If unsure, say N. +config SERIAL_BFLB + tristate "Bouffalolab serial port support" + select SERIAL_CORE + depends on COMMON_CLK + help + This enables the driver for the Bouffalolab's serial. + +config SERIAL_BFLB_CONSOLE + bool "Support for console on Bouffalolab serial port" + depends on SERIAL_BFLB + select SERIAL_CORE_CONSOLE + select SERIAL_EARLYCON + help + Say Y here if you wish to use a Bouffalolab UART as the + system console (the system console is the device which + receives all kernel messages and warnings and which allows + logins in single user mode) as /dev/ttySn. + config SERIAL_KGDB_NMI bool "Serial console over KGDB NMI debugger port" depends on KGDB_SERIAL_CONSOLE diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 238a9557b4872e..8509cdc11d874c 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_SERIAL_8250) += 8250/ obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o +obj-$(CONFIG_SERIAL_BFLB) += bflb_uart.o obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o obj-$(CONFIG_SERIAL_PXA_NON8250) += pxa.o obj-$(CONFIG_SERIAL_SA1100) += sa1100.o diff --git a/drivers/tty/serial/bflb_uart.c b/drivers/tty/serial/bflb_uart.c new file mode 100644 index 00000000000000..5911f489959c75 --- /dev/null +++ b/drivers/tty/serial/bflb_uart.c @@ -0,0 +1,684 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Based on bflb_uart.c, by Bouffalolab team + * + * Copyright (C) 2022 Jisheng Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UART_UTX_CONFIG 0x00 +#define UART_CR_UTX_EN BIT(0) +#define UART_CR_UTX_CTS_EN BIT(1) +#define UART_CR_UTX_FRM_EN BIT(2) +#define UART_CR_UTX_PRT_EN BIT(4) +#define UART_CR_UTX_PRT_SEL BIT(5) +#define UART_CR_UTX_BIT_CNT_D_SFT 8 +#define UART_CR_UTX_BIT_CNT_D_MSK GENMASK(10, 8) +#define UART_CR_UTX_BIT_CNT_P_SFT 11 +#define UART_CR_UTX_BIT_CNT_P_MSK GENMASK(12, 11) +#define UART_URX_CONFIG 0x04 +#define UART_CR_URX_EN BIT(0) +#define UART_CR_URX_PRT_EN BIT(4) +#define UART_CR_URX_PRT_SEL BIT(5) +#define UART_CR_URX_BIT_CNT_D_SFT 8 +#define UART_CR_URX_BIT_CNT_D_MSK GENMASK(10, 8) +#define UART_BIT_PRD 0x08 +#define UART_CR_UTX_BIT_PRD GENMASK(15, 0) +#define UART_CR_URX_BIT_PRD GENMASK(31, 16) +#define UART_DATA_CONFIG 0x0c +#define UART_CR_UART_BIT_INV BIT(0) +#define UART_URX_RTO_TIMER 0x18 +#define UART_CR_URX_RTO_VALUE_MSK GENMASK(7, 0) +#define UART_SW_MODE 0x1c +#define UART_INT_STS (0x20) +#define UART_UTX_END_INT BIT(0) +#define UART_URX_END_INT BIT(1) +#define UART_UTX_FIFO_INT BIT(2) +#define UART_URX_FIFO_INT BIT(3) +#define UART_URX_RTO_INT BIT(4) +#define UART_URX_PCE_INT BIT(5) +#define UART_UTX_FER_INT BIT(6) +#define UART_URX_FER_INT BIT(7) +#define UART_URX_LSE_INT BIT(8) +#define UART_INT_MASK 0x24 +#define UART_INT_CLEAR 0x28 +#define UART_INT_EN 0x2c +#define UART_STATUS 0x30 +#define UART_STS_UTX_BUS_BUSY BIT(0) +#define UART_FIFO_CONFIG_0 (0x80) +#define UART_DMA_TX_EN BIT(0) +#define UART_DMA_RX_EN BIT(1) +#define UART_TX_FIFO_CLR BIT(2) +#define UART_RX_FIFO_CLR BIT(3) +#define UART_TX_FIFO_OVERFLOW BIT(4) +#define UART_TX_FIFO_UNDERFLOW BIT(5) +#define UART_RX_FIFO_OVERFLOW BIT(6) +#define UART_RX_FIFO_UNDERFLOW BIT(7) +#define UART_FIFO_CONFIG_1 (0x84) +#define UART_TX_FIFO_CNT_SFT 0 +#define UART_TX_FIFO_CNT_MSK GENMASK(5, 0) +#define UART_RX_FIFO_CNT_MSK GENMASK(13, 8) +#define UART_TX_FIFO_TH_SFT 16 +#define UART_TX_FIFO_TH_MSK GENMASK(20, 16) +#define UART_RX_FIFO_TH_SFT 24 +#define UART_RX_FIFO_TH_MSK GENMASK(28, 24) +#define UART_FIFO_WDATA 0x88 +#define UART_FIFO_RDATA 0x8c +#define UART_FIFO_RDATA_MSK GENMASK(7, 0) + +#define BFLB_UART_MAXPORTS 8 +#define BFLB_UART_BAUD 2000000 +#define BFLB_UART_RX_FIFO_TH 7 +#define BFLB_UART_TX_FIFO_DEPTH 32 + +struct bflb_uart_port { + struct uart_port port; + struct clk *clk; +}; + +#define to_bflb_uart_port(p) (container_of((p), \ + struct bflb_uart_port, \ + port)) + +static struct bflb_uart_port *bflb_uart_ports[BFLB_UART_MAXPORTS]; + +static inline u32 rdl(struct uart_port *port, u32 reg) +{ + return readl_relaxed(port->membase + reg); +} + +static inline void wrl(struct uart_port *port, u32 reg, u32 value) +{ + writel_relaxed(value, port->membase + reg); +} + +static inline void wrb(struct uart_port *port, u32 reg, u8 value) +{ + writeb_relaxed(value, port->membase + reg); +} + +static unsigned int bflb_uart_tx_empty(struct uart_port *port) +{ + return (rdl(port, UART_FIFO_CONFIG_1) & UART_TX_FIFO_CNT_MSK) ? TIOCSER_TEMT : 0; +} + +static unsigned int bflb_uart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; +} + +static void bflb_uart_set_mctrl(struct uart_port *port, unsigned int sigs) +{ +} + +static void bflb_uart_start_tx(struct uart_port *port) +{ + u32 val; + + val = rdl(port, UART_UTX_CONFIG); + val |= UART_CR_UTX_EN; + wrl(port, UART_UTX_CONFIG, val); + + val = rdl(port, UART_INT_MASK); + val &= ~UART_UTX_END_INT; + wrl(port, UART_INT_MASK, val); + + val = rdl(port, UART_FIFO_CONFIG_1); + val &= ~UART_TX_FIFO_TH_MSK; + val |= 15 << UART_TX_FIFO_TH_SFT; + wrl(port, UART_FIFO_CONFIG_1, val); + + val = rdl(port, UART_INT_MASK); + val &= ~UART_UTX_FIFO_INT; + wrl(port, UART_INT_MASK, val); +} + +static void bflb_uart_stop_tx(struct uart_port *port) +{ + u32 val; + + val = rdl(port, UART_INT_MASK); + val |= UART_UTX_END_INT | UART_UTX_FIFO_INT; + wrl(port, UART_INT_MASK, val); +} + +static void bflb_uart_stop_rx(struct uart_port *port) +{ + u32 val; + + val = rdl(port, UART_URX_CONFIG); + val &= ~UART_CR_URX_EN; + wrl(port, UART_URX_CONFIG, val); + + val = rdl(port, UART_INT_MASK); + val |= UART_URX_FIFO_INT | UART_URX_RTO_INT | + UART_URX_FER_INT; + wrl(port, UART_INT_MASK, val); +} + +static void bflb_uart_break_ctl(struct uart_port *port, int break_state) +{ +} + +static void bflb_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + const struct ktermios *old) +{ + unsigned long flags; + u32 valt, valr, val; + unsigned int baud, min; + + valt = valr = 0; + + spin_lock_irqsave(&port->lock, flags); + + /* set data length */ + val = tty_get_char_size(termios->c_cflag) - 1; + valt |= (val << UART_CR_UTX_BIT_CNT_D_SFT); + + /* calculate parity */ + termios->c_cflag &= ~CMSPAR; /* no support mark/space */ + if (termios->c_cflag & PARENB) { + valt |= UART_CR_UTX_PRT_EN; + if (termios->c_cflag & PARODD) + valr |= UART_CR_UTX_PRT_SEL; + } + + valr = valt; + + /* calculate stop bits */ + if (termios->c_cflag & CSTOPB) + val = 2; + else + val = 1; + valt |= (val << UART_CR_UTX_BIT_CNT_P_SFT); + + /* flow control */ + if (termios->c_cflag & CRTSCTS) + valt |= UART_CR_UTX_CTS_EN; + + /* enable TX freerunning mode */ + valt |= UART_CR_UTX_FRM_EN; + + valt |= UART_CR_UTX_EN; + valr |= UART_CR_URX_EN; + + wrl(port, UART_UTX_CONFIG, valt); + wrl(port, UART_URX_CONFIG, valr); + + min = port->uartclk / (UART_CR_UTX_BIT_PRD + 1); + baud = uart_get_baud_rate(port, termios, old, min, 4000000); + + val = DIV_ROUND_CLOSEST(port->uartclk, baud) - 1; + val &= UART_CR_UTX_BIT_PRD; + val |= (val << 16); + wrl(port, UART_BIT_PRD, val); + + uart_update_timeout(port, termios->c_cflag, baud); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void bflb_uart_rx_chars(struct bflb_uart_port *bp) +{ + unsigned char ch, flag; + unsigned long status; + + while ((status = rdl(&bp->port, UART_FIFO_CONFIG_1)) & UART_RX_FIFO_CNT_MSK) { + ch = rdl(&bp->port, UART_FIFO_RDATA) & UART_FIFO_RDATA_MSK; + flag = TTY_NORMAL; + bp->port.icount.rx++; + + if (uart_handle_sysrq_char(&bp->port, ch)) + continue; + uart_insert_char(&bp->port, 0, 0, ch, flag); + } + + spin_unlock(&bp->port.lock); + tty_flip_buffer_push(&bp->port.state->port); + spin_lock(&bp->port.lock); +} + +/** + * bflb_uart_txfifo_space() - How much space is left int the TX FIFO? + * @bp: pointer to a struct bflb_uart_port + * + * Read the transmit FIFO count to find out how much space is left + * + * Returns: UART_TX_FIFO_CNT - count of space left in the TX FIFO + */ +static int bflb_uart_txfifo_space(struct bflb_uart_port *bp) +{ + return (rdl(&bp->port, UART_FIFO_CONFIG_1) + & UART_TX_FIFO_CNT_MSK) >> UART_TX_FIFO_CNT_SFT; +} + +/** + * bflb_uart_tx_char() - enqueue a byte to transmit onto the TX FIFO + * @bp: pointer to a struct bflb_uart_port + * @ch: character to transmit + * + * Enqueue a byte @ch onto the transmit FIFO, given a pointer @bp to the + * struct bflb_uart_port * to transmit on. + * + * Context: Any context. + */ +static void bflb_uart_tx_char(struct bflb_uart_port *bp, int ch) +{ + wrl(&bp->port, UART_FIFO_WDATA, ch); +} + +/** + * bflb_uart_tx_chars() - enqueue multiple bytes onto the TX FIFO + * @bp: pointer to a struct bflb_uart_port + * + * Transfer up to a TX FIFO size's worth of characters from the Linux serial + * transmit buffer to the BFLB UART TX FIFO. + * + * Context: Any context. Expects @bp->port.lock to be held by caller. + */ +static void bflb_uart_tx_chars(struct bflb_uart_port *bp) +{ + u8 ch; + + uart_port_tx_limited(&bp->port, ch, BFLB_UART_TX_FIFO_DEPTH, + bflb_uart_txfifo_space(bp), + bflb_uart_tx_char(bp, ch), + ({})); +} + +static irqreturn_t bflb_uart_interrupt(int irq, void *data) +{ + struct bflb_uart_port *bp = data; + u32 isr, val; + + isr = rdl(&bp->port, UART_INT_STS); + wrl(&bp->port, UART_INT_CLEAR, isr); + + isr &= ~rdl(&bp->port, UART_INT_MASK); + + spin_lock(&bp->port.lock); + + if (isr & UART_URX_FER_INT) { + /* RX FIFO error interrupt */ + val = rdl(&bp->port, UART_FIFO_CONFIG_0); + if (val & UART_RX_FIFO_OVERFLOW) + bp->port.icount.overrun++; + + val |= UART_RX_FIFO_CLR; + wrl(&bp->port, UART_FIFO_CONFIG_0, val); + } + + if (isr & (UART_URX_FIFO_INT | UART_URX_RTO_INT)) { + bflb_uart_rx_chars(bp); + } + if (isr & (UART_UTX_FIFO_INT | UART_UTX_END_INT)) { + bflb_uart_tx_chars(bp); + } + + spin_unlock(&bp->port.lock); + + return IRQ_RETVAL(isr); +} + +static void bflb_uart_config_port(struct uart_port *port, int flags) +{ + u32 val; + + port->type = PORT_BFLB; + + /* Clear mask, so no surprise interrupts. */ + val = rdl(port, UART_INT_MASK); + val |= UART_UTX_END_INT; + val |= UART_UTX_FIFO_INT; + val |= UART_URX_FIFO_INT; + val |= UART_URX_RTO_INT; + val |= UART_URX_FER_INT; + wrl(port, UART_INT_MASK, val); +} + +static int bflb_uart_startup(struct uart_port *port) +{ + unsigned long flags; + u32 val; + struct bflb_uart_port *bp = to_bflb_uart_port(port); + int ret; + + dev_dbg(port->dev, "startup %s\n", port->name); + + spin_lock_irqsave(&port->lock, flags); + + ret = devm_request_irq(port->dev, port->irq, bflb_uart_interrupt, + IRQF_SHARED, port->name, bp); + if (ret) { + dev_err(port->dev, "fail to request serial irq %d, ret=%d\n", + port->irq, ret); + return ret; + } + + val = rdl(port, UART_INT_MASK); + val |= 0xfff; + wrl(port, UART_INT_MASK, val); + + wrl(port, UART_DATA_CONFIG, 0); + wrl(port, UART_SW_MODE, 0); + wrl(port, UART_URX_RTO_TIMER, 0x4f); + + val = rdl(port, UART_FIFO_CONFIG_1); + val &= ~UART_RX_FIFO_TH_MSK; + val |= BFLB_UART_RX_FIFO_TH << UART_RX_FIFO_TH_SFT; + wrl(port, UART_FIFO_CONFIG_1, val); + + /* Unmask RX interrupts now */ + val = rdl(port, UART_INT_MASK); + val &= ~UART_URX_FIFO_INT; + val &= ~UART_URX_RTO_INT; + val &= ~UART_URX_FER_INT; + wrl(port, UART_INT_MASK, val); + + val = rdl(port, UART_UTX_CONFIG); + val |= UART_CR_UTX_EN; + wrl(port, UART_UTX_CONFIG, val); + val = rdl(port, UART_URX_CONFIG); + val |= UART_CR_URX_EN; + wrl(port, UART_URX_CONFIG, val); + + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void bflb_uart_shutdown(struct uart_port *port) +{ + unsigned long flags; + struct bflb_uart_port *bp = to_bflb_uart_port(port); + + dev_dbg(port->dev, "shutdown %s\n", port->name); + + spin_lock_irqsave(&port->lock, flags); + /* mask all interrupts now */ + wrl(port, UART_INT_MASK, UART_UTX_END_INT | UART_URX_END_INT); + devm_free_irq(port->dev, port->irq, bp); + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *bflb_uart_type(struct uart_port *port) +{ + return (port->type == PORT_BFLB) ? "BFLB UART" : NULL; +} + +static int bflb_uart_request_port(struct uart_port *port) +{ + /* UARTs always present */ + return 0; +} + +static void bflb_uart_release_port(struct uart_port *port) +{ + /* Nothing to release... */ +} + +static int bflb_uart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (ser->type != PORT_UNKNOWN && ser->type != PORT_BFLB) + return -EINVAL; + return 0; +} + +static const struct uart_ops bflb_uart_ops = { + .tx_empty = bflb_uart_tx_empty, + .get_mctrl = bflb_uart_get_mctrl, + .set_mctrl = bflb_uart_set_mctrl, + .start_tx = bflb_uart_start_tx, + .stop_tx = bflb_uart_stop_tx, + .stop_rx = bflb_uart_stop_rx, + .break_ctl = bflb_uart_break_ctl, + .startup = bflb_uart_startup, + .shutdown = bflb_uart_shutdown, + .set_termios = bflb_uart_set_termios, + .type = bflb_uart_type, + .request_port = bflb_uart_request_port, + .release_port = bflb_uart_release_port, + .config_port = bflb_uart_config_port, + .verify_port = bflb_uart_verify_port, +}; + +#ifdef CONFIG_SERIAL_BFLB_CONSOLE +static void bflb_console_putchar(struct uart_port *port, unsigned char ch) +{ + while (!(rdl(port, UART_FIFO_CONFIG_1) & UART_TX_FIFO_CNT_MSK)) + cpu_relax(); + wrb(port, UART_FIFO_WDATA, ch); +} + +/* + * Interrupts are disabled on entering + */ +static void bflb_uart_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *port = &bflb_uart_ports[co->index]->port; + u32 status, reg, mask; + + /* save then disable interrupts */ + mask = rdl(port, UART_INT_MASK); + reg = -1; + wrl(port, UART_INT_MASK, reg); + + /* Make sure that tx is enabled */ + reg = rdl(port, UART_UTX_CONFIG); + reg |= UART_CR_UTX_EN; + wrl(port, UART_UTX_CONFIG, reg); + + uart_console_write(port, s, count, bflb_console_putchar); + + /* wait for TX done */ + do { + status = rdl(port, UART_STATUS); + } while ((status & UART_STS_UTX_BUS_BUSY)); + + /* restore IRQ mask */ + wrl(port, UART_INT_MASK, mask); +} + +static int bflb_uart_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + struct bflb_uart_port *bp; + int baud = BFLB_UART_BAUD; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + u32 val; + + if (co->index >= BFLB_UART_MAXPORTS || co->index < 0) + return -EINVAL; + + bp = bflb_uart_ports[co->index]; + if (!bp) + /* Port not initialized yet - delay setup */ + return -ENODEV; + + port = &bp->port; + + val = rdl(port, UART_UTX_CONFIG); + val |= UART_CR_UTX_EN; + wrl(port, UART_UTX_CONFIG, val); + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static struct uart_driver bflb_uart_driver; +static struct console bflb_uart_console = { + .name = "ttyS", + .write = bflb_uart_console_write, + .device = uart_console_device, + .setup = bflb_uart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &bflb_uart_driver, +}; + +static int __init bflb_uart_console_init(void) +{ + register_console(&bflb_uart_console); + return 0; +} +console_initcall(bflb_uart_console_init); + +#define BFLB_UART_CONSOLE (&bflb_uart_console) + +static void bflb_uart_earlycon_write(struct console *co, const char *s, + unsigned int count) +{ + struct earlycon_device *dev = co->data; + + uart_console_write(&dev->port, s, count, bflb_console_putchar); +} + +static int __init bflb_uart_earlycon_setup(struct earlycon_device *dev, + const char *options) +{ + if (!dev->port.membase) + return -ENODEV; + + dev->con->write = bflb_uart_earlycon_write; + + return 0; +} +OF_EARLYCON_DECLARE(bflb_uart, "bflb,bl808-uart", bflb_uart_earlycon_setup); + +#else + +#define BFLB_UART_CONSOLE NULL + +#endif /* CONFIG_SERIAL_BFLB_CONSOLE */ + +static struct uart_driver bflb_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "bflb_uart", + .dev_name = "ttyS", + .nr = BFLB_UART_MAXPORTS, + .cons = BFLB_UART_CONSOLE, +}; + +static int bflb_uart_probe(struct platform_device *pdev) +{ + struct uart_port *port; + struct bflb_uart_port *bp; + struct resource *res; + int index, irq; + + index = of_alias_get_id(pdev->dev.of_node, "serial"); + if (unlikely(index < 0 || index >= BFLB_UART_MAXPORTS)) { + dev_err(&pdev->dev, "got a wrong serial alias id %d\n", index); + return -EINVAL; + } + + bp = devm_kzalloc(&pdev->dev, sizeof(*bp), GFP_KERNEL); + if (!bp) + return -ENOMEM; + + bflb_uart_ports[index] = bp; + platform_set_drvdata(pdev, bp); + port = &bp->port; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + port->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(port->membase)) + return PTR_ERR(port->membase); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + port->mapbase = res->start; + port->irq = irq; + port->line = index; + port->type = PORT_BFLB; + port->iotype = UPIO_MEM; + port->fifosize = BFLB_UART_TX_FIFO_DEPTH; + port->ops = &bflb_uart_ops; + port->flags = UPF_BOOT_AUTOCONF; + port->dev = &pdev->dev; + port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_BFLB_CONSOLE); + + bp->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(bp->clk)) + return PTR_ERR(bp->clk); + port->uartclk = clk_get_rate(bp->clk); + + return uart_add_one_port(&bflb_uart_driver, port); +} + +static int bflb_uart_remove(struct platform_device *pdev) +{ + struct bflb_uart_port *bp = platform_get_drvdata(pdev); + + uart_remove_one_port(&bflb_uart_driver, &bp->port); + bflb_uart_ports[bp->port.line] = NULL; + + return 0; +} + +static const struct of_device_id bflb_uart_match[] = { + { + .compatible = "bflb,bl808-uart", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bflb_uart_match); + +static struct platform_driver bflb_uart_platform_driver = { + .probe = bflb_uart_probe, + .remove = bflb_uart_remove, + .driver = { + .name = "bflb_uart", + .of_match_table = of_match_ptr(bflb_uart_match), + }, +}; + +static int __init bflb_uart_init(void) +{ + int ret; + + ret = uart_register_driver(&bflb_uart_driver); + if (ret) + return ret; + + ret = platform_driver_register(&bflb_uart_platform_driver); + if (ret) + uart_unregister_driver(&bflb_uart_driver); + + return ret; +} + +static void __exit bflb_uart_exit(void) +{ + platform_driver_unregister(&bflb_uart_platform_driver); + uart_unregister_driver(&bflb_uart_driver); +} + +module_init(bflb_uart_init); +module_exit(bflb_uart_exit); + +MODULE_DESCRIPTION("Bouffalolab UART driver"); +MODULE_AUTHOR("Jisheng Zhang "); +MODULE_LICENSE("GPL"); diff --git a/include/dt-bindings/mailbox/bflb-ipc.h b/include/dt-bindings/mailbox/bflb-ipc.h new file mode 100644 index 00000000000000..02848cf7a30153 --- /dev/null +++ b/include/dt-bindings/mailbox/bflb-ipc.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +/* + * Copyright (C) 2023 Allen Martin + */ + +#ifndef __DT_BINDINGS_MAILBOX_BFLB_IPC_H +#define __DT_BINDINGS_MAILBOX_BFLB_IPC_H + +/* Source processor */ +#define BFLB_IPC_SOURCE_M0 0 +#define BFLB_IPC_SOURCE_LP 1 +#define BFLB_IPC_SOURCE_D0 2 + +/* Peripheral device ID */ +#define BFLB_IPC_DEVICE_SDHCI 0 +#define BFLB_IPC_DEVICE_UART2 1 +#define BFLB_IPC_DEVICE_USB 2 +#define BFLB_IPC_DEVICE_EMAC 3 +#define BFLB_IPC_DEVICE_MBOX_RX 5 +#define BFLB_IPC_DEVICE_MBOX_TX 6 + +/* TARGET for Sending to other processors, continues from the BFLB_IPC_SOURCE_* range */ +#define BFLB_IPC_TARGET_M0 3 +#define BFLB_IPC_TARGET_LP 4 +#define BFLB_IPC_TARGET_D0 5 + +/* MailBox Service */ +#define BFLB_IPC_MBOX_VIRTIO 1 + + +/* Operations for MBOX_VIRTIO */ +#define BFLB_IPC_MBOX_VIRTIO_OP_KICK 1 + + +#endif diff --git a/include/linux/bflb-mailbox.h b/include/linux/bflb-mailbox.h new file mode 100644 index 00000000000000..0105678d52d8e0 --- /dev/null +++ b/include/linux/bflb-mailbox.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * BL808 mailbox message format + * + * Copyright (C) 2021 The Asahi Linux Contributors + */ + +#ifndef _LINUX_BFLB_MAILBOX_H_ +#define _LINUX_BFLB_MAILBOX_H_ + +#include + +/* encodes a single 32bit message sent over the single channel */ +struct bflb_mbox_msg { + u32 param; + u32 id; +}; + +#endif diff --git a/include/linux/usb/ehci_def.h b/include/linux/usb/ehci_def.h index fbabadd3b372c2..f51fc87e0110cd 100644 --- a/include/linux/usb/ehci_def.h +++ b/include/linux/usb/ehci_def.h @@ -101,21 +101,10 @@ struct ehci_regs { /* ASYNCLISTADDR: offset 0x18 */ u32 async_next; /* address of next async queue head */ - u32 reserved1[2]; - - /* TXFILLTUNING: offset 0x24 */ - u32 txfill_tuning; /* TX FIFO Tuning register */ -#define TXFIFO_DEFAULT (8<<16) /* FIFO burst threshold 8 */ - - u32 reserved2[6]; - - /* CONFIGFLAG: offset 0x40 */ - u32 configured_flag; -#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ - + u32 reserved1[1]; union { - /* PORTSC: offset 0x44 */ - u32 port_status[HCS_N_PORTS_MAX]; /* up to N_PORTS */ + /* PORTSC: offset 0x20 */ + u32 port_status[1]; /* up to N_PORTS */ /* EHCI 1.1 addendum */ #define PORTSC_SUSPEND_STS_ACK 0 #define PORTSC_SUSPEND_STS_NYET 1 @@ -163,6 +152,17 @@ struct ehci_regs { #define USBMODE_CM_IDLE (0<<0) /* idle state */ }; + /* TXFILLTUNING: offset 0x24 */ + u32 txfill_tuning; /* TX FIFO Tuning register */ +#define TXFIFO_DEFAULT (8<<16) /* FIFO burst threshold 8 */ + + u32 reserved2[6]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* Moorestown has some non-standard registers, partially due to the fact that * its EHCI controller has both TT and LPM support. HOSTPCx are extensions to * PORTSCx diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 3ba34d8378bd0e..dabbb5ea285738 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -276,4 +276,7 @@ /* Sunplus UART */ #define PORT_SUNPLUS 123 +/* Bouffalolab UART */ +#define PORT_BFLB 124 + #endif /* _UAPILINUX_SERIAL_CORE_H */