diff --git a/Documentation/devicetree/bindings/clock/litex,clock.yaml b/Documentation/devicetree/bindings/clock/litex,clock.yaml new file mode 100644 index 00000000000000..619ee31afa34b0 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/litex,clock.yaml @@ -0,0 +1,224 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/litex,clock.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX clock control driver + +description: | + Common clock driver with MMCM unit for dynamic reconfiguration + of up to 7 clock outputs with ability to change frequency, duty + cycle and phase offset + +maintainers: + - Karol Gugala + - Mateusz Holenko + +properties: + compatible: + const: litex,clock + + reg: + description: Base address and lengths of the register space + + "#clock-cells": + description: + Number of cells in a clock specifier; + Typically 0 for nodes with a single clock output + and 1 for nodes with multiple clock outputs. + const: 1 + + "#address-cells": + description: + Number of cells that are needed to form the base address + part in the reg property. + const: 1 + + "#size-cells": + description: + Used to state how many cells are in each field of a reg property + const: 0 + + clock-output-names: + description: + List of strings of clock output signal names indexed + by the first cell in the clock specifier. + minItems: 1 + maxItems: 7 + items: + - const: CLKOUT0 + - const: CLKOUT1 + - const: CLKOUT2 + - const: CLKOUT3 + - const: CLKOUT4 + - const: CLKOUT5 + - const: CLKOUT6 + + litex,nclkout: + description: Number of desired clock outputs + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 7 + + litex,lock-timeout: + description: Number of ms to wait for MMCM to assert LOCK signal + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + + litex,drdy-timeout: + description: Number of ms to wait for MMCM to assert DRDY signal + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + + litex,sys-clock-freq: + description: System clock frequency + $ref: /schemas/types.yaml#/definitions/uint32 + +patternProperties: + "^CLKOUT[0-6]$": + description: + Child node representing configurable clock outputs of MMCM unit + type: object + + properties: + compatible: + const: litex,clock + + reg: + description: clock output ID, zero-based numbering + + litex,clock-frequency: + description: default frequency in Hz for clock output + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1587000 + maximum: 100000000 + + litex,clock-phase: + description: default phase offset given in degrees + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 359 + + litex,clock-duty-num: + description: default duty cycle numerator value + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 100 + + litex,clock-duty-den: + description: default duty cycle denominator value + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 100 + + required: + - compatible + - "#clock-cells" + - clock-output-names + - reg + - litex,clock-frequency + - litex,clock-phase + - litex,clock-duty-num + - litex,clock-duty-den + +required: + - compatible + - reg + - "#clock-cells" + - "#address-cells" + - "#size-cells" + - clock-output-names + - litex,nclkout + - CLKOUTx + +additionalProperties: false + +examples: + - | + clk0: clock-controller@f0003000 { + compatible = "litex,clk"; + reg = <0x0 0xf0003000>, <0x0 0x100>; + #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + clock-output-names = "CLKOUT0", + "CLKOUT1", + "CLKOUT2", + "CLKOUT3", + "CLKOUT4", + "CLKOUT5", + "CLKOUT6"; + litex,nclkout = <7>; + + CLKOUT0: CLKOUT0@0 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT0"; + reg = <0>; + litex,clock-frequency = <50000000>; + litex,clock-phase = <0>; + litex,clock-duty = <50>; + }; + + CLKOUT1: CLKOUT1@1 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT1"; + reg = <1>; + litex,clock-frequency = <50000000>; + litex,clock-phase = <90>; + litex,clock-duty = <50>; + }; + + CLKOUT2: CLKOUT2@2 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT2"; + reg = <2>; + litex,clock-frequency = <25000000>; + litex,clock-phase = <0>; + litex,clock-duty = <25>; + }; + + CLKOUT3: CLKOUT3@3 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT3"; + reg = <3>; + litex,clock-frequency = <12500000>; + litex,clock-phase = <0>; + litex,clock-duty = <75>; + }; + + CLKOUT4: CLKOUT4@4 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT4"; + reg = <4>; + litex,clock-frequency = <6250000>; + litex,clock-phase = <0>; + litex,clock-duty = <50>; + }; + + CLKOUT5: CLKOUT5@5 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT5"; + reg = <5>; + litex,clock-frequency = <3125000>; + litex,clock-phase = <0>; + litex,clock-duty = <50>; + }; + + CLKOUT6: CLKOUT6@6 { + compatible = "litex,clk"; + #clock-cells = <0>; + clock-output-names = "CLKOUT6"; + reg = <6>; + litex,clock-frequency = <1562500>; + litex,clock-phase = <0>; + litex,clock-duty = <5>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/fpga/litex-fpga.txt b/Documentation/devicetree/bindings/fpga/litex-fpga.txt new file mode 100644 index 00000000000000..5e9c4893ddc453 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/litex-fpga.txt @@ -0,0 +1,12 @@ +LiteX ICAPBitstream fpga manager + +Required properties: +- compatible: should be "litex,fpga-icap" +- reg: base address of configuration registers with length + +Examples: + +fpga_man: icap@f0007000 { + compatible = "litex,fpga-icap"; + reg = <0x0 0xf0007000 0x0 0x14>; +}; diff --git a/Documentation/devicetree/bindings/gpio/litex,gpio.txt b/Documentation/devicetree/bindings/gpio/litex,gpio.txt new file mode 100644 index 00000000000000..ab9aef6c6eb7fb --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/litex,gpio.txt @@ -0,0 +1,23 @@ +LiteX GPIO controller + +Required properties: +- compatible: should be "litex,gpio" +- reg: base address and length of the register +- litex,ngpio: number of gpio pins in port +- litex,direction: direction of gpio port, should be "in" or "out" + +Examples: + +gpio@f0003000 { + compatible = "litex,gpio"; + reg = <0x0 0xf0003800 0x0 0x1>; + litex,ngpio = <4>; + litex,direction = "in"; +}; + +gpio@f0003800 { + compatible = "litex,gpio"; + reg = <0x0 0xf0003800 0x0 0x1>; + litex,ngpio = <4>; + litex,direction = "out"; +}; diff --git a/Documentation/devicetree/bindings/gpu/litex,litevideo.txt b/Documentation/devicetree/bindings/gpu/litex,litevideo.txt new file mode 100644 index 00000000000000..a6e8f61f289d24 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/litex,litevideo.txt @@ -0,0 +1,34 @@ +DRM LiteX LiteVideo driver + +Required properties: +- compatible: should be "litex,litevideo" +- reg: base address of configuration registers with length +- litevideo,pixel-clock: pixel clock frequency in kHz +- litevideo,h-active: horizontal active pixels +- litevideo,h-blanking: total horizontal blanking +- litevideo,h-sync: horizontal sync width +- litevideo,h-front-porch: horizontal front porch +- litevideo,v-active: vertical active pixels +- litevideo,v-blanking: vertical total blanking +- litevideo,v-sync: vertical sync width +- litevideo,v-front-porch: vertical front porch +- litevideo,dma-offset: dma offset in memory +- litevideo,dma-length: size of memory used for storing the image + +Examples: + +litevideo0: gpu@f0009800 { + compatible = "litex,litevideo"; + reg = <0x0 0xf0009800 0x0 0x100>; + litevideo,pixel-clock = <148500>; + litevideo,h-active = <1920>; + litevideo,h-blanking = <280>; + litevideo,h-sync = <44>; + litevideo,h-front-porch = <148>; + litevideo,v-active = <1080>; + litevideo,v-blanking = <45>; + litevideo,v-sync = <5>; + litevideo,v-front-porch = <36>; + litevideo,dma-offset = <0x8000000>; + litevideo,dma-length = <0x7e9000>; +}; diff --git a/Documentation/devicetree/bindings/hwmon/litex-hwmon.txt b/Documentation/devicetree/bindings/hwmon/litex-hwmon.txt new file mode 100644 index 00000000000000..be2ceba1663cff --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/litex-hwmon.txt @@ -0,0 +1,12 @@ +LiteX XADC hwmon controller + +Required properties: +- compatible: should be "litex,hwmon-xadc" +- reg: base address of configuration registers with length + +Examples: + +hwmon@f0003000 { + compatible = "litex,hwmon-xadc"; + reg = <0x0 0xf0003000 0x0 0x20>; +}; diff --git a/Documentation/devicetree/bindings/i2c/i2c-litex.txt b/Documentation/devicetree/bindings/i2c/i2c-litex.txt new file mode 100644 index 00000000000000..e78a87155a9544 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-litex.txt @@ -0,0 +1,12 @@ +LiteX I2C controller + +Required properties: +- compatible: should be "litex,i2c" +- reg: base address of configuration registers with length + +Examples: + +i2c@f0003000 { + compatible = "litex,i2c"; + reg = <0x0 0xf0003000 0x0 0x5>; +}; diff --git a/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml b/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml new file mode 100644 index 00000000000000..e688584c27f83f --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/litex,spiflash.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0 + +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mtd/litex,spiflash.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX SPI Flash controller + +maintainers: + - Karol Gugala + - Mateusz Holenko + +description: | + LiteX SPI Flash controller is a part of LiteX FPGA SoC builder. It supports + multiple CPU architectures, currently including e.g. OpenRISC and RISC-V. + +allOf: + - $ref: "/schemas/spi/spi-controller.yaml#" + +properties: + compatible: + const: litex,spiflash + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + +patternProperties: + "flash@[0-9a-f]+$": + type: object + properties: + compatible: + enum: + - jedec,spi-nor + +unevaluatedProperties: false + +examples: + - | + spiflash: spi@f0005800 { + compatible = "litex,spiflash"; + reg = <0xf0005800 0xc>; + #address-cells = <1>; + #size-cells = <0>; + + flash: flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + }; + }; diff --git a/Documentation/devicetree/bindings/pwm/litex,pwm.txt b/Documentation/devicetree/bindings/pwm/litex,pwm.txt new file mode 100644 index 00000000000000..403de8b69b963f --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/litex,pwm.txt @@ -0,0 +1,19 @@ +LiteX PWM controller + +Required properties: +- compatible: should be "litex,pwm". +- reg: base address and length of the register set for this device. +- clock: frequency of the main clock. +- #pwm-cells: should be 6. See pwm.txt in this directory for a description of + the cells format. + + +Examples: + +pwm@f0003000 { + compatible = "litex,pwm"; + reg = <0x0 0xf0003000 0x0 0x24>; + clock = <100000000>; + #pwm-cells = <6>; + status = "okay"; +}; diff --git a/Documentation/devicetree/bindings/spi/litex,litespi.txt b/Documentation/devicetree/bindings/spi/litex,litespi.txt new file mode 100644 index 00000000000000..d5ddb4ec6ce493 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/litex,litespi.txt @@ -0,0 +1,19 @@ +LiteSPI controller + +Required properties: +- compatible should be "litex,litespi" +- reg: base address and length of the register set for this device +- litespi,max-bpw: maximum value of bits per word +- litespi,sck-frequency: SPI clock frequency +- litespi,num-cs: number of chip select lines available + +Example: + +litespi0: spi@f0005800 { + compatible = "litex,litespi"; + reg = <0xf0005800 0x100>; + + litespi,max-bpw = <8>; + litespi,sck-frequency = <1000000>; + litespi,num-cs = <1>; +}; diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 87d7b52f278f0c..473ad217a6ca24 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -97,6 +97,7 @@ config RISCV select SYSCTL_EXCEPTION_TRACE select THREAD_INFO_IN_TASK select UACCESS_MEMCPY if !MMU + select HANDLE_DOMAIN_IRQ # FIXME: should go in Kconfigs.soc !!! config ARCH_MMAP_RND_BITS_MIN default 18 if 64BIT diff --git a/arch/riscv/configs/litex_rocket_defconfig b/arch/riscv/configs/litex_rocket_defconfig new file mode 100644 index 00000000000000..0a26721a9073a8 --- /dev/null +++ b/arch/riscv/configs/litex_rocket_defconfig @@ -0,0 +1,113 @@ +CONFIG_DEFAULT_HOSTNAME="litex" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_SGETMASK_SYSCALL=y +CONFIG_BPF_SYSCALL=y +CONFIG_EMBEDDED=y +CONFIG_HZ_100=y +CONFIG_PARTITION_ADVANCED=y +# CONFIG_EFI_PARTITION is not set +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_INET_DIAG is not set +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_NBD=y +CONFIG_NETDEVICES=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_BROADCOM is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +CONFIG_LITEX_LITEETH=y +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NI 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_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_WLAN is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_CONSOLE_TRANSLATIONS is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_SERIAL_EARLYCON_RISCV_SBI=y +CONFIG_HVC_RISCV_SBI=y +# CONFIG_HW_RANDOM is not set +CONFIG_SPI=y +CONFIG_SPI_LITESPI=y +# CONFIG_HWMON is not set +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE_COLUMNS=128 +CONFIG_DUMMY_CONSOLE_ROWS=32 +# CONFIG_HID is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_MMC=y +# CONFIG_PWRSEQ_EMMC is not set +# CONFIG_PWRSEQ_SIMPLE is not set +CONFIG_MMC_SPI=y +CONFIG_MMC_LITEX=y +CONFIG_LITEX_SOC_CONTROLLER=y +CONFIG_SIFIVE_PLIC=y +CONFIG_RAS=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_F2FS_FS=y +# CONFIG_F2FS_FS_POSIX_ACL is not set +# CONFIG_DNOTIFY is not set +CONFIG_FANOTIFY=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_UTF8=y +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +# CONFIG_MISC_FILESYSTEMS is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y +CONFIG_SECURITY=y +CONFIG_CRYPTO_RSA=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=15 +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7 +CONFIG_DEBUG_INFO=y diff --git a/arch/riscv/configs/litex_rocket_initramfs.config b/arch/riscv/configs/litex_rocket_initramfs.config new file mode 100644 index 00000000000000..658bbdb98e9d27 --- /dev/null +++ b/arch/riscv/configs/litex_rocket_initramfs.config @@ -0,0 +1 @@ +CONFIG_INITRAMFS_SOURCE="initramfs.cpio" diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index a588d56502d40a..6898b3564d10ae 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -376,6 +376,12 @@ config COMMON_CLK_K210 help Support for the Canaan Kendryte K210 RISC-V SoC clocks. +config COMMON_CLK_LITEX + tristate "LiteX Clock control support" + depends on COMMON_CLK && OF && LITEX_SOC_CONTROLLER + help + Clock control support for LiteX SoC builder, utilising MMCM unit and DRP registers + source "drivers/clk/actions/Kconfig" source "drivers/clk/analogbits/Kconfig" source "drivers/clk/baikal-t1/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index b22ae4f81e0b33..e6d0a0e8cb0fb1 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o +obj-$(CONFIG_COMMON_CLK_LITEX) += clk-litex.o obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o diff --git a/drivers/clk/clk-litex.c b/drivers/clk/clk-litex.c new file mode 100644 index 00000000000000..2e1f89d795b5e0 --- /dev/null +++ b/drivers/clk/clk-litex.c @@ -0,0 +1,1786 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Antmicro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ +#include "clk-litex.h" +#include +#include +#include +#include +#include + +struct litex_drp_reg { + u32 offset; + u32 size; +}; + +struct litex_drp_reg drp[] = { + {DRP_OF_RESET, DRP_SIZE_RESET}, + {DRP_OF_READ, DRP_SIZE_READ}, + {DRP_OF_WRITE, DRP_SIZE_WRITE}, + {DRP_OF_DRDY, DRP_SIZE_DRDY}, + {DRP_OF_ADR, DRP_SIZE_ADR}, + {DRP_OF_DAT_W, DRP_SIZE_DAT_W}, + {DRP_OF_DAT_R, DRP_SIZE_DAT_R}, + {DRP_OF_LOCKED, DRP_SIZE_LOCKED}, +}; + +struct litex_clk_device { + void __iomem *base; + int is_clkout; + struct clk_hw clk_hw; + struct litex_clk_clkout *clkouts; + u32 nclkout; + u32 lock_timeout; + u32 drdy_timeout; + u32 sys_clk_freq; +}; + +struct litex_clk_clkout_addr { + u8 reg1; + u8 reg2; +}; + +struct litex_clk_regs_addr { + struct litex_clk_clkout_addr clkout[CLKOUT_MAX]; +}; + +struct litex_clk_clkout { + void __iomem *base; + int is_clkout; + struct clk_hw clk_hw; + u32 id; + u32 default_freq; + u32 default_phase; + u32 default_duty_num; + u32 default_duty_den; + u32 lock_timeout; + u32 drdy_timeout; + u32 sys_clk_freq; +}; + +struct litex_clk_regs_addr litex_clk_regs_addr_init(void) +{ + struct litex_clk_regs_addr m; + u32 i, addr; + + addr = CLKOUT0_REG1; + for (i = 0; i <= CLKOUT_MAX; i++) { + if (i == 5) { + /* + *special case because CLKOUT5 have its reg addresses + *placed lower than other CLKOUTs + */ + m.clkout[5].reg1 = CLKOUT5_REG1; + m.clkout[5].reg2 = CLKOUT5_REG2; + } else { + m.clkout[i].reg1 = addr; + addr++; + m.clkout[i].reg2 = addr; + addr++; + } + } + return m; +} + +/* + * This code is taken from: + * https://github.com/torvalds/linux/blob/master/drivers/clk/clk-axi-clkgen.c + * + * Copyright 2012-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * FIXME: make this code common + */ + +/* MMCM loop filter lookup table */ +static u32 litex_clk_lookup_filter(u32 glob_mul) +{ + switch (glob_mul) { + case 0: + return 0x01001990; + case 1: + return 0x01001190; + case 2: + return 0x01009890; + case 3: + return 0x01001890; + case 4: + return 0x01008890; + case 5 ... 8: + return 0x01009090; + case 9 ... 11: + return 0x01000890; + case 12: + return 0x08009090; + case 13 ... 22: + return 0x01001090; + case 23 ... 36: + return 0x01008090; + case 37 ... 46: + return 0x08001090; + default: + return 0x08008090; + } +} + +/* MMCM lock detection lookup table */ +static const u32 litex_clk_lock_table[] = { + 0x060603e8, 0x060603e8, 0x080803e8, 0x0b0b03e8, + 0x0e0e03e8, 0x111103e8, 0x131303e8, 0x161603e8, + 0x191903e8, 0x1c1c03e8, 0x1f1f0384, 0x1f1f0339, + 0x1f1f02ee, 0x1f1f02bc, 0x1f1f028a, 0x1f1f0271, + 0x1f1f023f, 0x1f1f0226, 0x1f1f020d, 0x1f1f01f4, + 0x1f1f01db, 0x1f1f01c2, 0x1f1f01a9, 0x1f1f0190, + 0x1f1f0190, 0x1f1f0177, 0x1f1f015e, 0x1f1f015e, + 0x1f1f0145, 0x1f1f0145, 0x1f1f012c, 0x1f1f012c, + 0x1f1f012c, 0x1f1f0113, 0x1f1f0113, 0x1f1f0113, +}; + +/* Helper function for lock lookup table */ +static u32 litex_clk_lookup_lock(u32 glob_mul) +{ + if (glob_mul < ARRAY_SIZE(litex_clk_lock_table)) + return litex_clk_lock_table[glob_mul]; + return 0x1f1f00fa; +} +/* End of copied code */ + +static inline struct litex_clk_device *clk_hw_to_litex_clk_device + (struct clk_hw *clk_hw) +{ + return container_of(clk_hw, struct litex_clk_device, clk_hw); +} + +static inline struct litex_clk_clkout *clk_hw_to_litex_clk_clkout + (struct clk_hw *clk_hw) +{ + return container_of(clk_hw, struct litex_clk_clkout, clk_hw); +} + +/* Clock control driver functions */ +static inline void litex_clk_set_reg(struct clk_hw *clk_hw, u32 reg, u32 val) +{ + struct litex_clk_clkout *l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + _litex_set_reg(l_clk->base + drp[reg].offset, drp[reg].size, val); +} + +static inline u32 litex_clk_get_reg(struct clk_hw *clk_hw, u32 reg) +{ + struct litex_clk_clkout *l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + return _litex_get_reg(l_clk->base + drp[reg].offset, drp[reg].size); +} + +static inline void litex_clk_assert_reg(struct clk_hw *clk_hw, u32 reg) +{ + int assert = (1 << (drp[reg].size * BITS_PER_BYTE)) - 1; + + litex_clk_set_reg(clk_hw, reg, assert); +} + +static inline void litex_clk_deassert_reg(struct clk_hw *clk_hw, u32 reg) +{ + litex_clk_set_reg(clk_hw, reg, ZERO_REG); +} + +static int litex_clk_wait_lock(struct clk_hw *clk_hw) +{ + struct litex_clk_clkout *l_clk; + u32 timeout; + + l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + /* Check if clk_hw is global or clkout specific */ + if (l_clk->is_clkout) { + timeout = l_clk->lock_timeout; + } else { + /* clk hw is global */ + struct litex_clk_device *l_dev; + + l_dev = clk_hw_to_litex_clk_device(clk_hw); + timeout = l_dev->lock_timeout; + } + + /*Waiting for LOCK signal to assert in MMCM*/ + while (!litex_clk_get_reg(clk_hw, DRP_LOCKED) && timeout) { + timeout--; + usleep_range(900, 1000); + } + if (timeout == 0) { + pr_err("CLKOUT%d: %s timeout", l_clk->id, __func__); + return -ETIME; + } + return 0; +} + +static int litex_clk_wait_drdy(struct clk_hw *clk_hw) +{ + struct litex_clk_clkout *l_clk; + u32 timeout; + + l_clk = clk_hw_to_litex_clk_clkout(clk_hw); + + /* Check if clk_hw is global or clkout specific */ + if (l_clk->is_clkout) { + timeout = l_clk->drdy_timeout; + } else { + /* clk hw is global */ + struct litex_clk_device *l_dev; + + l_dev = clk_hw_to_litex_clk_device(clk_hw); + timeout = l_dev->drdy_timeout; + } + + /*Waiting for DRDY signal to assert in MMCM*/ + while (!litex_clk_get_reg(clk_hw, DRP_DRDY) && timeout) { + timeout--; + usleep_range(900, 1000); + } + if (timeout == 0) { + pr_err("CLKOUT%d: %s timeout", l_clk->id, __func__); + return -ETIME; + } + return 0; +} + +/* Read value written in given internal MMCM register*/ +static int litex_clk_get_DO(struct clk_hw *clk_hw, u8 clk_reg_addr, u16 *res) +{ + int ret; + + litex_clk_set_reg(clk_hw, DRP_ADR, clk_reg_addr); + litex_clk_assert_reg(clk_hw, DRP_READ); + + litex_clk_deassert_reg(clk_hw, DRP_READ); + ret = litex_clk_wait_drdy(clk_hw); + if (ret != 0) + return ret; + + *res = litex_clk_get_reg(clk_hw, DRP_DAT_R); + + return 0; +} + +/* Return global divider and multiplier values */ +static int litex_clk_get_global_divider(struct clk_hw *hw, u32 *divider, + u32 *multiplier) +{ + int ret; + u16 divreg, mult; + u8 low_time, high_time; + + ret = litex_clk_get_DO(hw, CLKFBOUT_REG1, &mult); + if (ret != 0) + return ret; + ret = litex_clk_get_DO(hw, DIV_REG, &divreg); + if (ret != 0) + return ret; + + low_time = mult & HL_TIME_MASK; + high_time = (mult >> HIGH_TIME_POS) & HL_TIME_MASK; + *multiplier = low_time + high_time; + + low_time = divreg & HL_TIME_MASK; + high_time = (divreg >> HIGH_TIME_POS) & HL_TIME_MASK; + *divider = low_time + high_time; + + return 0; +} + +/* Calculate frequency after global divider and multiplier */ +static u32 litex_clk_get_real_global_frequency(struct clk_hw *hw) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 g_divider, g_multiplier; + + litex_clk_get_global_divider(hw, &g_divider, &g_multiplier); + return l_clkout->sys_clk_freq * g_multiplier / g_divider; +} + +/* Calculate frequency after global divider and multiplier without clk_hw */ +static u32 litex_clk_get_expctd_global_frequency(u32 sys_clk_freq) +{ + u32 g_divider, g_multiplier; + + g_divider = (GLOB_DIV_VAL & HL_TIME_MASK) + + ((GLOB_DIV_VAL >> HIGH_TIME_POS) & HL_TIME_MASK); + + g_multiplier = (GLOB_MUL_VAL & HL_TIME_MASK) + + ((GLOB_MUL_VAL >> HIGH_TIME_POS) & HL_TIME_MASK); + + return sys_clk_freq * g_multiplier / g_divider; +} +/* Return dividers of given CLKOUT */ +static int litex_clk_get_clkout_divider(struct clk_hw *hw, u32 *divider, + u32 *fract_cnt) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + u16 div, frac; + u8 clkout_nr = l_clkout->id; + u8 low_time, high_time; + + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg1, &div); + if (ret != 0) + return ret; + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg2, &frac); + if (ret != 0) + return ret; + + low_time = div & HL_TIME_MASK; + high_time = (div >> HIGH_TIME_POS) & HL_TIME_MASK; + *divider = low_time + high_time; + *fract_cnt = (frac >> FRAC_POS) & FRAC_MASK; + + return 0; +} + +/* Debug functions */ +#ifdef DEBUG +static void litex_clk_print_general_regs(struct clk_hw *clk_hw) +{ + u16 power_reg, div_reg, clkfbout_reg, lock_reg1, + lock_reg2, lock_reg3, filt_reg1, filt_reg2; + + litex_clk_get_DO(clk_hw, POWER_REG, &power_reg); + litex_clk_get_DO(clk_hw, DIV_REG, &div_reg); + litex_clk_get_DO(clk_hw, CLKFBOUT_REG1, &clkfbout_reg); + litex_clk_get_DO(clk_hw, LOCK_REG1, &lock_reg1); + litex_clk_get_DO(clk_hw, LOCK_REG2, &lock_reg2); + litex_clk_get_DO(clk_hw, LOCK_REG3, &lock_reg3); + litex_clk_get_DO(clk_hw, FILT_REG1, &filt_reg1); + litex_clk_get_DO(clk_hw, FILT_REG2, &filt_reg2); + + pr_debug("POWER REG: 0x%x\n", power_reg); + pr_debug("DIV REG: 0x%x\n", div_reg); + pr_debug("MUL REG: 0x%x\n", clkfbout_reg); + pr_debug("LOCK_REG1: 0x%x\n", lock_reg1); + pr_debug("LOCK_REG2: 0x%x\n", lock_reg2); + pr_debug("LOCK_REG3: 0x%x\n", lock_reg3); + pr_debug("FILT_REG1: 0x%x\n", filt_reg1); + pr_debug("FILT_REG2: 0x%x\n", filt_reg2); +} + +static void litex_clk_print_clkout_regs(struct clk_hw *clk_hw, u8 clkout, + u8 reg1, u8 reg2) +{ + u16 clkout_reg1, clkout_reg2; + + litex_clk_get_DO(clk_hw, reg1, &clkout_reg1); + pr_debug("CLKOUT%d REG1: 0x%x\n", clkout, clkout_reg1); + + litex_clk_get_DO(clk_hw, reg1, &clkout_reg2); + pr_debug("CLKOUT%d REG2: 0x%x\n", clkout, clkout_reg2); +} + +static void litex_clk_print_all_regs(struct clk_hw *clk_hw) +{ + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + u32 i; + + pr_debug("General regs:\n"); + litex_clk_print_general_regs(clk_hw); + for (i = 0; i < CLKOUT_MAX; i++) + litex_clk_print_clkout_regs(clk_hw, i, drp_addr.clkout[i].reg1, + drp_addr.clkout[i].reg2); +} +#endif /* #ifdef DEBUG */ + +/* Return raw value ready to be written into DRP */ +static inline u16 litex_clk_calc_DI(u16 DO_val, u16 mask, u16 bitset) +{ + u16 DI_val; + + DI_val = DO_val & mask; + DI_val |= bitset; + + return DI_val; +} + +/* Sets calculated DI value into DI DRP register */ +static int litex_clk_set_DI(struct clk_hw *clk_hw, u16 DI_val) +{ + int ret; + + litex_clk_set_reg(clk_hw, DRP_DAT_W, DI_val); + litex_clk_assert_reg(clk_hw, DRP_WRITE); + litex_clk_deassert_reg(clk_hw, DRP_WRITE); + ret = litex_clk_wait_drdy(clk_hw); + if (ret != 0) + return ret; + return 0; +} + +/* + * Change register value as specified in arguments + * + * clk_hw: hardware specific clock structure for selecting CLKOUT + * mask: preserve or zero MMCM register bits + * by selecting 1 or 0 on desired specific mask positions + * bitset: set those bits in MMCM register which are 1 in bitset + * clk_reg_addr: internal MMCM address of control register + * + */ +static int litex_clk_change_value(struct clk_hw *clk_hw, u16 mask, u16 bitset, + u8 clk_reg_addr) +{ + u16 DO_val, DI_val; + int ret; + + litex_clk_assert_reg(clk_hw, DRP_RESET); + + ret = litex_clk_get_DO(clk_hw, clk_reg_addr, &DO_val); + if (ret != 0) + return ret; + DI_val = litex_clk_calc_DI(DO_val, mask, bitset); + ret = litex_clk_set_DI(clk_hw, DI_val); + if (ret != 0) + return ret; +#ifdef DEBUG + DI_val = litex_clk_get_reg(clk_hw, DRP_DAT_W); +#endif + litex_clk_deassert_reg(clk_hw, DRP_DAT_W); + litex_clk_deassert_reg(clk_hw, DRP_RESET); + ret = litex_clk_wait_lock(clk_hw); + if (ret != 0) + return ret; + + pr_debug("set 0x%x under addr: 0x%x", DI_val, clk_reg_addr); + return 0; +} + +/* + * Set register values for given CLKOUT + * + * clk_hw: hardware specific clock structure for selecting CLKOUT + * clkout_nr: clock output number + * mask_regX: preserve or zero MMCM register X bits + * by selecting 1 or 0 on desired specific mask positions + * bitset_regX: set those bits in MMCM register X which are 1 in bitset + * + */ +static int litex_clk_set_clock(struct clk_hw *clk_hw, u8 clkout_nr, + u16 mask_reg1, u16 bitset_reg1, + u16 mask_reg2, u16 bitset_reg2) +{ + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + + if (!(mask_reg2 == KEEP_REG_16 && bitset_reg2 == ZERO_REG)) { + ret = litex_clk_change_value(clk_hw, mask_reg2, bitset_reg2, + drp_addr.clkout[clkout_nr].reg2); + if (ret != 0) + return ret; + } + if (!(mask_reg1 == KEEP_REG_16 && bitset_reg1 == ZERO_REG)) { + ret = litex_clk_change_value(clk_hw, mask_reg1, bitset_reg1, + drp_addr.clkout[clkout_nr].reg1); + if (ret != 0) + return ret; + } + return 0; +} + +/* + * Set global divider for all CLKOUTs + * + * clk_hw: hardware specific clock structure + * mask: preserve or zero MMCM register bits + * by selecting 1 or 0 on desired specific mask positions + * bitset: set those bits in MMCM register which are 1 in bitset + * + */ +static int litex_clk_set_divreg(struct clk_hw *clk_hw, u16 mask, u16 bitset) +{ + int ret; + + ret = litex_clk_change_value(clk_hw, mask, bitset, DIV_REG); + if (ret != 0) + return ret; + pr_debug("Global divider set to %d", (bitset % (1 << HIGH_TIME_POS)) + + ((bitset >> HIGH_TIME_POS) % (1 << HIGH_TIME_POS))); + return 0; +} + +/* + * Set global multiplier for all CLKOUTs + * + * clk_hw: hardware specific clock structure + * mask: preserve or zero MMCM register bits + * by selecting 1 or 0 on desired specific mask positions + * bitset: set those bits in MMCM register which are 1 in bitset + * + */ +static int litex_clk_set_mulreg(struct clk_hw *clk_hw, u16 mask, u16 bitset) +{ + int ret; + + ret = litex_clk_change_value(clk_hw, mask, bitset, CLKFBOUT_REG1); + if (ret != 0) + return ret; + pr_debug("Global multiplier set to %d", + (bitset % (1 << HIGH_TIME_POS)) + ((bitset >> HIGH_TIME_POS) % + (1 << HIGH_TIME_POS))); + return 0; +} + +static int litex_clk_set_filt(struct clk_hw *hw) +{ + u32 filt, mul; + int ret; + + mul = GLOB_MUL_VAL && HL_TIME_MASK; + mul += (GLOB_MUL_VAL >> HIGH_TIME_POS) && HL_TIME_MASK; + + filt = litex_clk_lookup_filter(mul - 1); + + ret = litex_clk_change_value(hw, FILT_MASK, filt >> 16, FILT_REG1); + if (ret != 0) + return ret; + ret = litex_clk_change_value(hw, FILT_MASK, filt, FILT_REG2); + if (ret != 0) + return ret; + return 0; +} + +static int litex_clk_set_lock(struct clk_hw *hw) +{ + u32 lock, mul; + int ret; + + mul = GLOB_MUL_VAL && HL_TIME_MASK; + mul += (GLOB_MUL_VAL >> HIGH_TIME_POS) && HL_TIME_MASK; + + lock = litex_clk_lookup_lock(mul - 1); + + ret = litex_clk_change_value(hw, LOCK1_MASK, lock & 0x3ff, LOCK_REG1); + if (ret != 0) + return ret; + ret = litex_clk_change_value(hw, LOCK23_MASK, + (((lock >> 16) & 0x1f) << 10) | 0x1, LOCK_REG2); + if (ret != 0) + return ret; + ret = litex_clk_change_value(hw, LOCK23_MASK, + (((lock >> 24) & 0x1f) << 10) | 0x3e9, LOCK_REG3); + if (ret != 0) + return ret; + return 0; +} + +/* + * Load default global divider and multiplier values + * + */ +static int litex_clk_set_dm(struct clk_hw *clk_hw) +{ + int ret; + + ret = litex_clk_set_divreg(clk_hw, KEEP_IN_DIV, GLOB_DIV_VAL); + if (ret != 0) + return ret; + ret = litex_clk_set_mulreg(clk_hw, KEEP_IN_MUL, GLOB_MUL_VAL); + if (ret != 0) + return ret; + ret = litex_clk_set_filt(clk_hw); + if (ret != 0) + return ret; + ret = litex_clk_set_lock(clk_hw); + if (ret != 0) + return ret; + return 0; +} + +/* Round scaled value*/ +static inline u32 litex_round(u32 val, u32 mod) +{ + if (val % mod > mod / 2) + return val / mod + 1; + else + return val / mod; +} + +/* + * Duty Cycle + */ + +/* + * Calculate necessary values for setting duty cycle in fractional mode + * + * divider: frequency divider value for calculating the phase + * high_duty: desired duty cycle in percent + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * pointers: function return values, used for setting desired duty + * + * This function is based on Xilinx Unified Simulation Library Component + * Advanced Mixed Mode Clock Manager (MMCM) taken from Xilinx Vivado + * found in: /data/verilog/src/unisims/MMCME2_ADV.v + * + */ +static int litex_clk_calc_duty_fract(int divider, int high_duty, int fract_cnt, + u8 *edge, u8 *high_time, u8 *low_time, + u8 *frac_wf_r, u8 *frac_wf_f) +{ + int even_part_high, even_part_low, odd, odd_and_frac; + + /* divider / 2 */ + even_part_high = divider >> 1; + even_part_low = even_part_high; + odd = divider - even_part_high - even_part_low; + odd_and_frac = (8 * odd) + fract_cnt; + + *low_time = even_part_high; + if (odd_and_frac <= ODD_AND_FRAC) + (*low_time)--; + + *high_time = even_part_low; + if (odd_and_frac <= EVEN_AND_FRAC) + (*high_time)--; + + *edge = divider % 2; + + if (((odd_and_frac >= 2) && (odd_and_frac <= ODD_AND_FRAC)) || + ((fract_cnt == 1) && (divider == 2))) + *frac_wf_f = 1; + else + *frac_wf_f = 0; + + if ((odd_and_frac >= 1) && (odd_and_frac <= EVEN_AND_FRAC)) + *frac_wf_r = 1; + else + *frac_wf_r = 0; + + return 0; +} +/* End of Vivado based code */ + +/* + * Calculate necessary values for setting duty cycle in normal mode + * + * divider: frequency divider value for calculating the phase + * high_duty: desired duty cycle in percent + * pointers: function return values, used for setting desired duty + * + */ +static int litex_clk_calc_duty_normal(int divider, int high_duty, u8 *edge, + u8 *high_time, u8 *low_time, + u8 *frac_wf_r, u8 *frac_wf_f) +{ + int synthetic_duty, delta_d, min_d; + u32 high_time_it, ht_aprox, edge_it; + + min_d = INT_MAX; + /* check if duty is available to set */ + ht_aprox = high_duty * divider; + if (ht_aprox > ((HIGH_LOW_TIME_REG_MAX * 100) + 50) || + (divider * 100) - ht_aprox > + ((HIGH_LOW_TIME_REG_MAX * 100) + 50)) + return -EINVAL; + + /* to prevent high_time == 0 or low_time == 0 */ + for (high_time_it = 1; high_time_it < divider; high_time_it++) { + for (edge_it = 0; edge_it < 2; edge_it++) { + synthetic_duty = (high_time_it * 100 + 50 * edge_it) / + divider; + delta_d = abs(synthetic_duty - high_duty); + /* check if low_time won't be above acceptable range */ + if (delta_d < min_d && (divider - high_time_it) <= + HIGH_LOW_TIME_REG_MAX) { + min_d = delta_d; + *high_time = high_time_it; + *low_time = divider - *high_time; + *edge = edge_it; + } + } + } + /* + * Calculating values in normal mode, + * clear control bits of fractional part + */ + + *frac_wf_f = 0; + *frac_wf_r = 0; + + return 0; +} + +/* Calculates duty cycle for given ratio in percent, 1% accuracy */ +static inline int litex_clk_calc_duty_percent(struct clk_hw *hw, + struct clk_duty *duty) +{ + u32 div, duty_ratio, ht; + + ht = duty->num; + div = duty->den; + duty_ratio = ht * 10000 / div; + + return litex_round(duty_ratio, 100); +} + +/* Calculates duty high_time for given divider and ratio */ +static inline int litex_clk_calc_duty_high_time(struct clk_hw *hw, + struct clk_duty *duty, + u32 divider) +{ + u32 high_duty; + + high_duty = litex_clk_calc_duty_percent(hw, duty) * divider; + + return litex_round(high_duty, 100); +} + +/* Returns accurate duty ratio of given clkout*/ +int litex_clk_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + u32 divider; + u16 clkout_reg1, clkout_reg2; + u8 clkout_nr, high_time, edge, no_cnt, frac_en, frac_cnt; + + clkout_nr = l_clkout->id; + /* Check if divider is off */ + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg2, + &clkout_reg2); + if (ret != 0) + return ret; + edge = (clkout_reg2 >> EDGE_POS) & EDGE_MASK; + no_cnt = (clkout_reg2 >> NO_CNT_POS) & NO_CNT_MASK; + frac_en = (clkout_reg2 >> FRAC_EN_POS) & FRAC_EN_MASK; + frac_cnt = (clkout_reg2 >> FRAC_POS) & FRAC_MASK; + + /* get duty 50% when divider is off or fractional is enabled */ + if (no_cnt || (frac_en && frac_cnt)) { + duty->num = 1; + duty->den = 2; + return 0; + } + + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg1, + &clkout_reg1); + if (ret != 0) + return ret; + divider = clkout_reg1 & HL_TIME_MASK; + high_time = (clkout_reg1 >> HIGH_TIME_POS) & HL_TIME_MASK; + divider += high_time; + + /* Scaling to consider edge control bit */ + duty->num = high_time * 10 + edge * 5; + duty->den = (divider + edge) * 10; + + return 0; +} + +/* Set duty cycle with given ratio */ +int litex_clk_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 divider, fract_cnt, high_duty; + u16 bitset1, bitset2; + u8 frac_wf_r, frac_wf_f, frac_en, no_cnt, + edge, high_time, low_time, clkout_nr; + + clkout_nr = l_clkout->id; + edge = 0, high_time = 0, low_time = 0, frac_en = 0, + no_cnt = 0, frac_wf_r = 0, frac_wf_f = 0; + high_duty = litex_clk_calc_duty_percent(hw, duty); + + litex_clk_get_clkout_divider(hw, ÷r, &fract_cnt); + + if (fract_cnt == 0) { + int ret; + + ret = litex_clk_calc_duty_normal(divider, high_duty, &edge, + &high_time, &low_time, + &frac_wf_r, &frac_wf_f); + if (ret != 0) { + pr_err("CLKOUT%d: cannot set %d%% duty cycle for that frequency", + clkout_nr, high_duty); + return ret; + } + } else if (high_duty == 50) { + return 0; + } else { + pr_err("CLKOUT%d: cannot set duty cycle when fractional divider enabled!", + clkout_nr); + return -EACCES; + } + + bitset1 = (high_time << HIGH_TIME_POS) | low_time; + bitset2 = (edge << EDGE_POS); + + pr_debug("SET DUTY CYCLE: divider:%d fract_cnt:%d frac_wf_r:%d frac_wf_f:%d frac_en:%d no_cnt:%d edge:%d high_time:%d low_time:%d bitset1: 0x%x bitset2: 0x%x", + divider, fract_cnt, frac_wf_r, frac_wf_f, frac_en, + no_cnt, edge, high_time, low_time, bitset1, bitset2); + + return litex_clk_set_clock(hw, clkout_nr, REG1_DUTY_MASK, bitset1, + REG2_DUTY_MASK, bitset2); +} + +/* + * Phase + */ + +/* + * Calculate necessary values for setting phase in fractional mode + * + * clk_hw: hardware specific clock structure + * divider: frequency divider value for calculating the phase + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * phase_offset: desired phase in angle degrees + * pointers: function return values, used for setting desired phase + * + * This function is based on Xilinx Unified Simulation Library Component + * Advanced Mixed Mode Clock Manager (MMCM) taken from Xilinx Vivado + * found in: /data/verilog/src/unisims/MMCME2_ADV.v + * + */ +static int litex_clk_calc_phase_fract(struct clk_hw *hw, u32 divider, + u32 fract_cnt, u32 phase_offset, + u32 *phase_mux, u32 *delay_time, + u32 *phase_mux_f, u32 *period_offset) +{ + int phase, dt_calc, dt, + a_per_in_octets, a_phase_in_cycles, + pm_rise_frac, pm_rise_frac_filtered, + pm_fall, pm_fall_frac, pm_fall_frac_filtered; + + phase = phase_offset * 1000; + a_per_in_octets = (8 * divider) + fract_cnt; + a_phase_in_cycles = (phase + 10) * a_per_in_octets / 360000; + + if ((a_phase_in_cycles & FULL_BYTE) != 0) + pm_rise_frac = (a_phase_in_cycles & PHASE_MUX_MAX); + else + pm_rise_frac = 0; + + dt_calc = a_phase_in_cycles / 8; + dt = dt_calc & FULL_BYTE; + + pm_rise_frac_filtered = pm_rise_frac; + if (pm_rise_frac >= 8) + pm_rise_frac_filtered -= 8; + + pm_fall = ((divider % 2) << 2) + ((fract_cnt >> 1) & TWO_LSBITS); + pm_fall_frac = pm_fall + pm_rise_frac; + pm_fall_frac_filtered = pm_fall + pm_rise_frac - + (pm_fall_frac & F_FRAC_MASK); + + /* keep only the lowest bits to fit in control registers bit groups */ + *delay_time = dt % (1 << 6); + *phase_mux = pm_rise_frac_filtered % (1 << 3); + *phase_mux_f = pm_fall_frac_filtered % (1 << 3); + + return 0; +} +/* End of Vivado based code */ + +/* + * Calculate necessary values for setting phase in normal mode + * + * clk_hw: hardware specific clock structure + * divider: frequency divider value for calculating the phase + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * phase_offset: desired phase in angle degrees + * pointers: function return values, used for setting desired phase + * + */ +static int litex_clk_calc_phase_normal(struct clk_hw *hw, u32 divider, + u32 fract_cnt, u32 phase_offset, + u32 *phase_mux, u32 *delay_time, + u32 *phase_mux_f, u32 *period_offset) +{ + /* ns unit */ + u32 global_period, clkout_period, post_glob_div_f; + + post_glob_div_f = litex_clk_get_real_global_frequency(hw); + global_period = NS_IN_SEC / post_glob_div_f; + clkout_period = global_period * divider; + + if (phase_offset != 0) { + int synthetic_phase, delta_p, min_p; + u8 d_t, p_m; + + *period_offset = litex_round(clkout_period * (*period_offset), + 10000); + + if (*period_offset / global_period > DELAY_TIME_MAX) + return -EINVAL; + + min_p = INT_MAX; + /* Delay_time: (0-63) */ + for (d_t = 0; d_t <= DELAY_TIME_MAX; d_t++) { + /* phase_mux: (0-7) */ + for (p_m = 0; p_m <= PHASE_MUX_MAX; p_m++) { + synthetic_phase = (d_t * global_period) + + ((p_m * ((global_period * 100) / 8) / 100)); + + delta_p = abs(synthetic_phase - *period_offset); + if (delta_p < min_p) { + min_p = delta_p; + *phase_mux = p_m; + *delay_time = d_t; + } + } + } + } else { + /* Don't change phase offset*/ + *phase_mux = 0; + *delay_time = 0; + } +/* Calculating values in normal mode, fractional control bits need to be zero*/ + *phase_mux_f = 0; + + return 0; +} + +/* + * Convert phase offset to positive lower than 360 deg. + * and calculate period in nanoseconds + * + */ +static int litex_clk_prepare_phase(int *phase_offset, u32 *period_offset) +{ + *phase_offset = *phase_offset % 360; + + if (*phase_offset < 0) + *phase_offset += 360; + + *period_offset = ((*phase_offset * 10000) / 360); + + return 0; +} + +/* + * Calculate necessary values for setting phase + * + * clk_hw: hardware specific clock structure + * divider: frequency divider value for calculating the phase + * fract_cnt: fractional frequency divider value, if not for + * CLKOUT0 - set 0 + * clkout_nr: clock output number + * phase_offset: desired phase in angle degrees + * pointers: function return values, used for setting desired phase + * + */ +static int litex_clk_calc_phase(struct clk_hw *clk_hw, + u32 divider, u32 fract_cnt, + u32 clkout_nr, int phase_offset, + u32 *phase_mux, u32 *delay_time, + u32 *phase_mux_f, u32 *period_offset) +{ + int ret; + + litex_clk_prepare_phase(&phase_offset, period_offset); + + if (clkout_nr == 0 && fract_cnt > 0) + ret = litex_clk_calc_phase_fract(clk_hw, divider, fract_cnt, + phase_offset, + phase_mux, delay_time, + phase_mux_f, period_offset); + else + ret = litex_clk_calc_phase_normal(clk_hw, divider, fract_cnt, + phase_offset, + phase_mux, delay_time, + phase_mux_f, period_offset); + return ret; +} + +/* Returns phase-specific values of given clock output */ +static int litex_clk_get_phase_data(struct clk_hw *hw, u8 *phase_mux, + u8 *delay_time, u8 *phase_mux_f) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + struct litex_clk_regs_addr drp_addr = litex_clk_regs_addr_init(); + int ret; + u16 r1, r2; + u8 clkout_nr = l_clkout->id; + + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg1, &r1); + if (ret != 0) + return ret; + ret = litex_clk_get_DO(hw, drp_addr.clkout[clkout_nr].reg2, &r2); + if (ret != 0) + return ret; + *phase_mux = (r1 >> PHASE_MUX_POS) & PHASE_MUX_MASK; + *delay_time = (r2 >> DELAY_TIME_POS) & HL_TIME_MASK; + + if (clkout_nr == 0) { + ret = litex_clk_get_DO(hw, CLKOUT5_REG2, &r2); + if (ret != 0) + return ret; + *phase_mux_f = (r2 >> PHASE_MUX_F_POS) & PHASE_MUX_F_MASK; + } else { + *phase_mux_f = 0; + } + + return 0; +} + +/* Returns phase of given clock output in degrees */ +int litex_clk_get_phase(struct clk_hw *hw) +{ + u8 phase_mux = 0, delay_time = 0, phase_mux_f; + u32 divider, fract_cnt, post_glob_div_f, pm; + /* ns unit */ + u32 global_period, clkout_period, period; + + litex_clk_get_phase_data(hw, &phase_mux, &delay_time, &phase_mux_f); + litex_clk_get_clkout_divider(hw, ÷r, &fract_cnt); + + post_glob_div_f = litex_clk_get_real_global_frequency(hw); + global_period = NS_IN_SEC / post_glob_div_f; + clkout_period = global_period * divider; + + pm = (phase_mux * global_period * 1000) / PHASE_MUX_RES_FACTOR; + pm = litex_round(pm, 1000); + + period = delay_time * global_period + pm; + + period = period * 1000 / clkout_period; + period = period * 360; + + return litex_round(period, 1000); +} + +/* Sets phase given in degrees on given clock output */ +int litex_clk_set_phase(struct clk_hw *hw, int degrees) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 phase_mux, divider, fract_cnt, + phase_mux_f, delay_time, period_offset; + u16 bitset1, bitset2, reg2_mask; + u8 clkout_nr, edge, high_time, low_time, frac_wf_r, frac_wf_f; + int ret; + + reg2_mask = REG2_PHASE_MASK; + phase_mux = 0; + delay_time = 0; + clkout_nr = l_clkout->id; + litex_clk_get_clkout_divider(hw, ÷r, &fract_cnt); + if (fract_cnt > 0 && degrees != 0) { + pr_err("CLKOUT%d: cannot set phase on that frequency", + clkout_nr); + return -ENOSYS; + } + ret = litex_clk_calc_phase(hw, divider, fract_cnt, clkout_nr, degrees, + &phase_mux, &delay_time, + &phase_mux_f, &period_offset); + if (ret != 0) { + pr_err("CLKOUT%d: phase offset %d deg is too high for given frequency", + clkout_nr, degrees); + return ret; + } + + bitset1 = (phase_mux << PHASE_MUX_POS); + bitset2 = (delay_time << DELAY_TIME_POS); + + if (clkout_nr == 0 && fract_cnt > 0) { + u16 clkout5_reg2_bitset; + + litex_clk_calc_duty_fract(divider, 50, fract_cnt, + &edge, &high_time, &low_time, + &frac_wf_r, &frac_wf_f); + bitset2 |= (fract_cnt << FRAC_POS) | + (1 << FRAC_EN_POS) | + (frac_wf_r << FRAC_WF_R_POS); + + clkout5_reg2_bitset = (phase_mux_f << PHASE_MUX_F_POS) | + (frac_wf_f << FRAC_WF_F_POS); + + ret = litex_clk_change_value(hw, CLKOUT5_FRAC_MASK, + clkout5_reg2_bitset, + CLKOUT5_REG2); + if (ret != 0) + return ret; + reg2_mask = REG2_PHASE_F_MASK; + } + + pr_debug("SET PHASE: phase_mux:%d phase_mux_f:%d delay_time:%d bitset1: 0x%x bitset2: 0x%x", + phase_mux, phase_mux_f, delay_time, bitset1, bitset2); + + return litex_clk_set_clock(hw, clkout_nr, REG1_PHASE_MASK, bitset1, + reg2_mask, bitset2); +} + +/* + * Frequency + */ + +/* + * Calculate necessary values for setting frequency in fractional mode + * + * clk_hw: hardware specific clock structure + * freq: desired frequency in Hz + * pointers: function return values, used for setting + * desired frequency + */ +static int litex_clk_calc_freq_fract(struct clk_hw *clk_hw, u32 freq, + u32 *divider, u8 *frac_en, + u8 *no_cnt, u32 *fract_cnt) +{ + int delta_f, synthetic_freq, min_f; + u32 post_glob_div_f, div, f_cnt, fract; + + post_glob_div_f = litex_clk_get_real_global_frequency(clk_hw); + + delta_f = 0; + synthetic_freq = post_glob_div_f; + min_f = abs(synthetic_freq - freq); + *divider = 1; + /* div is integer divider * 1000 to keep precision */ + for (div = MIN_DIVIDER_F; div <= MAX_DIVIDER_F; div += 1000) { + /* + * f_cnt is fractional part of divider * 1000 incremented + * by minimal fractional divider step to keep precision + * and find best divider + */ + for (f_cnt = 0, fract = 0; fract < 1000; + fract += FRACT_DIV_RES, f_cnt++) { + + synthetic_freq = (post_glob_div_f / (div + fract)) * + 1000; + delta_f = abs(synthetic_freq - freq); + + if (delta_f < min_f) { + min_f = delta_f; + *divider = div / 1000; + *fract_cnt = f_cnt; + } + } + } + if (*fract_cnt == 0) { + *frac_en = 0; + if (*divider == 1) + *no_cnt = 1; + else + *no_cnt = 0; + } else { + *frac_en = 1; + *no_cnt = 0; + } + + return 0; +} + +/* + * Calculate necessary values for setting frequency in normal mode + * + * clk_hw: hardware specific clock structure + * freq: desired frequency in Hz + * pointers: function return values, used for setting + * desired frequency + * + */ +static int litex_clk_calc_freq_normal(struct clk_hw *clk_hw, u32 freq, + u32 *divider, u8 *frac_en, + u8 *no_cnt, u32 *fract_cnt) +{ + int delta_f, synthetic_freq, min_f; + u32 post_glob_div_f, div; + + post_glob_div_f = litex_clk_get_real_global_frequency(clk_hw); + synthetic_freq = post_glob_div_f; + min_f = abs(synthetic_freq - freq); + *divider = 1; + delta_f = 0; + + for (div = MIN_DIVIDER; div <= MAX_DIVIDER; div++) { + synthetic_freq = post_glob_div_f / div; + delta_f = abs(synthetic_freq - freq); + if (delta_f < min_f) { + min_f = delta_f; + *divider = div; + } + } + if (*divider == 1) + *no_cnt = 1; + else + *no_cnt = 0; + + *frac_en = 0; + *fract_cnt = 0; + + return 0; +} + +/* Returns rate in Hz */ +static inline unsigned long litex_clk_calc_rate(struct clk_hw *hw, u32 g_mul, + u32 g_div, u32 div, + u32 fract_cnt) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + + return (l_clkout->sys_clk_freq * g_mul / + ((div * 1000 + (fract_cnt * FRACT_DIV_RES)) * g_div)) * 1000; +} + +/* + * Calculate necessary values for setting frequency + * + * clk_hw: hardware specific clock structure + * rate: desired frequency in Hz + * clkout_nr: clock output number + * pointers: function return values, used for setting + * desired frequency + * + */ +static void litex_clk_calc_dividers(struct clk_hw *hw, u32 rate, u8 clkout_nr, + u32 *divider, u32 *fract_cnt, + u8 *frac_wf_r, u8 *frac_wf_f, + u8 *frac_en, u8 *no_cnt, u8 *edge, + u8 *high_time, u8 *low_time) +{ + u32 high_duty; + struct clk_duty duty; + + litex_clk_get_duty_cycle(hw, &duty); + high_duty = litex_clk_calc_duty_percent(hw, &duty); + + if (clkout_nr == 0) { + litex_clk_calc_freq_fract(hw, rate, divider, frac_en, no_cnt, + fract_cnt); + if (*fract_cnt > 0) + litex_clk_calc_duty_fract(*divider, high_duty, + *fract_cnt, edge, + high_time, low_time, + frac_wf_r, frac_wf_f); + else + litex_clk_calc_duty_normal(*divider, high_duty, edge, + high_time, low_time, + frac_wf_r, frac_wf_f); + } else { + + litex_clk_calc_freq_normal(hw, rate, divider, frac_en, + no_cnt, fract_cnt); + + litex_clk_calc_duty_normal(*divider, high_duty, edge, + high_time, low_time, + frac_wf_r, frac_wf_f); + } +} + +/* Returns rate of given CLKOUT, parent_rate ignored */ +unsigned long litex_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + u32 multiplier, divider, clkout_div, fract_cnt; + + LITEX_UNUSED(parent_rate); + + litex_clk_get_global_divider(hw, ÷r, &multiplier); + litex_clk_get_clkout_divider(hw, &clkout_div, &fract_cnt); + return litex_clk_calc_rate(hw, multiplier, divider, clkout_div, + fract_cnt); +} + +int litex_clk_check_rate_range(struct clk_hw *hw, unsigned long rate) +{ + u32 f_max, f_min, gf; + + gf = litex_clk_get_real_global_frequency(hw); + f_max = gf / 2; + f_min = gf / MAX_DIVIDER; + /* margin of 1kHz */ + if ((rate > f_max + KHZ) | (rate < f_min - KHZ)) + return -EINVAL; + + return 0; +} + +/* Returns closest available clock rate in Hz, parent_rate ignored */ +long litex_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u32 divider, fract_cnt, g_divider, g_multiplier; + u8 frac_wf_r, frac_wf_f, frac_en, no_cnt, + edge, high_time, low_time, clkout_nr; + + if (litex_clk_check_rate_range(hw, rate)) + return -EINVAL; + + LITEX_UNUSED(parent_rate); + clkout_nr = l_clkout->id; + + litex_clk_calc_dividers(hw, rate, clkout_nr, ÷r, &fract_cnt, + &frac_wf_r, &frac_wf_f, &frac_en, + &no_cnt, &edge, &high_time, &low_time); + litex_clk_get_global_divider(hw, &g_divider, &g_multiplier); + + return litex_clk_calc_rate(hw, g_multiplier, g_divider, divider, + fract_cnt); +} + +/* Set closest available clock rate in Hz, parent_rate ignored */ +int litex_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct litex_clk_clkout *l_clkout = clk_hw_to_litex_clk_clkout(hw); + u16 bitset1, bitset2; + u32 divider, fract_cnt; + u8 frac_wf_r, frac_wf_f, frac_en, no_cnt, + edge, high_time, low_time, clkout_nr; + int ret; + + LITEX_UNUSED(parent_rate); + clkout_nr = l_clkout->id; + + pr_debug("set_rate: %lu", rate); + if (litex_clk_check_rate_range(hw, rate)) { + pr_err("CLKOUT%d: cannot set %luHz, frequency not in available range", + clkout_nr, rate); + return -EINVAL; + } + + litex_clk_calc_dividers(hw, rate, clkout_nr, ÷r, &fract_cnt, + &frac_wf_r, &frac_wf_f, &frac_en, &no_cnt, + &edge, &high_time, &low_time); + + bitset1 = (high_time << HIGH_TIME_POS) | + (low_time << LOW_TIME_POS); + + bitset2 = (fract_cnt << FRAC_POS) | + (frac_en << FRAC_EN_POS) | + (frac_wf_r << FRAC_WF_R_POS) | + (edge << EDGE_POS) | + (no_cnt << NO_CNT_POS); + if (frac_en != 0) { + u32 phase_mux, delay_time, phase_mux_f, + phase_offset, period_offset; + + phase_offset = 0; + litex_clk_calc_phase_fract(hw, divider, fract_cnt, phase_offset, + &phase_mux, &delay_time, + &phase_mux_f, &period_offset); + + bitset1 |= (phase_mux << PHASE_MUX_POS); + bitset2 |= (delay_time << DELAY_TIME_POS); + + if (frac_wf_f > 0 || phase_mux_f > 0) { + u16 clkout5_reg2_bitset = 0; + + clkout5_reg2_bitset = (phase_mux_f << PHASE_MUX_F_POS) | + (frac_wf_f << FRAC_WF_F_POS); + + ret = litex_clk_change_value(hw, CLKOUT5_FRAC_MASK, + clkout5_reg2_bitset, CLKOUT5_REG2); + if (ret != 0) + return ret; + } + } + + pr_debug("SET RATE: divider:%d fract_cnt:%d frac_wf_r:%d frac_wf_f:%d frac_en:%d no_cnt:%d edge:%d high_time:%d low_time:%d bitset1: 0x%x bitset2: 0x%x", + divider, fract_cnt, frac_wf_r, frac_wf_f, frac_en, + no_cnt, edge, high_time, low_time, bitset1, bitset2); + + return litex_clk_set_clock(hw, clkout_nr, REG1_FREQ_MASK, bitset1, + REG2_FREQ_MASK, bitset2); +} + +/* Prepare clock output */ +int litex_clk_prepare(struct clk_hw *hw) +{ + struct litex_clk_clkout *l_clkout; + struct clk_duty duty; + u32 freq, phase; + + l_clkout = clk_hw_to_litex_clk_clkout(hw); + freq = l_clkout->default_freq; + phase = l_clkout->default_phase; + duty.num = l_clkout->default_duty_num; + duty.den = l_clkout->default_duty_den; + + litex_clk_set_rate(hw, freq, 0); + litex_clk_set_duty_cycle(hw, &duty); + litex_clk_set_phase(hw, phase); + return 0; +} + +/* Unrepare clock output */ +void litex_clk_unprepare(struct clk_hw *hw) +{ + struct clk_duty duty; + + duty.num = 1; + duty.den = 2; + litex_clk_set_rate(hw, 100000000, 0); + litex_clk_set_duty_cycle(hw, &duty); + litex_clk_set_phase(hw, 0); +} + +/* Enable Clock Control MMCM module */ +int litex_clk_enable(struct clk_hw *hw) +{ + litex_clk_assert_reg(hw, DRP_RESET); + return 0; +} + +/* Disable Clock Control MMCM module */ +void litex_clk_disable(struct clk_hw *hw) +{ + litex_clk_deassert_reg(hw, DRP_RESET); +} + +/* Set default clock value from device tree for given clkout*/ +static int litex_clk_set_def_clkout(struct clk_hw *hw, u32 freq, u32 phase, + struct clk_duty *duty) +{ + int ret; + + ret = litex_clk_set_rate(hw, freq, 0); + if (ret != 0) + return ret; + ret = litex_clk_set_duty_cycle(hw, duty); + if (ret != 0) + return ret; + ret = litex_clk_set_phase(hw, phase); + if (ret != 0) + return ret; + return 0; +} + +static const struct clk_ops litex_clk_ops = { + .prepare = litex_clk_prepare, + .unprepare = litex_clk_unprepare, + .enable = litex_clk_enable, + .disable = litex_clk_disable, + .recalc_rate = litex_clk_recalc_rate, + .round_rate = litex_clk_round_rate, + .set_rate = litex_clk_set_rate, + .get_phase = litex_clk_get_phase, + .set_phase = litex_clk_set_phase, + .get_duty_cycle = litex_clk_get_duty_cycle, + .set_duty_cycle = litex_clk_set_duty_cycle, +}; + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,clk"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static int litex_clk_read_clkout_dts(struct device_node *child, + struct litex_clk_clkout *clkout) +{ + int ret; + u32 dt_num, dt_freq, dt_phase, dt_duty_num, + dt_duty_den, f_max, f_min, gf; + + gf = litex_clk_get_expctd_global_frequency(clkout->sys_clk_freq); + f_max = gf / 2; + f_min = gf / MAX_DIVIDER; + + ret = of_property_read_u32(child, "reg", &dt_num); + if (ret != 0) + return -ret; + + if (dt_num > CLKOUT_MAX) { + pr_err("%s: Invalid CLKOUT index!", child->name); + return -EINVAL; + } + + ret = of_property_read_u32(child, "litex,clock-frequency", &dt_freq); + if (ret != 0) + return -ret; + + if (dt_freq > f_max || dt_freq < f_min) { + pr_err("%s: Invalid default frequency!(%d), MIN/MAX (%d/%d)Hz", + child->name, dt_freq, f_min, f_max); + return -EINVAL; + } + + ret = of_property_read_u32(child, "litex,clock-phase", &dt_phase); + if (ret != 0) + return -ret; + + ret = of_property_read_u32(child, "litex,clock-duty-den", &dt_duty_den); + if (ret != 0) + return -ret; + + ret = of_property_read_u32(child, "litex,clock-duty-num", &dt_duty_num); + if (ret != 0) + return -ret; + + if (dt_duty_den <= 0 || dt_duty_num > dt_duty_den) { + pr_err("%s: Invalid default duty! %d/%d", child->name, + dt_duty_num, dt_duty_den); + return -EINVAL; + } + + clkout->id = dt_num; + clkout->default_freq = dt_freq; + clkout->default_phase = dt_phase; + clkout->default_duty_num = dt_duty_num; + clkout->default_duty_den = dt_duty_den; + + return 0; +} + +static int litex_clk_get_timeouts(struct device_node *node, int *dt_lock_timeout, + int *dt_drdy_timeout) +{ + int ret; + + /* Read wait_lock timeout from device property*/ + ret = of_property_read_u32(node, "litex,lock-timeout", dt_lock_timeout); + if (ret < 0) { + pr_err("No litex,lock_timeout entry in the dts file\n"); + return -ENODEV; + } + if (*dt_lock_timeout < 1) { + pr_err("LiteX CLK driver cannot wait for time bellow ca. 1ms\n"); + return -EINVAL; + } + + /* Read wait_drdy timeout from device property*/ + ret = of_property_read_u32(node, "litex,drdy-timeout", dt_drdy_timeout); + if (ret < 0) { + pr_err("No litex,lock_drdy entry in the dts file\n"); + return -ENODEV; + } + if (*dt_drdy_timeout < 1) { + pr_err("LiteX CLK driver cannot wait for time bellow ca. 1ms\n"); + return -EINVAL; + } + + return 0; +} + +static int litex_clk_init_clkouts(struct device *dev, + struct device_node *node, + struct litex_clk_device *l_dev) +{ + struct device_node *child_node; + struct clk_hw *hw; + struct clk_duty duty; + u32 dt_lock_timeout, dt_drdy_timeout; + int i = 0, ret; + + ret = litex_clk_get_timeouts(node, &dt_lock_timeout, &dt_drdy_timeout); + if (ret != 0) + return ret; + + for_each_child_of_node(node, child_node) { + struct litex_clk_clkout *clkout; + struct clk_init_data clkout_init; + + clkout = &l_dev->clkouts[i]; + clkout->is_clkout = true; + + clkout->lock_timeout = dt_lock_timeout; + clkout->drdy_timeout = dt_drdy_timeout; + clkout->sys_clk_freq = l_dev->sys_clk_freq; + + ret = litex_clk_read_clkout_dts(child_node, clkout); + if (ret != 0) + return ret; + + clkout_init.name = child_node->name; + clkout_init.ops = &litex_clk_ops; + clkout_init.num_parents = 0; + clkout_init.flags = CLK_SET_RATE_UNGATE | CLK_GET_RATE_NOCACHE; + clkout->clk_hw.init = &clkout_init; + clkout->base = l_dev->base; + + duty.num = clkout->default_duty_num; + duty.den = clkout->default_duty_den; + + /* set global divider and multiplier once */ + if (i == 0) { + hw = &l_dev->clkouts[i].clk_hw; + litex_clk_enable(hw); + ret = litex_clk_set_dm(hw); + litex_clk_disable(hw); + if (ret != 0) + return ret; + } + + ret = litex_clk_set_def_clkout(&clkout->clk_hw, + clkout->default_freq, + clkout->default_phase, &duty); + ret = devm_clk_hw_register(dev, &clkout->clk_hw); + if (ret != 0) { + pr_err("devm_clk_hw_register failure, %s ret: %d\n", + child_node->name, ret); + return ret; + } + + ret = of_clk_add_hw_provider(child_node, of_clk_hw_simple_get, + &clkout->clk_hw); + if (ret != 0) + return ret; + i++; + } + return 0; +} + +static int litex_clk_read_global_dts(struct device *dev, + struct device_node *node, + struct litex_clk_device *l_dev) +{ + struct device_node *child_node; + struct litex_clk_clkout *l_clkout; + u32 dt_nclkout, dt_lock_timeout, dt_drdy_timeout, dt_sys_clk_freq; + int i = 0, ret; + + ret = of_property_read_u32(node, "litex,sys-clock-frequency", + &dt_sys_clk_freq); + if (ret < 0) { + pr_err("No clock-frequency entry in the dts file\n"); + return -ENODEV; + } + l_dev->sys_clk_freq = dt_sys_clk_freq; + + /* Read cnt of CLKOUTs from device property*/ + ret = of_property_read_u32(node, "litex,nclkout", &dt_nclkout); + if (ret < 0) { + pr_err("No litex,nclkout entry in the dts file\n"); + return -ENODEV; + } + if (dt_nclkout > CLKOUT_MAX) { + pr_err("LiteX CLK driver cannot use more than %d clock outputs\n", + CLKOUT_MAX); + return -EINVAL; + } + l_dev->nclkout = dt_nclkout; + + /* count existing CLKOUTs */ + for_each_child_of_node(node, child_node) + i++; + if (i != dt_nclkout) { + pr_err("nclkout(%d) not matching actual CLKOUT count(%d)!", + dt_nclkout, i); + return -EINVAL; + } + + l_dev->clkouts = devm_kzalloc(dev, sizeof(*l_clkout) * i, GFP_KERNEL); + if (!l_dev->clkouts) { + pr_err("CLKOUT memory allocation failure!"); + return -ENOMEM; + } + + ret = litex_clk_get_timeouts(node, &dt_lock_timeout, &dt_drdy_timeout); + if (ret != 0) + return ret; + l_dev->lock_timeout = dt_lock_timeout; + l_dev->drdy_timeout = dt_drdy_timeout; + + return 0; +} + +static int litex_clk_init_glob_clk(struct device *dev, + struct device_node *node, + struct litex_clk_device *l_dev) +{ + struct clk_init_data init; + struct clk_hw *hw; + int ret; + + init.name = node->name; + init.ops = &litex_clk_ops; + init.flags = CLK_SET_RATE_UNGATE; + + hw = &l_dev->clk_hw; + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + if (ret != 0) { + pr_err("devm_clk_hw_register failure, ret: %d\n", ret); + return ret; + } + + ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw); + if (ret != 0) { + pr_err("of_clk_add_hw_provider failure, ret: %d", ret); + return ret; + } + + /* Power on MMCM module */ + ret = litex_clk_change_value(hw, FULL_REG_16, FULL_REG_16, POWER_REG); + if (ret != 0) { + pr_err("MMCM initialization failure, ret: %d", ret); + return ret; + } + + return 0; +} + +static int litex_clk_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + struct device *dev; + struct device_node *node; + struct litex_clk_device *litex_clk_device; + struct resource *res; + int ret; + + dev = &pdev->dev; + node = dev->of_node; + if (!node) + return -ENODEV; + + id = of_match_node(litex_of_match, node); + if (!id) + return -ENODEV; + + litex_clk_device = devm_kzalloc(dev, sizeof(*litex_clk_device), + GFP_KERNEL); + if (IS_ERR_OR_NULL(litex_clk_device)) + return -ENOMEM; + litex_clk_device->is_clkout = false; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR_OR_NULL(res)) + return -EBUSY; + + litex_clk_device->base = devm_of_iomap(dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(litex_clk_device->base)) + return -EIO; + + ret = litex_clk_read_global_dts(dev, node, litex_clk_device); + if (ret != 0) + return ret; + + ret = litex_clk_init_clkouts(dev, node, litex_clk_device); + if (ret != 0) + return ret; + + ret = litex_clk_init_glob_clk(dev, node, litex_clk_device); + if (ret != 0) + return ret; + + pr_info("litex clk control driver initialized"); + return 0; +} + +static int litex_clk_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static struct platform_driver litex_clk_driver = { + .driver = { + .name = "litex-clk", + .of_match_table = of_match_ptr(litex_of_match), + }, + .probe = litex_clk_probe, + .remove = litex_clk_remove, +}; + +module_platform_driver(litex_clk_driver); +MODULE_DESCRIPTION("LiteX clock driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/clk-litex.h b/drivers/clk/clk-litex.h new file mode 100644 index 00000000000000..76be275d581a6e --- /dev/null +++ b/drivers/clk/clk-litex.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#define LITEX_UNUSED(var) (void)(var) + +/* Common values */ +#define NS_IN_SEC 1000000000 +#define FULL_BYTE 0xFF +#define KHZ 1000 + +/* MMCM specific numbers */ +#define CLKOUT_MAX 7 +#define DELAY_TIME_MAX 63 +#define PHASE_MUX_MAX 7 +#define HIGH_LOW_TIME_SAFE_MAX 62 +#define HIGH_LOW_TIME_REG_MAX 63 +#define MAX_DIVIDER 126 +#define MAX_DIVIDER_F 126000 +#define MIN_DIVIDER 2 +#define MIN_DIVIDER_F 2000 +#define PHASE_MUX_RES_FACTOR 8 +/* Fractional divider resolution multiplied by 1000 to keep precision */ +#define FRACT_DIV_RES 125 +/* Minimal value when div is odd and fract is on */ +#define ODD_AND_FRAC 9 +/* Odd div and no frac or even div and enabled fract max value */ +#define EVEN_AND_FRAC 8 + +/* DRP registers index */ +/* Additional register */ +#define MMCM 0 +/* DRP registers */ +#define DRP_RESET 0 +#define DRP_READ 1 +#define DRP_WRITE 2 +#define DRP_DRDY 3 +#define DRP_ADR 4 +#define DRP_DAT_W 5 +#define DRP_DAT_R 6 +#define DRP_LOCKED 7 + +/* Register space offsets */ +#define DRP_OF_RESET 0x0 +#define DRP_OF_LOCKED 0x4 +#define DRP_OF_READ 0x8 +#define DRP_OF_WRITE 0xc +#define DRP_OF_DRDY 0x10 +#define DRP_OF_ADR 0x14 +#define DRP_OF_DAT_W 0x18 +#define DRP_OF_DAT_R 0x20 + +/* Register sizes */ +#define DRP_SIZE_RESET 0x1 +#define DRP_SIZE_READ 0x1 +#define DRP_SIZE_WRITE 0x1 +#define DRP_SIZE_DRDY 0x1 +#define DRP_SIZE_ADR 0x1 +#define DRP_SIZE_DAT_W 0x2 +#define DRP_SIZE_DAT_R 0x2 +#define DRP_SIZE_LOCKED 0x1 + +/* Register values */ +#define GLOB_DIV_VAL 0x41 +#define GLOB_MUL_VAL 0x82 +#define FULL_REG_16 0xFFFF +#define KEEP_REG_16 FULL_REG_16 +#define KEEP_IN_MUL 0xF000 +#define KEEP_IN_DIV 0xE000 +#define ZERO_REG 0x0 +#define REG1_MASK 0x1000 +#define REG2_MASK 0x8000 +#define REG1_BITSET 0x41 +#define REG2_BITSET 0x40 +#define CLKOUT5_FRAC_MASK 0xC3FF +#define CLKOUT5_FRAC_MASK_F 0xFBFF +#define CLKOUT5_FRAC_MASK_P 0xC7FF +#define REG1_FREQ_MASK 0xF000 +#define REG2_FREQ_MASK 0x803F +#define REG1_DUTY_MASK 0xF000 +#define REG2_DUTY_MASK 0xFF7F +#define REG1_PHASE_MASK 0x1FFF +#define REG2_PHASE_MASK 0xFCC0 +#define REG2_PHASE_F_MASK 0x80C0 +#define FILT_MASK 0x9900 +#define LOCK1_MASK 0x03FF +#define LOCK23_MASK 0x7FFF +/* Control bits extraction masks */ +#define HL_TIME_MASK 0x3F +#define FRAC_MASK 0x7 +#define EDGE_MASK 0x1 +#define NO_CNT_MASK 0x1 +#define FRAC_EN_MASK 0x1 +#define PHASE_MUX_MASK 0x7 +#define PHASE_MUX_F_MASK 0x7 +#define F_FRAC_MASK 0xF8 +#define TWO_LSBITS 0x3 + +/* Bit groups start position in DRP registers */ +#define HIGH_TIME_POS 6 +#define LOW_TIME_POS 0 +#define PHASE_MUX_POS 13 +#define PHASE_MUX_F_POS 11 +#define FRAC_POS 12 +#define FRAC_EN_POS 11 +#define FRAC_WF_R_POS 10 +#define FRAC_WF_F_POS 10 +#define EDGE_POS 7 +#define NO_CNT_POS 6 +#define DELAY_TIME_POS 0 + +/* MMCM Register addresses */ +#define POWER_REG 0x28 +#define DIV_REG 0x16 +#define LOCK_REG1 0x18 +#define LOCK_REG2 0x19 +#define LOCK_REG3 0x1A +#define FILT_REG1 0x4E +#define FILT_REG2 0x4F +#define CLKOUT0_REG1 0x08 +#define CLKOUT0_REG2 0x09 +#define CLKOUT1_REG1 0x0A +#define CLKOUT1_REG2 0x0B +#define CLKOUT2_REG1 0x0C +#define CLKOUT2_REG2 0x0D +#define CLKOUT3_REG1 0x0E +#define CLKOUT3_REG2 0x0F +#define CLKOUT4_REG1 0x10 +#define CLKOUT4_REG2 0x11 +#define CLKOUT5_REG1 0x06 +#define CLKOUT5_REG2 0x07 +#define CLKOUT6_REG1 0x12 +#define CLKOUT6_REG2 0x13 +#define CLKFBOUT_REG1 0x14 +#define CLKFBOUT_REG2 0x15 diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 5ff9438b7b4611..53dec5274b1360 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -91,6 +91,12 @@ config FPGA_MGR_TS73XX FPGA manager driver support for the Altera Cyclone II FPGA present on the TS-73xx SBC boards. +config FPGA_MGR_LITEX + tristate "LiteX ICAPBitstream FPGA Manager" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + help + FPGA Manager for LiteX SoC builder that uses ICAPBitstream. + config FPGA_BRIDGE tristate "FPGA Bridge Framework" help diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 18dc9885883a29..9d61ed0c3d99f8 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o obj-$(CONFIG_FPGA_MGR_ALTERA_CVP) += altera-cvp.o obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI) += altera-ps-spi.o obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o +obj-$(CONFIG_FPGA_MGR_LITEX) += litex-fpga.o obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI) += machxo2-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o diff --git a/drivers/fpga/litex-fpga.c b/drivers/fpga/litex-fpga.c new file mode 100644 index 00000000000000..c3a4f38a6610e7 --- /dev/null +++ b/drivers/fpga/litex-fpga.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OFFSET_REG_SINK_DATA 0x0 +#define OFFSET_REG_SINK_READY 0x10 + +#define REG_SINK_DATA_SIZE 0x4 +#define REG_SINK_READY_SIZE 0x1 + +#define INITIAL_HEADER_SIZE -1 /* Set to maximum value */ +#define ALLOWED_FPGA_MGR_FLAGS (FPGA_MGR_PARTIAL_RECONFIG | \ + FPGA_MGR_COMPRESSED_BITSTREAM) +#define BITSTREAM_INSTR_SIZE sizeof(uint32_t) + +/* Macros for accessing ICAP registers */ + +#define WRITE_SINK_DATA(mem, val) litex_write32(mem + OFFSET_REG_SINK_DATA, val) +#define READ_SINK_READY(mem) litex_read8(mem + OFFSET_REG_SINK_READY) + +struct litex_fpga { + void __iomem *membase; +}; + +/* Helper functions */ + +static inline bool bit_has_sync(uint8_t *buf, size_t count) +{ + int i; + + for (i = 0; i < count - BITSTREAM_INSTR_SIZE; i += BITSTREAM_INSTR_SIZE) + /* Sync word is 0xAA995566 */ + if (buf[i] == 0xAA && + buf[i + 1] == 0x99 && + buf[i + 2] == 0x55 && + buf[i + 3] == 0x66) + return true; + return false; +} + +static inline bool bit_is_aligned(uint8_t *buf, size_t count) +{ + return !(count % BITSTREAM_INSTR_SIZE); +} + +/* API functions */ + +static enum fpga_mgr_states litex_fpga_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static int litex_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + /* Check if driver supports given operations */ + if (info->flags & ~ALLOWED_FPGA_MGR_FLAGS) { + dev_err(&mgr->dev, "Unsupported bitstream flags occurred\n"); + return -EINVAL; + } + + return 0; +} + +static int litex_fpga_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + const struct litex_fpga *fpga_s = (const struct litex_fpga *) mgr->priv; + uint32_t *buf32; + int i, count32; + + /* Bitstream should consist of 32bit words*/ + if (!bit_is_aligned((uint8_t *) buf, count)) { + dev_err(&mgr->dev, "Invalid bitstream alignment\n"); + return -EINVAL; + } + + /* Correct bitstream contains sync word */ + if (!bit_has_sync((uint8_t *) buf, count)) { + dev_err(&mgr->dev, "Bitstream has no sync word\n"); + return -EINVAL; + } + + buf32 = (uint32_t *) buf; + count32 = count / BITSTREAM_INSTR_SIZE; + for (i = 0; i < count32; ++i) { + while (!READ_SINK_READY(fpga_s->membase)) + ; + WRITE_SINK_DATA(fpga_s->membase, be32_to_cpu(buf32[i])); + } + + return 0; +} + +static int litex_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + return 0; +} + +static const struct fpga_manager_ops litex_fpga_manager_ops = { + .initial_header_size = INITIAL_HEADER_SIZE, + .state = litex_fpga_state, + .write_init = litex_fpga_write_init, + .write = litex_fpga_write, + .write_complete = litex_fpga_write_complete, +}; + +/* Driver functions */ + +static int litex_fpga_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr; + + mgr = platform_get_drvdata(pdev); + fpga_mgr_unregister(mgr); + + return 0; +} + +static int litex_fpga_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_fpga *fpga_s; + struct fpga_manager *mgr; + struct resource *res; + + if (!node) + return -ENODEV; + + fpga_s = devm_kzalloc(&pdev->dev, sizeof(*fpga_s), GFP_KERNEL); + if (!fpga_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + fpga_s->membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(fpga_s->membase)) + return -EIO; + + mgr = devm_fpga_mgr_create(&pdev->dev, + "LiteX ICAPBitstream FPGA Manager", + &litex_fpga_manager_ops, fpga_s); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + return fpga_mgr_register(mgr); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,fpga-icap"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_fpga_driver = { + .driver = { + .name = "litex-icap-fpga-mgr", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_fpga_probe, + .remove = litex_fpga_remove +}; + +module_platform_driver(litex_fpga_driver); + +MODULE_DESCRIPTION("LiteX ICAPBitstream FPGA Manager driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e3607ec4c2e837..497bfd5e4d62ad 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -361,6 +361,12 @@ config GPIO_LOGICVC Say yes here to support GPIO functionality of the Xylon LogiCVC programmable logic block. +config GPIO_LITEX + tristate "LiteX GPIO support" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + help + driver for GPIO functionality on LiteX + config GPIO_LOONGSON bool "Loongson-2/3 GPIO support" depends on CPU_LOONGSON2EF || CPU_LOONGSON64 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index c58a90a3c3b116..0b1a8a23c3399a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_GPIO_IT87) += gpio-it87.o obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o obj-$(CONFIG_GPIO_KEMPLD) += gpio-kempld.o +obj-$(CONFIG_GPIO_LITEX) += gpio-litex.o obj-$(CONFIG_GPIO_LOGICVC) += gpio-logicvc.o obj-$(CONFIG_GPIO_LOONGSON1) += gpio-loongson1.o obj-$(CONFIG_GPIO_LOONGSON) += gpio-loongson.o diff --git a/drivers/gpio/gpio-litex.c b/drivers/gpio/gpio-litex.c new file mode 100644 index 00000000000000..70e05249c23bf4 --- /dev/null +++ b/drivers/gpio/gpio-litex.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_PINS_MAX 32 + +struct litex_gpio { + void __iomem *membase; + int port_direction; + int reg_span; + struct gpio_chip chip; +}; + +/* API functions */ + +static int litex_gpio_get_value(struct gpio_chip *chip, unsigned int offset) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv; + + if (offset >= chip->ngpio) + return -EINVAL; + + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); + return !!(regv & BIT(offset)); +} + +static int litex_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv; + + if (*mask >= (1 << chip->ngpio)) + return -EINVAL; + + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); + *bits = (regv & *mask); + return 0; +} + +static void litex_gpio_set_value(struct gpio_chip *chip, unsigned int offset, + int val) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv, new_regv; + + if (offset >= chip->ngpio) + return; + + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); + new_regv = (regv & ~BIT(offset)) | (!!val << offset); + _litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); +} + +static void litex_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + u32 regv, new_regv; + + if (*mask >= (1 << chip->ngpio)) + return; + + regv = _litex_get_reg(gpio_s->membase, gpio_s->reg_span); + new_regv = (regv & ~(*mask)) | (*bits); + _litex_set_reg(gpio_s->membase, gpio_s->reg_span, new_regv); +} + +static int litex_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + + return gpio_s->port_direction; +} + +static int litex_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + + if (gpio_s->port_direction != GPIOF_DIR_IN) + return -ENOTSUPP; + else + return 0; +} + +static int litex_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + + if (gpio_s->port_direction != GPIOF_DIR_OUT) + return -ENOTSUPP; + else + return 0; +} + +/* Driver functions */ + +static int litex_gpio_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_gpio *gpio_s; + struct resource *res; + int ret_i; + + int dt_ngpio; + const char *dt_direction; + + if (!node) + return -ENODEV; + + gpio_s = devm_kzalloc(&pdev->dev, sizeof(*gpio_s), GFP_KERNEL); + if (!gpio_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + gpio_s->membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(gpio_s->membase)) + return -EIO; + + ret_i = of_property_read_u32(node, "litex,ngpio", &dt_ngpio); + if (ret_i < 0) { + dev_err(&pdev->dev, "No litex,ngpio entry in the dts file\n"); + return -ENODEV; + } + if (dt_ngpio >= GPIO_PINS_MAX) { + dev_err(&pdev->dev, + "LiteX GPIO driver cannot use more than %d pins\n", + GPIO_PINS_MAX); + return -EINVAL; + } + + ret_i = of_property_read_string(node, "litex,direction", + &dt_direction); + if (ret_i < 0) { + dev_err(&pdev->dev, "No litex,direction entry in the dts file\n"); + return -ENODEV; + } + + if (!strcmp(dt_direction, "in")) + gpio_s->port_direction = GPIOF_DIR_IN; + else if (!strcmp(dt_direction, "out")) + gpio_s->port_direction = GPIOF_DIR_OUT; + else + return -ENODEV; + + /* Assign API functions */ + + gpio_s->chip.label = "litex_gpio"; + gpio_s->chip.owner = THIS_MODULE; + gpio_s->chip.get = litex_gpio_get_value; + gpio_s->chip.get_multiple = litex_gpio_get_multiple; + gpio_s->chip.set = litex_gpio_set_value; + gpio_s->chip.set_multiple = litex_gpio_set_multiple; + gpio_s->chip.get_direction = litex_gpio_get_direction; + gpio_s->chip.direction_input = litex_gpio_direction_input; + gpio_s->chip.direction_output = litex_gpio_direction_output; + gpio_s->chip.parent = &pdev->dev; + gpio_s->chip.base = -1; + gpio_s->chip.ngpio = dt_ngpio; + gpio_s->chip.can_sleep = false; + + gpio_s->reg_span = (dt_ngpio + LITEX_SUBREG_SIZE_BIT - 1) / + LITEX_SUBREG_SIZE_BIT; + + platform_set_drvdata(pdev, gpio_s); + return devm_gpiochip_add_data(&pdev->dev, &gpio_s->chip, gpio_s); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,gpio"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_gpio_driver = { + .driver = { + .name = "litex-gpio", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_gpio_probe, +}; + +module_platform_driver(litex_gpio_driver); + +MODULE_DESCRIPTION("LiteX gpio driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 85b79a7fee630b..fe790e37645cfd 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -386,6 +386,8 @@ source "drivers/gpu/drm/tidss/Kconfig" source "drivers/gpu/drm/xlnx/Kconfig" +source "drivers/gpu/drm/litevideo/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 926adef289db96..23854a6060b586 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -125,3 +125,4 @@ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ obj-$(CONFIG_DRM_MCDE) += mcde/ obj-$(CONFIG_DRM_TIDSS) += tidss/ obj-y += xlnx/ +obj-$(CONFIG_DRM_LITEVIDEO) += litevideo/ diff --git a/drivers/gpu/drm/litevideo/Kconfig b/drivers/gpu/drm/litevideo/Kconfig new file mode 100644 index 00000000000000..27a4c7660d4a9b --- /dev/null +++ b/drivers/gpu/drm/litevideo/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Copyright (C) 2020 Antmicro +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +config DRM_LITEVIDEO + tristate "LiteVideo support" + depends on DRM && LITEX_SOC_CONTROLLER + help + This enables DRM driver for LiteX LiteVideo. diff --git a/drivers/gpu/drm/litevideo/Makefile b/drivers/gpu/drm/litevideo/Makefile new file mode 100644 index 00000000000000..d752ef770bc36c --- /dev/null +++ b/drivers/gpu/drm/litevideo/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_LITEVIDEO) += litevideo.o diff --git a/drivers/gpu/drm/litevideo/litevideo.c b/drivers/gpu/drm/litevideo/litevideo.c new file mode 100644 index 00000000000000..f028a8fa883d6e --- /dev/null +++ b/drivers/gpu/drm/litevideo/litevideo.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "litevideo.h" +#include "mmcm.h" + +#define DRIVER_NAME "litevideo" + +#define DMA_DISABLE 0 +#define DMA_ENABLE 1 + +static void litevideo_get_md(u32 pixel_clock, u32 *best_m, u32 *best_d) +{ + u32 ideal_m, ideal_d, curr_m, curr_d, m, d; + u32 curr_diff, test_diff; + + ideal_m = pixel_clock; + ideal_d = LITEVIDEO_IDEAL_DIV_VALUE; + + /* Start searching from minimum values */ + + curr_m = MMCM_MIN_M; + curr_d = MMCM_MIN_D; + + for (d = MMCM_MIN_D; d < MMCM_MAX_D; d++) { + for (m = MMCM_MIN_M; m < MMCM_MAX_M; m++) { + + /* Clocks cannot be set perfectly, therefore all + * combinations for multiplier (m) and divisor (d) + * are checked to find the closest possible clock value + */ + + curr_diff = abs((d * ideal_d * curr_m) - + (d * curr_d * ideal_m)); + test_diff = abs((curr_d * ideal_d * m) - + (d * curr_d * ideal_m)); + + if (test_diff < curr_diff) { + curr_m = m; + curr_d = d; + } + } + } + + *best_m = curr_m; + *best_d = curr_d; +} + +static int litevideo_mmcm_write(struct litevideo_prv *prv, u32 addr, u32 data) +{ + /* write MMCM register address */ + + litex_write8(prv->base + LITEVIDEO_MMCM_ADDR_OFF, addr); + + /* write data to send to MMCM register */ + + litex_write16(prv->base + LITEVIDEO_MMCM_DATA_OFF, data); + + /* send the data */ + + litex_write8(prv->base + LITEVIDEO_MMCM_WRITE_OFF, MMCM_WRITE); + + /* wait for transfer finish */ + + if (!wait_event_timeout(prv->wq, + litex_read8(prv->base + LITEVIDEO_MMCM_READY_OFF), + HZ)) + return -ETIMEDOUT; + + return 0; +} + +static int litevideo_clkgen_write(struct litevideo_prv *prv, u32 m, u32 d) +{ + /* write M */ + + int ret; + + ret = litevideo_mmcm_write(prv, MMCM_CLKFBOUT1, + MMCM_HT_FALLING_EDGE | + (m / 2) << MMCM_HT_SHIFT | + (m / 2 + (m % 2)) << MMCM_LT_SHIFT); + + if (ret < 0) + return ret; + + /* write D */ + + if (d == 1) + ret = litevideo_mmcm_write(prv, MMCM_DIVCLK, + MMCM_HT_FALLING_EDGE); + else + ret = litevideo_mmcm_write(prv, MMCM_DIVCLK, + (d / 2) << MMCM_HT_SHIFT | + (d / 2 + (d % 2)) << MMCM_LT_SHIFT); + + if (ret < 0) + return ret; + + /* clkout0_divide = 10 */ + + ret = litevideo_mmcm_write(prv, MMCM_CLKOUT0, + MMCM_HT_FALLING_EDGE | + MMCM_CLKOUT_DIV10 << MMCM_HT_SHIFT | + MMCM_CLKOUT_DIV10 << MMCM_LT_SHIFT); + + if (ret < 0) + return ret; + + /* clkout1_divide = 2 */ + + ret = litevideo_mmcm_write(prv, MMCM_CLKOUT1, + MMCM_HT_FALLING_EDGE | + MMCM_CLKOUT_DIV2 << MMCM_HT_SHIFT | + MMCM_CLKOUT_DIV2 << MMCM_LT_SHIFT); + + return ret; +} + +static int litevideo_drv_init(struct litevideo_prv *prv, u32 m, u32 d) +{ + int ret; + + /* initialize waitqueue for timeouts in litex_mmcm_write() */ + + init_waitqueue_head(&prv->wq); + + /* generate clock */ + + ret = litevideo_clkgen_write(prv, m, d); + if (ret < 0) + return ret; + + /* timings - horizontal */ + + litex_write16(prv->base + LITEVIDEO_CORE_HRES_OFF, + prv->h_active); + litex_write16(prv->base + LITEVIDEO_CORE_HSYNC_START_OFF, + prv->h_active + prv->h_front_porch); + litex_write16(prv->base + LITEVIDEO_CORE_HSYNC_END_OFF, + prv->h_active + prv->h_front_porch + prv->v_sync); + litex_write16(prv->base + LITEVIDEO_CORE_HSCAN_OFF, + prv->h_active + prv->h_blanking); + + /* timings - vertical */ + + litex_write16(prv->base + LITEVIDEO_CORE_VRES_OFF, + prv->v_active); + litex_write16(prv->base + LITEVIDEO_CORE_VSYNC_START_OFF, + prv->v_active + prv->v_front_porch); + litex_write16(prv->base + LITEVIDEO_CORE_VSYNC_END_OFF, + prv->v_active + prv->v_front_porch + prv->v_sync); + litex_write16(prv->base + LITEVIDEO_CORE_VSCAN_OFF, + prv->v_active + prv->v_blanking); + + /* configure DMA */ + + litex_write8(prv->base + LITEVIDEO_DMA_ENABLE_OFF, DMA_DISABLE); + // FIXME: gateware and this call should be updated to 64-bit base addr! + litex_write32(prv->base + LITEVIDEO_DMA_BASE_ADDR_OFF, prv->dma_offset); + litex_write32(prv->base + LITEVIDEO_DMA_LENGTH_OFF, prv->dma_length); + litex_write8(prv->base + LITEVIDEO_DMA_ENABLE_OFF, DMA_ENABLE); + + return 0; +} + +static int litevideo_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct litevideo_prv *prv; + struct resource *res; + int ret; + u32 val; + u32 m, d; + + /* no device tree */ + + if (!np) + return -ENODEV; + + prv = devm_kzalloc(&pdev->dev, sizeof(*prv), GFP_KERNEL); + if (!prv) + return -ENOMEM; + + /* pixel clock */ + + ret = of_property_read_u32(np, "litevideo,pixel-clock", &val); + if (ret) + return -EINVAL; + prv->pixel_clock = val; + + /* timings - vertical */ + + ret = of_property_read_u32(np, "litevideo,v-active", &val); + if (ret) + return -EINVAL; + prv->v_active = val; + + ret = of_property_read_u32(np, "litevideo,v-blanking", &val); + if (ret) + return -EINVAL; + prv->v_blanking = val; + + ret = of_property_read_u32(np, "litevideo,v-front-porch", &val); + if (ret) + return -EINVAL; + prv->v_front_porch = val; + + ret = of_property_read_u32(np, "litevideo,v-sync", &val); + if (ret) + return -EINVAL; + prv->v_sync = val; + + /* timings - horizontal */ + + ret = of_property_read_u32(np, "litevideo,h-active", &val); + if (ret) + return -EINVAL; + prv->h_active = val; + + ret = of_property_read_u32(np, "litevideo,h-blanking", &val); + if (ret) + return -EINVAL; + prv->h_blanking = val; + + ret = of_property_read_u32(np, "litevideo,h-front-porch", &val); + if (ret) + return -EINVAL; + prv->h_front_porch = val; + + ret = of_property_read_u32(np, "litevideo,h-sync", &val); + if (ret) + return -EINVAL; + prv->h_sync = val; + + /* DMA */ + + ret = of_property_read_u32(np, "litevideo,dma-offset", &val); + if (ret) + return -EINVAL; + prv->dma_offset = val; + + ret = of_property_read_u32(np, "litevideo,dma-length", &val); + if (ret) + return -EINVAL; + prv->dma_length = val; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + prv->base = devm_ioremap_resource(&pdev->dev, res); + if (!prv->base) + return -ENXIO; + + litevideo_get_md(prv->pixel_clock, &m, &d); + return litevideo_drv_init(prv, m, d); +} + +static const struct of_device_id litevideo_of_match[] = { + { .compatible = "litex,litevideo" }, + {} +}; +MODULE_DEVICE_TABLE(of, litevideo_of_match); + +static struct platform_driver litevideo_platform_driver = { + .probe = litevideo_probe, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = litevideo_of_match, + }, +}; + +module_platform_driver(litevideo_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("LiteVideo driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/litevideo/litevideo.h b/drivers/gpu/drm/litevideo/litevideo.h new file mode 100644 index 00000000000000..c0fbaaa83d9f50 --- /dev/null +++ b/drivers/gpu/drm/litevideo/litevideo.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LITEVIDEO_LITEVIDEO_H_ +#define _LITEVIDEO_LITEVIDEO_H_ + +#include +#include +#include +#include + +/* register offsets */ + +#define LITEVIDEO_CORE_HRES_OFF 0x1c +#define LITEVIDEO_CORE_HSYNC_START_OFF 0x24 +#define LITEVIDEO_CORE_HSYNC_END_OFF 0x2c +#define LITEVIDEO_CORE_HSCAN_OFF 0x34 +#define LITEVIDEO_CORE_VRES_OFF 0x3c +#define LITEVIDEO_CORE_VSYNC_START_OFF 0x44 +#define LITEVIDEO_CORE_VSYNC_END_OFF 0x4c +#define LITEVIDEO_CORE_VSCAN_OFF 0x54 + +#define LITEVIDEO_MMCM_WRITE_OFF 0x94 +#define LITEVIDEO_MMCM_READY_OFF 0x98 +#define LITEVIDEO_MMCM_ADDR_OFF 0x9c +#define LITEVIDEO_MMCM_DATA_OFF 0xa0 + +#define LITEVIDEO_DMA_ENABLE_OFF 0x18 +#define LITEVIDEO_DMA_BASE_ADDR_OFF 0x5c +#define LITEVIDEO_DMA_LENGTH_OFF 0x6c + +/* register sizes */ + +#define LITEVIDEO_CORE_HRES_SIZE 2 +#define LITEVIDEO_CORE_HSYNC_START_SIZE 2 +#define LITEVIDEO_CORE_HSYNC_END_SIZE 2 +#define LITEVIDEO_CORE_HSCAN_SIZE 2 +#define LITEVIDEO_CORE_VRES_SIZE 2 +#define LITEVIDEO_CORE_VSYNC_START_SIZE 2 +#define LITEVIDEO_CORE_VSYNC_END_SIZE 2 +#define LITEVIDEO_CORE_VSCAN_SIZE 2 + +#define LITEVIDEO_MMCM_WRITE_SIZE 1 +#define LITEVIDEO_MMCM_READY_SIZE 1 +#define LITEVIDEO_MMCM_ADDR_SIZE 1 +#define LITEVIDEO_MMCM_DATA_SIZE 2 + +#define LITEVIDEO_DMA_ENABLE_SIZE 1 +#define LITEVIDEO_DMA_BASE_ADDR_SIZE 4 +#define LITEVIDEO_DMA_LENGTH_SIZE 4 + +/* constants */ + +/* clk100 has to be used for LiteX VideoOut */ +#define LITEVIDEO_IDEAL_DIV_VALUE 10000 + +struct litevideo_prv { + struct drm_device *drm_dev; + void __iomem *base; + struct clk *hdmi_clk; + wait_queue_head_t wq; + u32 h_active; + u32 h_blanking; + u32 h_front_porch; + u32 h_sync; + u32 v_active; + u32 v_blanking; + u32 v_front_porch; + u32 v_sync; + u32 dma_offset; + u32 dma_length; + u32 pixel_clock; +}; + +#endif /* _LITEVIDEO_LITEVIDEO_H_ */ diff --git a/drivers/gpu/drm/litevideo/mmcm.h b/drivers/gpu/drm/litevideo/mmcm.h new file mode 100644 index 00000000000000..de25c5a0ac955b --- /dev/null +++ b/drivers/gpu/drm/litevideo/mmcm.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LITEVIDEO_MMCM_H_ +#define _LITEVIDEO_MMCM_H_ + +/* mmcm addresses */ + +#define MMCM_CLKFBOUT1 0x14 +#define MMCM_DIVCLK 0x16 +#define MMCM_CLKOUT0 0x08 +#define MMCM_CLKOUT1 0x0a + +/* predefined values and shifts */ + +#define MMCM_HT_FALLING_EDGE 0x1000 +#define MMCM_HT_SHIFT 6 +#define MMCM_LT_SHIFT 0 + +/* This values should be set both to HIGH_TIME and LOW_TIME in CLKOUTx + * - clk division time = 2*VALUE // (i.e. MMCM_CLKOUT_DIV10) + * - duty = 50% // (HIGH_TIME / (HIGH_TIME + LOW_TIME)) + */ + +#define MMCM_CLKOUT_DIV10 5 +#define MMCM_CLKOUT_DIV2 1 + +#define MMCM_WRITE 1 + +#define MMCM_MIN_M 2 +#define MMCM_MAX_M 128 +#define MMCM_MIN_D 1 +#define MMCM_MAX_D 128 + +#endif /* _LITEVIDEO_MMCM_H_ */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 54f04e61fb8364..430a262d52d356 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1180,6 +1180,12 @@ config SENSORS_ADCXX This driver can also be built as a module. If so, the module will be called adcxx. +config SENSORS_LITEX_HWMON + tristate "LiteX XADC hardware monitor" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + help + This enables hardware monitoring using LiteX XADC. + config SENSORS_LM63 tristate "National Semiconductor LM63 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index fe38e8a5c979ae..0b947c2f4b7ff7 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -97,6 +97,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o +obj-$(CONFIG_SENSORS_LITEX_HWMON) += litex-hwmon.o obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o diff --git a/drivers/hwmon/litex-hwmon.c b/drivers/hwmon/litex-hwmon.c new file mode 100644 index 00000000000000..c73ac6223c89bf --- /dev/null +++ b/drivers/hwmon/litex-hwmon.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#define TEMP_REG_OFFSET 0x0 +#define TEMP_REG_SIZE 2 +#define VCCINT_REG_OFFSET 0x8 +#define VCCINT_REG_SIZE 2 +#define VCCAUX_REG_OFFSET 0x10 +#define VCCAUX_REG_SIZE 2 +#define VCCBRAM_REG_OFFSET 0x18 +#define VCCBRAM_REG_SIZE 2 + +#define CHANNEL_TEMP 0 +#define CHANNEL_VCCINT 0 +#define CHANNEL_VCCAUX 1 +#define CHANNEL_VCCBRAM 2 + +struct litex_hwmon { + void __iomem *membase; + struct device *hdev; +}; + +/* Transfer functions taken from XILINX UG480 (v1.10.1) + * www.xilinx.com/support/documentation/user_guides/ug480_7Series_XADC.pdf + */ + +static inline long litex_temp_transfer_fun(long val) +{ + return ((val * 503975ULL) / 4096ULL) - 273150ULL; +} + +static inline long litex_supp_transfer_fun(long val) +{ + return ((val * 3000) / 4096); +} + +static inline int litex_read_temp(struct litex_hwmon *hwmon_s, u32 attr, + int channel, long *val) +{ + unsigned long raw_data; + + if (attr != hwmon_temp_input) + return -ENOTSUPP; + + if (channel != CHANNEL_TEMP) + return -EINVAL; + + raw_data = litex_read16(hwmon_s->membase + TEMP_REG_OFFSET); + *val = litex_temp_transfer_fun(raw_data); + return 0; +} + +static inline int litex_read_in(struct litex_hwmon *hwmon_s, u32 attr, + int channel, long *val) +{ + int offset; + unsigned long raw_data; + + if (attr != hwmon_in_input) + return -ENOTSUPP; + + switch (channel) { + case CHANNEL_VCCINT: + offset = VCCINT_REG_OFFSET; + break; + case CHANNEL_VCCAUX: + offset = VCCAUX_REG_OFFSET; + break; + case CHANNEL_VCCBRAM: + offset = VCCBRAM_REG_OFFSET; + break; + default: + return -EINVAL; + } + + raw_data = litex_read16(hwmon_s->membase + offset); + *val = litex_supp_transfer_fun(raw_data); + return 0; +} + +/* API functions */ + +umode_t litex_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +int litex_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct litex_hwmon *hwmon_s = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return litex_read_temp(hwmon_s, attr, channel, val); + case hwmon_in: + return litex_read_in(hwmon_s, attr, channel, val); + default: + return -ENOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops litex_hwmon_ops = { + .is_visible = litex_hwmon_is_visible, + .read = litex_hwmon_read, +}; + +/* Attribute management */ + +static const unsigned int litex_temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info litex_hwmon_temp = { + .type = hwmon_temp, + .config = litex_temp_config +}; + +static const unsigned int litex_vcc_config[] = { + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + 0 +}; + +static const struct hwmon_channel_info litex_hwmon_vcc = { + .type = hwmon_in, + .config = litex_vcc_config +}; + +static const struct hwmon_channel_info *litex_hwmon_channel_info[] = { + &litex_hwmon_temp, + &litex_hwmon_vcc, + NULL +}; + +static const struct hwmon_chip_info litex_chip_info = { + .ops = &litex_hwmon_ops, + .info = litex_hwmon_channel_info +}; + +/* Driver functions */ + +static int litex_hwmon_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_hwmon *hwmon_s; + struct resource *res; + + if (!node) + return -ENODEV; + + hwmon_s = devm_kzalloc(&pdev->dev, sizeof(*hwmon_s), GFP_KERNEL); + if (!hwmon_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + hwmon_s->membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(hwmon_s->membase)) + return -EIO; + + hwmon_s->hdev = devm_hwmon_device_register_with_info(&pdev->dev, + "litex_xadc", + hwmon_s, + &litex_chip_info, + NULL); + platform_set_drvdata(pdev, hwmon_s); + return PTR_ERR_OR_ZERO(hwmon_s->hdev); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,hwmon-xadc"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_hwmon_driver = { + .driver = { + .name = "litex-hwmon", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_hwmon_probe, +}; + +module_platform_driver(litex_hwmon_driver); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 05ebf7546e3f61..4bac4a3f902905 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -715,6 +715,13 @@ config I2C_KEMPLD This driver can also be built as a module. If so, the module will be called i2c-kempld. +config I2C_LITEX + tristate "LiteX I2C support" + depends on OF && HAS_IOMEM && LITEX_SOC_CONTROLLER + select I2C_ALGOBIT + help + This enables I2C bitbang driver for LiteX SoC builder. + config I2C_LPC2K tristate "I2C bus support for NXP LPC2K/LPC178x/18xx/43xx" depends on OF && (ARCH_LPC18XX || COMPILE_TEST) diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 615f35e3e31f88..158470e962e442 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_I2C_IMX_LPI2C) += i2c-imx-lpi2c.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_JZ4780) += i2c-jz4780.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o +obj-$(CONFIG_I2C_LITEX) += i2c-litex.o obj-$(CONFIG_I2C_LPC2K) += i2c-lpc2k.o obj-$(CONFIG_I2C_MESON) += i2c-meson.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o diff --git a/drivers/i2c/busses/i2c-litex.c b/drivers/i2c/busses/i2c-litex.c new file mode 100644 index 00000000000000..7723a59a5ba336 --- /dev/null +++ b/drivers/i2c/busses/i2c-litex.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#define REGISTER_SIZE 1 +#define OFFSET_REG_W 0x0 +#define OFFSET_REG_R 0x4 + +#define BITPOS_SCL 0 +#define BITPOS_SDA_DIR 1 +#define BITPOS_SDA_W 2 +#define BITPOS_SDA_R 0 + +#define SETDIR_SDA_OUTPUT 1 +#define SETDIR_SDA_INPUT 0 + +#define DRIVER_ALGO_BIT_UDELAY 20 + +struct litex_i2c { + void __iomem *reg_w; + void __iomem *reg_r; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo_data; +}; + +/* Helper functions */ + +static inline void litex_set_bit(void __iomem *mem, int bit, int val) +{ + u32 regv, new_regv; + + regv = litex_read8(mem); + new_regv = (regv & ~BIT(bit)) | ((!!val) << bit); + litex_write8(mem, new_regv); +} + +static inline int litex_get_bit(void __iomem *mem, int bit) +{ + u32 regv; + + regv = litex_read8(mem); + return !!(regv & BIT(bit)); +} + +/* API functions */ + +static void litex_i2c_setscl(void *data, int state) +{ + struct litex_i2c *i2c = (struct litex_i2c *) data; + + litex_set_bit(i2c->reg_w, BITPOS_SCL, state); +} + +static void litex_i2c_setsda(void *data, int state) +{ + struct litex_i2c *i2c = (struct litex_i2c *) data; + + litex_set_bit(i2c->reg_w, BITPOS_SDA_DIR, SETDIR_SDA_OUTPUT); + litex_set_bit(i2c->reg_w, BITPOS_SDA_W, state); +} + +static int litex_i2c_getsda(void *data) +{ + struct litex_i2c *i2c = (struct litex_i2c *) data; + + litex_set_bit(i2c->reg_w, BITPOS_SDA_DIR, SETDIR_SDA_INPUT); + return litex_get_bit(i2c->reg_r, BITPOS_SDA_R); +} + +/* Driver functions */ + +static int litex_i2c_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + void __iomem *membase; + struct litex_i2c *i2c_s; + struct resource *res; + + if (!node) + return -ENODEV; + + i2c_s = devm_kzalloc(&pdev->dev, sizeof(*i2c_s), GFP_KERNEL); + if (!i2c_s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + membase = devm_of_iomap(&pdev->dev, node, 0, &res->end); + if (IS_ERR_OR_NULL(membase)) + return -EIO; + + i2c_s->reg_w = membase + OFFSET_REG_W; + i2c_s->reg_r = membase + OFFSET_REG_R; + + strncpy(i2c_s->adapter.name, "litex_i2c_adapter", + sizeof(i2c_s->adapter.name)); + i2c_s->adapter.owner = THIS_MODULE; + i2c_s->adapter.algo_data = &i2c_s->algo_data; + i2c_s->adapter.dev.parent = &pdev->dev; + i2c_s->adapter.dev.of_node = node; + i2c_s->algo_data.data = i2c_s; + + i2c_s->algo_data.setsda = litex_i2c_setsda; + i2c_s->algo_data.setscl = litex_i2c_setscl; + i2c_s->algo_data.getsda = litex_i2c_getsda; + i2c_s->algo_data.getscl = NULL; + i2c_s->algo_data.udelay = DRIVER_ALGO_BIT_UDELAY; + i2c_s->algo_data.timeout = HZ; + + platform_set_drvdata(pdev, i2c_s); + return i2c_bit_add_bus(&i2c_s->adapter); +} + +static const struct of_device_id litex_of_match[] = { + {.compatible = "litex,i2c"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_i2c_driver = { + .driver = { + .name = "litex-i2c", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_i2c_probe, +}; + +module_platform_driver(litex_i2c_driver); + +MODULE_DESCRIPTION("LiteX bitbang I2C Bus driver"); +MODULE_AUTHOR("Antmicro "); diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 15536e321df556..7e107977d31916 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -540,6 +540,24 @@ config LOONGSON_HTPIC select GENERIC_IRQ_CHIP help Support for the Loongson-3 HyperTransport PIC Controller. +config LITEX_VEXRISCV_INTC + bool "LiteX VexRiscv interrupt controller support" + select IRQ_DOMAIN + help + Support for the VexRiscv's interrupt controller. VexRiscv does not follow + RISC-V privileged specification and uses two additional CSRs: mask and + pending. Please refer to LITEX_VEXRISCV_CSR_MASK and + LITEX_VEXRISCV_CSR_PENDING for their respective configuration. + +config LITEX_VEXRISCV_CSR_MASK + depends on LITEX_VEXRISCV_INTC + hex "VexRiscv's mask CSR index." + default 0x9c0 + +config LITEX_VEXRISCV_CSR_PENDING + depends on LITEX_VEXRISCV_INTC + hex "VexRiscv's pending CSR index." + default 0xdc0 config LOONGSON_HTVEC bool "Loongson3 HyperTransport Interrupt Vector Controller" diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c59b95a0532c9a..4a098d1aa69248 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o obj-$(CONFIG_MADERA_IRQ) += irq-madera.o +obj-$(CONFIG_LITEX_VEXRISCV_INTC) += irq-litex-vexriscv.o obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o diff --git a/drivers/irqchip/irq-litex-vexriscv.c b/drivers/irqchip/irq-litex-vexriscv.c new file mode 100644 index 00000000000000..a4fe688dd81986 --- /dev/null +++ b/drivers/irqchip/irq-litex-vexriscv.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VexRiscv interrupt controller driver + * + * Copyright (C) 2019 Antmicro Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#define IRQ_MASK CONFIG_LITEX_VEXRISCV_CSR_MASK +#define IRQ_PENDING CONFIG_LITEX_VEXRISCV_CSR_PENDING +#define IRQ_VEC_SZ 32 + +static struct irq_domain *root_domain; + +struct litex_vexriscv_intc { + struct irq_chip chip; + irq_flow_handler_t handle; + unsigned long flags; +}; + +static unsigned int litex_vexriscv_irq_getie(void) +{ + return (csr_read(sstatus) & IE_SIE) != 0; +} + +static void litex_vexriscv_irq_setie(unsigned int ie) +{ + if (ie) + csr_write(sstatus, IE_SIE); + else + csr_clear(sstatus, IE_SIE); +} + +static unsigned int litex_vexriscv_irq_getmask(void) +{ + u32 mask; + + __asm__ __volatile__ ("csrr %0, %1" : "=r"(mask) : "i"(IRQ_MASK)); + return mask; +} + +static void litex_vexriscv_irq_setmask(unsigned int mask) +{ + __asm__ volatile ("csrw %0, %1" :: "i"(IRQ_MASK), "r"(mask)); +} + +static unsigned int litex_vexriscv_irq_pending(void) +{ + u32 pending; + + __asm__ __volatile__ ("csrr %0, %1" : "=r"(pending) : "i"(IRQ_PENDING)); + return pending; +} + +static int litex_vexriscv_intc_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct litex_vexriscv_intc *intc = d->host_data; + + pr_debug("irqchip: LiteX Vexriscv irqmap: %d\n", irq); + irq_set_chip_and_handler(irq, &intc->chip, intc->handle); + irq_set_status_flags(irq, intc->flags); + + pr_debug("irqchip: mapcount: %d\n", d->mapcount); + + return 0; +} + +static void litex_vexriscv_intc_mask(struct irq_data *data) +{ + unsigned int mask = litex_vexriscv_irq_getmask(); + unsigned int irqbit = BIT(data->hwirq); + + pr_debug("irqchip: LiteX VexRiscv irqchip mask: 0x%08x -> 0x%08x", + mask, mask & ~irqbit); + mask &= ~irqbit; + litex_vexriscv_irq_setmask(mask); + + if (!mask) + litex_vexriscv_irq_setie(0); +} + +static void litex_vexriscv_intc_unmask(struct irq_data *data) +{ + unsigned int mask = litex_vexriscv_irq_getmask(); + unsigned int irqbit = BIT(data->hwirq); + + pr_debug("irqchip: LiteX VexRiscv irqchip mask: 0x%08x -> 0x%08x", + mask, mask | irqbit); + mask |= irqbit; + litex_vexriscv_irq_setmask(mask); + + if (!litex_vexriscv_irq_getie()) + litex_vexriscv_irq_setie(1); +} + +static void litex_vexriscv_intc_ack(struct irq_data *data) +{ + /* no need to ack IRQs */ +} + +static void litex_vexriscv_intc_handle_irq(struct pt_regs *regs) +{ + unsigned int irq; + int i; + + irq = litex_vexriscv_irq_pending() & litex_vexriscv_irq_getmask(); + pr_debug("irqchip: Litex Vexriscv irqchip mask: 0x%08x pending: 0x%08x", + litex_vexriscv_irq_getmask(), + litex_vexriscv_irq_pending()); + + for (i = 0; i < IRQ_VEC_SZ; i++) { + pr_debug("irqchip: i %d, irq 0x%08x, trying 0x%08lx\n", i, irq, BIT(i)); + + if (irq & BIT(i)) { + pr_debug("irqchip: handling at i = %d\n", i); + handle_domain_irq(root_domain, i, regs); + } + } +} + +static const struct irq_domain_ops litex_vexriscv_domain_ops = { + .xlate = irq_domain_xlate_onecell, + .map = litex_vexriscv_intc_map, +}; + +static struct litex_vexriscv_intc litex_vexriscv_intc0 = { + .handle = handle_edge_irq, + .flags = IRQ_TYPE_DEFAULT | IRQ_TYPE_PROBE, + .chip = { + .name = "litex-vexriscv-intc0", + .irq_mask = litex_vexriscv_intc_mask, + .irq_unmask = litex_vexriscv_intc_unmask, + .irq_ack = litex_vexriscv_intc_ack, + }, +}; + +static int __init litex_vexriscv_intc_init(struct device_node *node, struct device_node *parent) +{ + /* clear interrupts for init */ + litex_vexriscv_irq_setie(0); + litex_vexriscv_irq_setmask(0); + + root_domain = irq_domain_add_linear(node, IRQ_VEC_SZ, + &litex_vexriscv_domain_ops, + &litex_vexriscv_intc0); + irq_set_default_host(root_domain); + set_handle_irq(litex_vexriscv_intc_handle_irq); + + /* print device info */ + pr_info("irqchip: LiteX VexRiscv irqchip driver initialized. " + "IE: %d, mask: 0x%08x, pending: 0x%08x\n", + litex_vexriscv_irq_getie(), + litex_vexriscv_irq_getmask(), + litex_vexriscv_irq_pending()); + pr_info("irqchip: LiteX VexRiscv irqchip settings: " + "mask CSR 0x%03x, pending CSR 0x%03x\n", IRQ_MASK, + IRQ_PENDING); + + return 0; +} + +IRQCHIP_DECLARE(litex_vexriscv_intc0, "vexriscv,intc0", litex_vexriscv_intc_init); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index b236dfe2e87983..3e034316e61c85 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1089,3 +1089,9 @@ config MMC_OWL config MMC_SDHCI_EXTERNAL_DMA bool + +config MMC_LITEX + tristate "Support for the MMC Controller in LiteX SOCs" + depends on OF && LITEX + help + Generic MCC driver for LiteX diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 6df5c4774260c4..2532f236c14dfe 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_MMC_CQHCI) += cqhci.o 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 ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/litex_mmc.c b/drivers/mmc/host/litex_mmc.c new file mode 100644 index 00000000000000..020c64617304f0 --- /dev/null +++ b/drivers/mmc/host/litex_mmc.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2020 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "litex_mmc.h" + +#define SDCARD_CTRL_DATA_TRANSFER_NONE 0 +#define SDCARD_CTRL_DATA_TRANSFER_READ 1 +#define SDCARD_CTRL_DATA_TRANSFER_WRITE 2 + +#define SDCARD_CTRL_RESPONSE_NONE 0 +#define SDCARD_CTRL_RESPONSE_SHORT 1 +#define SDCARD_CTRL_RESPONSE_LONG 2 + +#define SD_OK 0 +#define SD_WRITEERROR 1 +#define SD_TIMEOUT 2 +#define SD_CRCERROR 3 +#define SD_ERR_OTHER 4 + +struct litex_mmc_host { + struct mmc_host *mmc; + struct platform_device *dev; + + void __iomem *sdphy; + void __iomem *sdcore; + void __iomem *sdreader; + void __iomem *sdwriter; + + u32 resp[4]; + u16 rca; + + void *buffer; + size_t buffer_size; + dma_addr_t dma_handle; + + unsigned int freq; + unsigned int clock; + bool is_bus_width_set; + bool app_cmd; +}; + + +void sdclk_set_clk(struct litex_mmc_host *host, unsigned int clk_freq) { + u32 div = clk_freq ? host->freq / clk_freq : 256; + div = roundup_pow_of_two(div); + div = min(max(div, (u32)2), (u32)256); + dev_info(&host->dev->dev, + "Requested clk_freq=%d: set to %d via div=%d\n", + clk_freq, host->freq / div, div); + litex_write16(host->sdphy + LITEX_MMC_SDPHY_CLOCKERDIV_OFF, div); +} + + +static int sdcard_wait_done(void __iomem *reg) { + u8 evt; + for (;;) { + evt = litex_read8(reg); + if (evt & 0x1) + break; + udelay(5); + } + if (evt == 0x1) + return SD_OK; + if (evt & 0x2) + return SD_WRITEERROR; + if (evt & 0x4) + return SD_TIMEOUT; + if (evt & 0x8) + return SD_CRCERROR; + pr_err("sdcard_wait_done: unknown error evt=%x\n", evt); + return SD_ERR_OTHER; +} + +static int send_cmd(struct litex_mmc_host *host, u8 cmd, u32 arg, + u8 response_len, u8 transfer) { + void __iomem *reg; + ulong n; + u8 i; + int status; + + litex_write32(host->sdcore + LITEX_MMC_SDCORE_CMDARG_OFF, arg); + litex_write32(host->sdcore + LITEX_MMC_SDCORE_CMDCMD_OFF, + cmd << 8 | transfer << 5 | response_len); + litex_write8(host->sdcore + LITEX_MMC_SDCORE_CMDSND_OFF, 1); + + status = sdcard_wait_done(host->sdcore + LITEX_MMC_SDCORE_CMDEVT_OFF); + if (status != SD_OK) { + pr_err("Command (cmd %d) failed, status %d\n", cmd, status); + return status; + } + + if (response_len != SDCARD_CTRL_RESPONSE_NONE) { + reg = host->sdcore + LITEX_MMC_SDCORE_CMDRSP_OFF; + for (i = 0; i < 4; i++) { + host->resp[i] = litex_read32(reg); + reg += _next_reg_off(0, sizeof(u32)); + } + } + + if (!host->app_cmd && cmd == SD_SEND_RELATIVE_ADDR) { + host->rca = (host->resp[3] >> 16) & 0xffff; + } + + host->app_cmd = (cmd == MMC_APP_CMD); + + if (transfer == SDCARD_CTRL_DATA_TRANSFER_NONE) + return status; /* SD_OK from prior sdcard_wait_done(cmd_evt) */ + + status = sdcard_wait_done(host->sdcore + LITEX_MMC_SDCORE_DATAEVT_OFF); + if (status != SD_OK){ + pr_err("Data xfer (cmd %d) failed, status %d\n", cmd, status); + return status; + } + + /* wait for completion of (read or write) DMA transfer */ + reg = (transfer == SDCARD_CTRL_DATA_TRANSFER_READ) ? + host->sdreader + LITEX_MMC_SDBLK2MEM_DONE_OFF : + host->sdwriter + LITEX_MMC_SDMEM2BLK_DONE_OFF; + n = jiffies + (HZ << 1); + while ((litex_read8(reg) & 0x01) == 0) + if (time_after(jiffies, n)) { + pr_err("DMA timeout (cmd %d)\n", cmd); + return SD_TIMEOUT; + } + + return status; +} + +// CMD12 +static inline int send_stop_tx_cmd(struct litex_mmc_host *host) { + return send_cmd(host, MMC_STOP_TRANSMISSION, 0, + SDCARD_CTRL_RESPONSE_SHORT, + SDCARD_CTRL_DATA_TRANSFER_NONE); +} + +// CMD55 +static inline int send_app_cmd(struct litex_mmc_host *host) { + return send_cmd(host, MMC_APP_CMD, host->rca << 16, + SDCARD_CTRL_RESPONSE_SHORT, + SDCARD_CTRL_DATA_TRANSFER_NONE); +} + +// ACMD6 +static inline int send_app_set_bus_width_cmd( + struct litex_mmc_host *host, u32 width) { + return send_cmd(host, SD_APP_SET_BUS_WIDTH, width, + SDCARD_CTRL_RESPONSE_SHORT, + SDCARD_CTRL_DATA_TRANSFER_NONE); +} + +static int litex_set_bus_width(struct litex_mmc_host *host) { + bool app_cmd_sent = host->app_cmd; /* was preceding command app_cmd? */ + int status; + + /* ensure 'app_cmd' precedes 'app_set_bus_width_cmd' */ + if (!app_cmd_sent) + send_app_cmd(host); + + /* litesdcard only supports 4-bit bus width */ + status = send_app_set_bus_width_cmd(host, MMC_BUS_WIDTH_4); + + /* re-send 'app_cmd' if necessary */ + if (app_cmd_sent) + send_app_cmd(host); + + return status; +} + +static int litex_get_cd(struct mmc_host *mmc) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + int gpio_cd = mmc_gpio_get_cd(mmc); + int ret; + + if (!mmc_card_is_removable(mmc)) + return 1; + + if (gpio_cd >= 0) + /* GPIO based card-detect explicitly specified in DTS */ + ret = !!gpio_cd; + else + /* use gateware card-detect bit by default */ + ret = !litex_read8(host->sdphy + + LITEX_MMC_SDPHY_CARDDETECT_OFF); + + /* ensure bus width will be set (again) upon card (re)insertion */ + if (ret == 0) + host->is_bus_width_set = false; + + return ret; +} + +/* + * Send request to a card. Command, data transfer, things like this. + * Call mmc_request_done() when finished. + */ +static void litex_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + struct platform_device *pdev = to_platform_device(mmc->parent); + struct device *dev = &pdev->dev; + struct mmc_data *data = mrq->data; + struct mmc_command *cmd = mrq->cmd; + unsigned int retries = cmd->retries; + int status; + + u32 response_len = SDCARD_CTRL_RESPONSE_NONE; + u32 transfer = SDCARD_CTRL_DATA_TRANSFER_NONE; + + if (cmd->flags & MMC_RSP_136) { + response_len = SDCARD_CTRL_RESPONSE_LONG; + } else if (cmd->flags & MMC_RSP_PRESENT) { + response_len = SDCARD_CTRL_RESPONSE_SHORT; + } + + if (data) { + /* LiteSDCard only supports 4-bit bus width; therefore, we MUST + * inject a SET_BUS_WIDTH (acmd6) before the very first data + * transfer, earlier than when the mmc subsystem would normally + * get around to it! + */ + if (!host->is_bus_width_set) { + ulong n = jiffies + 2 * HZ; // 500ms timeout + while (litex_set_bus_width(host) != SD_OK) { + if (time_after(jiffies, n)) { + dev_warn(dev, "Can't set bus width!\n"); + cmd->error = -ETIMEDOUT; + mmc_request_done(mmc, mrq); + return; + } + } + host->is_bus_width_set = true; + } + + if (mrq->data->flags & MMC_DATA_READ) { + litex_write8(host->sdreader + + LITEX_MMC_SDBLK2MEM_ENA_OFF, 0); + litex_write64(host->sdreader + + LITEX_MMC_SDBLK2MEM_BASE_OFF, + host->dma_handle); + litex_write32(host->sdreader + + LITEX_MMC_SDBLK2MEM_LEN_OFF, + data->blksz * data->blocks); + litex_write8(host->sdreader + + LITEX_MMC_SDBLK2MEM_ENA_OFF, 1); + + transfer = SDCARD_CTRL_DATA_TRANSFER_READ; + + } else if (mrq->data->flags & MMC_DATA_WRITE) { + int write_length = min(data->blksz * data->blocks, + (u32)host->buffer_size); + + sg_copy_to_buffer(data->sg, data->sg_len, + host->buffer, write_length); + + litex_write8(host->sdwriter + + LITEX_MMC_SDMEM2BLK_ENA_OFF, 0); + litex_write64(host->sdwriter + + LITEX_MMC_SDMEM2BLK_BASE_OFF, + host->dma_handle); + litex_write32(host->sdwriter + + LITEX_MMC_SDMEM2BLK_LEN_OFF, + write_length); + litex_write8(host->sdwriter + + LITEX_MMC_SDMEM2BLK_ENA_OFF, 1); + + transfer = SDCARD_CTRL_DATA_TRANSFER_WRITE; + } else { + dev_warn(dev, "Data present w/o read or write flag.\n"); + // Intentionally continue: set cmd status, mark req done + } + + litex_write16(host->sdcore + LITEX_MMC_SDCORE_BLKLEN_OFF, + data->blksz); + litex_write32(host->sdcore + LITEX_MMC_SDCORE_BLKCNT_OFF, + data->blocks); + } + + do { + status = send_cmd(host, cmd->opcode, cmd->arg, + response_len, transfer); + } while (status != SD_OK && retries-- > 0); + + /* Each multi-block data transfer MUST be followed by a cmd12 + * (MMC_STOP_TRANSMISSION). + * FIXME: figure out why we need to do this here explicitly, and + * whether there's a way (e.g., capability flag, possibly set via + * some DT property) to get the driver to do this automatically! + */ + if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK || + cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) + send_stop_tx_cmd(host); + + switch (status) { + case SD_OK: + cmd->error = 0; + break; + case SD_WRITEERROR: + cmd->error = -EIO; + break; + case SD_TIMEOUT: + cmd->error = -ETIMEDOUT; + break; + case SD_CRCERROR: + cmd->error = -EILSEQ; + break; + default: + cmd->error = -EINVAL; + break; + } + + // It looks strange I know, but it's as it should be + if (response_len == SDCARD_CTRL_RESPONSE_SHORT) { + cmd->resp[0] = host->resp[3]; + cmd->resp[1] = host->resp[2] & 0xFF; + } else if (response_len == SDCARD_CTRL_RESPONSE_LONG) { + cmd->resp[0] = host->resp[0]; + cmd->resp[1] = host->resp[1]; + cmd->resp[2] = host->resp[2]; + cmd->resp[3] = host->resp[3]; + } + + if (status == SD_OK && transfer != SDCARD_CTRL_DATA_TRANSFER_NONE) { + data->bytes_xfered = min(data->blksz * data->blocks, + mmc->max_req_size); + if (transfer == SDCARD_CTRL_DATA_TRANSFER_READ) { + sg_copy_from_buffer(data->sg, sg_nents(data->sg), + host->buffer, data->bytes_xfered); + } + } + + mmc_request_done(mmc, mrq); +} + +static void litex_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + + /* updated ios->bus_width -- do nothing; + * This happens right after the mmc core subsystem has sent its + * own acmd6 to notify the card of the bus-width change, and it's + * effectively a no-op given that we already forced bus-width to 4 + * by snooping on the command flow, and inserting an acmd6 before + * the first data xfer comand! + */ + + if (ios->clock != host->clock) { + sdclk_set_clk(host, ios->clock); + host->clock = ios->clock; + } +} + +static const struct mmc_host_ops litex_mmc_ops = { + .get_cd = litex_get_cd, + .request = litex_request, + .set_ios = litex_set_ios, +}; + +#define MAP_RESOURCE(res_name, idx) \ +{ \ + res = platform_get_resource(pdev, IORESOURCE_MEM, idx); \ + host->res_name = devm_ioremap_resource(&pdev->dev, res); \ + if (IS_ERR(host->res_name)) { \ + ret = PTR_ERR(host->res_name); \ + pr_err("MAP_RESOURCE %d failed\n", idx); \ + goto err_exit; \ + } \ +} + +static int litex_mmc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct litex_mmc_host *host; + struct device_node *node, *cpu; + struct mmc_host *mmc; + int ret; + + node = pdev->dev.of_node; + if (!node) + return -ENODEV; + + host = devm_kzalloc(&pdev->dev, sizeof(struct litex_mmc_host), + GFP_KERNEL); + if (!host) + return -ENOMEM; + + mmc = mmc_alloc_host(sizeof(struct litex_mmc_host), &pdev->dev); + /* NOTE: mmc_alloc_host() defaults to max_[req,seg]_size=PAGE_SIZE, + * max_blk_size=512, and sets max_blk_count accordingly (to 8); If, + * for some reason, we want to modify max_blk_count, we must also + * re-calculate max_[req,seg]_size=max_blk_size*max_blk_count! + */ + if (!mmc) + return -ENOMEM; + + /* force single-block transfers only */ + mmc->max_blk_count = 1; + mmc->max_seg_size = mmc->max_req_size = + mmc->max_blk_size * mmc->max_blk_count; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->dev = pdev; + // Initial state + host->clock = 0; + + cpu = of_find_node_by_name(NULL, "cpu"); + ret = of_property_read_u32(cpu, "clock-frequency", &host->freq); + of_node_put(cpu); + if (ret) { + pr_err("Couldn't find \"clock-frequency\" property in DT\n"); + goto err_exit; + } + + /* LiteSDCard only supports 4-bit bus width; therefore, we MUST inject + * a SET_BUS_WIDTH (acmd6) before the very first data transfer, earlier + * than when the mmc subsystem would normally get around to it! + */ + host->is_bus_width_set = false; + host->app_cmd = false; + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))){ + pr_err("Unable to set DMA driver failed\n"); + ret = -EINVAL; + goto err_exit; + } + + host->buffer_size = mmc->max_req_size * 2; + host->buffer = dma_alloc_coherent(&pdev->dev, host->buffer_size, + &host->dma_handle, GFP_DMA); + if (host->buffer == NULL) { + pr_err("could not allocate dma buffer\n"); + ret = -ENOMEM; + goto err_exit; + } + + MAP_RESOURCE(sdphy, 0); + MAP_RESOURCE(sdcore, 1); + MAP_RESOURCE(sdreader, 2); + MAP_RESOURCE(sdwriter, 3); + + ret = mmc_of_parse(mmc); + if (ret) { + pr_err("couldn't parse DT node\n"); + goto err_exit; + } + + /* add set-by-default capabilities */ + mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_DRIVER_TYPE_D; + /* FIXME: set "broken-cd" in dt, or somehow handle through irq? */ + mmc->caps |= MMC_CAP_NEEDS_POLL; + /* default to "disable-wp", "full-pwr-cycle", "no-sdio" */ + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT | + MMC_CAP2_FULL_PWR_CYCLE | + MMC_CAP2_NO_SDIO; + + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->ops = &litex_mmc_ops; + + mmc->f_min = 125 * 1e5; // sys_clk/256 is minimal frequency mmcm can produce, set minimal to 12.5Mhz on lower frequencies, sdcard sometimes do not initialize properly + mmc->f_max = 50 * 1e6; // 50Mhz is max frequency sd card can support + + platform_set_drvdata(pdev, host); + + ret = mmc_add_host(mmc); + if (ret < 0) { + pr_err("mmc_add_host() failed\n"); + goto err_exit; + } + + /* ensure DMA bus masters are disabled */ + litex_write8(host->sdreader + LITEX_MMC_SDBLK2MEM_ENA_OFF, 0); + litex_write8(host->sdwriter + LITEX_MMC_SDMEM2BLK_ENA_OFF, 0); + + return 0; + +err_exit: + kfree(host->buffer); + mmc_free_host(mmc); + return ret; +} + +static int litex_mmc_remove(struct platform_device *pdev) +{ + struct litex_mmc_host *host = dev_get_drvdata(&pdev->dev); + + mmc_remove_host(host->mmc); + mmc_free_host(host->mmc); + + return 0; +} + +static const struct of_device_id litex_match[] = { + { .compatible = "litex,mmc" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, litex_match); + +static struct platform_driver litex_mmc_driver = { + .driver = { + .name = "litex-mmc", + .of_match_table = of_match_ptr(litex_match), + }, + .probe = litex_mmc_probe, + .remove = litex_mmc_remove, +}; + +module_platform_driver(litex_mmc_driver); + +MODULE_DESCRIPTION("LiteX SDCard driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/mmc/host/litex_mmc.h b/drivers/mmc/host/litex_mmc.h new file mode 100644 index 00000000000000..dd7807529ee71d --- /dev/null +++ b/drivers/mmc/host/litex_mmc.h @@ -0,0 +1,104 @@ +#ifndef _LITEX_MMC_H_ +#define _LITEX_MMC_H_ + +#include + +/* register sizes (in bytes) */ +#define LITEX_MMC_SDPHY_CARDDETECT_SIZE 1 +#define LITEX_MMC_SDPHY_CLOCKERDIV_SIZE 2 +#define LITEX_MMC_SDPHY_INITIALIZE_SIZE 1 +#define LITEX_MMC_SDPHY_WRITESTATUS_SIZE 1 + +#define LITEX_MMC_SDCORE_CMDARG_SIZE 4 +#define LITEX_MMC_SDCORE_CMDCMD_SIZE 4 +#define LITEX_MMC_SDCORE_CMDSND_SIZE 1 +#define LITEX_MMC_SDCORE_CMDRSP_SIZE 16 +#define LITEX_MMC_SDCORE_CMDEVT_SIZE 1 +#define LITEX_MMC_SDCORE_DATAEVT_SIZE 1 +#define LITEX_MMC_SDCORE_BLKLEN_SIZE 2 +#define LITEX_MMC_SDCORE_BLKCNT_SIZE 4 + +/* sdreader */ +#define LITEX_MMC_SDBLK2MEM_BASE_SIZE 8 +#define LITEX_MMC_SDBLK2MEM_LEN_SIZE 4 +#define LITEX_MMC_SDBLK2MEM_ENA_SIZE 1 +#define LITEX_MMC_SDBLK2MEM_DONE_SIZE 1 +#define LITEX_MMC_SDBLK2MEM_LOOP_SIZE 1 + +/* sdwriter */ +#define LITEX_MMC_SDMEM2BLK_BASE_SIZE 8 +#define LITEX_MMC_SDMEM2BLK_LEN_SIZE 4 +#define LITEX_MMC_SDMEM2BLK_ENA_SIZE 1 +#define LITEX_MMC_SDMEM2BLK_DONE_SIZE 1 +#define LITEX_MMC_SDMEM2BLK_LOOP_SIZE 1 +#define LITEX_MMC_SDMEM2BLK_OFF_SIZE 4 + +/* register offsets (w.r.t. region base addr.) */ +#define LITEX_MMC_SDPHY_CARDDETECT_OFF 0x00 +#define LITEX_MMC_SDPHY_CLOCKERDIV_OFF \ + _next_reg_off(LITEX_MMC_SDPHY_CARDDETECT_OFF, \ + LITEX_MMC_SDPHY_CARDDETECT_SIZE) +#define LITEX_MMC_SDPHY_INITIALIZE_OFF \ + _next_reg_off(LITEX_MMC_SDPHY_CLOCKERDIV_OFF, \ + LITEX_MMC_SDPHY_CLOCKERDIV_SIZE) +#define LITEX_MMC_SDPHY_WRITESTATUS_OFF \ + _next_reg_off(LITEX_MMC_SDPHY_INITIALIZE_OFF, \ + LITEX_MMC_SDPHY_INITIALIZE_SIZE) + +#define LITEX_MMC_SDCORE_CMDARG_OFF 0x00 +#define LITEX_MMC_SDCORE_CMDCMD_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDARG_OFF, \ + LITEX_MMC_SDCORE_CMDARG_SIZE) +#define LITEX_MMC_SDCORE_CMDSND_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDCMD_OFF, \ + LITEX_MMC_SDCORE_CMDCMD_SIZE) +#define LITEX_MMC_SDCORE_CMDRSP_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDSND_OFF, \ + LITEX_MMC_SDCORE_CMDSND_SIZE) +#define LITEX_MMC_SDCORE_CMDEVT_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDRSP_OFF, \ + LITEX_MMC_SDCORE_CMDRSP_SIZE) +#define LITEX_MMC_SDCORE_DATAEVT_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_CMDEVT_OFF, \ + LITEX_MMC_SDCORE_CMDEVT_SIZE) +#define LITEX_MMC_SDCORE_BLKLEN_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_DATAEVT_OFF, \ + LITEX_MMC_SDCORE_DATAEVT_SIZE) +#define LITEX_MMC_SDCORE_BLKCNT_OFF \ + _next_reg_off(LITEX_MMC_SDCORE_BLKLEN_OFF, \ + LITEX_MMC_SDCORE_BLKLEN_SIZE) + +/* sdreader */ +#define LITEX_MMC_SDBLK2MEM_BASE_OFF 0x00 +#define LITEX_MMC_SDBLK2MEM_LEN_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_BASE_OFF, \ + LITEX_MMC_SDBLK2MEM_BASE_SIZE) +#define LITEX_MMC_SDBLK2MEM_ENA_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_LEN_OFF, \ + LITEX_MMC_SDBLK2MEM_LEN_SIZE) +#define LITEX_MMC_SDBLK2MEM_DONE_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_ENA_OFF, \ + LITEX_MMC_SDBLK2MEM_ENA_SIZE) +#define LITEX_MMC_SDBLK2MEM_LOOP_OFF \ + _next_reg_off(LITEX_MMC_SDBLK2MEM_DONE_OFF, \ + LITEX_MMC_SDBLK2MEM_DONE_SIZE) + +/* sdwriter */ +#define LITEX_MMC_SDMEM2BLK_BASE_OFF 0x00 +#define LITEX_MMC_SDMEM2BLK_LEN_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_BASE_OFF, \ + LITEX_MMC_SDMEM2BLK_BASE_SIZE) +#define LITEX_MMC_SDMEM2BLK_ENA_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_LEN_OFF, \ + LITEX_MMC_SDMEM2BLK_LEN_SIZE) +#define LITEX_MMC_SDMEM2BLK_DONE_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_ENA_OFF, \ + LITEX_MMC_SDMEM2BLK_ENA_SIZE) +#define LITEX_MMC_SDMEM2BLK_LOOP_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_DONE_OFF, \ + LITEX_MMC_SDMEM2BLK_DONE_SIZE) +#define LITEX_MMC_SDMEM2BLK_OFF_OFF \ + _next_reg_off(LITEX_MMC_SDMEM2BLK_LOOP_OFF, \ + LITEX_MMC_SDMEM2BLK_LOOP_SIZE) + +#endif /* _LITEX_MMC_H_ */ diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 24cd25de2b8b71..95530ad0de2b73 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -70,4 +70,12 @@ endchoice source "drivers/mtd/spi-nor/controllers/Kconfig" +config SPI_FLASH_LITEX + tristate "LiteX SPI Flash support" + depends on OF + depends on HAS_IOMEM + depends on LITEX_SOC_CONTROLLER + help + Generic SPI Flash driver for LiteX + endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index 653923896205c2..12f680e6fae625 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -20,3 +20,5 @@ spi-nor-objs += xmc.o obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_MTD_SPI_NOR) += controllers/ + +obj-$(CONFIG_SPI_FLASH_LITEX) += litex-spiflash.o diff --git a/drivers/mtd/spi-nor/litex-spiflash.c b/drivers/mtd/spi-nor/litex-spiflash.c new file mode 100644 index 00000000000000..82744962d9381c --- /dev/null +++ b/drivers/mtd/spi-nor/litex-spiflash.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPIFLASH_BITBANG_OFFSET 0x0 +#define SPIFLASH_BITBANG_SIZE 0x1 +#define SPIFLASH_MISO_OFFSET 0x4 +#define SPIFLASH_MISO_SIZE 0x1 +#define SPIFLASH_BITBANG_EN_OFFSET 0x8 +#define SPIFLASH_BITBANG_EN_SIZE 0x1 + +#define SPIFLASH_ENABLE 0x01 +#define SPIFLASH_DISABLE 0x00 +#define CLK_ENABLE 0x02 +#define CS_ENABLE 0x04 +#define MISO_MODE 0x08 + +#define WRITE_ENABLE 0x06 +#define READ_STATUS_REGISTER 0x05 +#define WORK_IN_PROGRESS 0x01 +#define READ_FLAG_STATUS_REGISTER 0x70 +#define PROGRAM_ERR 0x10 +#define ERASE_BUSY 0x80 +#define ERASE_ERR 0x20 + +#define TIMEOUT_ERASE_MS 3000 +#define TIMEOUT_MS 50 +#define BIT_SHIFT 7 +#define ADDRESS_SIZE 3 +#define DUMMY_CYCLES 8 + +struct spi { + struct spi_nor nor; + struct device *dev; + void __iomem *base; + struct clk *clk; +}; + +static void cs(struct spi_nor *nor, u8 new_val) +{ + struct spi *spi = nor->priv; + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); + u8 set_val = new_val == CS_ENABLE ? + curr_val | new_val : curr_val & new_val; + + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); +} + +static void clk(struct spi_nor *nor, u8 new_val) +{ + struct spi *spi = nor->priv; + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); + u8 set_val = new_val == CLK_ENABLE ? + curr_val | new_val : curr_val & new_val; + + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); +} + +static u8 miso_read(struct spi_nor *nor) +{ + struct spi *spi = nor->priv; + + return litex_read8(spi->base + SPIFLASH_MISO_OFFSET) & 0x1; +} + +static void mosi_set(bool mosi, struct spi_nor *nor) +{ + struct spi *spi = nor->priv; + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); + u8 set_val = mosi ? curr_val | (0x01) : curr_val & (~0x01); + + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); +} + +static void enable(struct spi_nor *nor, u8 new_val) +{ + struct spi *spi = nor->priv; + u8 curr_val = litex_read8(spi->base + SPIFLASH_BITBANG_OFFSET); + u8 set_val = new_val == MISO_MODE ? + curr_val | new_val : curr_val & new_val; + + litex_write8(spi->base + SPIFLASH_BITBANG_OFFSET, set_val); +} + +static void initial_config(struct spi_nor *nor) +{ + struct spi *spi = nor->priv; + + clk(nor, ~CLK_ENABLE); + cs(nor, CS_ENABLE); + enable(nor, ~MISO_MODE); + litex_write8(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_ENABLE); +} + +static void dummy_cycles(struct spi_nor *nor, int n_cycles) +{ + int i; + + for (i = 0; i < n_cycles; i++) { + clk(nor, ~CLK_ENABLE); + clk(nor, CLK_ENABLE); + } +} + +static void spi_bitbang_send(struct spi_nor *nor, const u8 command) +{ + int i; + u8 c = command; + + for (i = BIT_SHIFT; i >= 0; i--) { + // Sending on MSB order + mosi_set(c & 0x80, nor); + c <<= 1; + clk(nor, ~CLK_ENABLE); + clk(nor, CLK_ENABLE); + } +} + +static u8 spi_bitbang_read(struct spi_nor *nor) +{ + int i; + u8 byte = 0x00; + + for (i = BIT_SHIFT; i >= 0; i--) { + clk(nor, ~CLK_ENABLE); + clk(nor, CLK_ENABLE); + byte |= (miso_read(nor) << i); + } + return byte; +} + +static void write_command(struct spi_nor *nor, const u8 command) +{ + enable(nor, ~MISO_MODE); + dummy_cycles(nor, DUMMY_CYCLES); + cs(nor, ~CS_ENABLE); + spi_bitbang_send(nor, command); +} + +static void write_address(struct spi_nor *nor, const u32 addr32) +{ + int i; + u8 *addr8 = (u8 *) &addr32; + + for (i = (ADDRESS_SIZE - 1); i >= 0; i--) + spi_bitbang_send(nor, *(addr8 + i)); +} + +static void write_data(struct spi_nor *nor, const u8 *data, int len) +{ + int i; + + enable(nor, ~MISO_MODE); + for (i = 0; i < len; i++) + spi_bitbang_send(nor, data[i]); +} + +static void read_data(struct spi_nor *nor, u8 *buffer, int len) +{ + int i; + + enable(nor, MISO_MODE); + for (i = 0; i < len; i++) + buffer[i] = spi_bitbang_read(nor); +} + +static int spi_flash_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, + size_t len) +{ + write_command(nor, opcode); + read_data(nor, buf, len); + cs(nor, CS_ENABLE); + return 0; +} + +static u8 read_status(struct spi_nor *nor, const u8 status_command) +{ + u8 status; + + spi_flash_nor_read_reg(nor, status_command, &status, 1); + return status; +} + +static int busy(struct spi_nor *nor, int time, u8 reg, u8 flag) +{ + unsigned long end = jiffies + msecs_to_jiffies(time); + /* Wait until device is ready */ + while (read_status(nor, reg) & flag) { + if (time_after(jiffies, end)) + return -ETIMEDOUT; + }; + + return 0; +} + +static int spi_flash_nor_erase(struct spi_nor *nor, loff_t offs) +{ + write_command(nor, WRITE_ENABLE); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + write_command(nor, nor->erase_opcode); + write_address(nor, offs); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_ERASE_MS, READ_FLAG_STATUS_REGISTER, ERASE_BUSY) != 0) + return -ETIMEDOUT; + + if(busy(nor, TIMEOUT_ERASE_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + return (read_status(nor, READ_FLAG_STATUS_REGISTER) & ERASE_ERR); +} + +static ssize_t spi_flash_nor_read(struct spi_nor *nor, loff_t from, + size_t length, u8 *buffer) +{ + write_command(nor, nor->read_opcode); + write_address(nor, from); + read_data(nor, buffer, length); + cs(nor, CS_ENABLE); + + return length; +} + +static ssize_t spi_flash_nor_write(struct spi_nor *nor, loff_t to, size_t len, + const u8 *buf) +{ + /* Write WRITE ENABLE command*/ + write_command(nor, WRITE_ENABLE); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + write_command(nor, nor->program_opcode); + write_address(nor, to); + write_data(nor, buf, len); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + if (read_status(nor, READ_FLAG_STATUS_REGISTER)&PROGRAM_ERR) + return -EINVAL; + + return len; +} + +static ssize_t spi_flash_nor_write_reg(struct spi_nor *nor, u8 opcode, + const u8 *buf, size_t len) +{ + /* Write WRITE ENABLE command*/ + write_command(nor, WRITE_ENABLE); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + write_command(nor, opcode); + write_data(nor, buf, len); + cs(nor, CS_ENABLE); + + if(busy(nor, TIMEOUT_MS, READ_STATUS_REGISTER, WORK_IN_PROGRESS) != 0) + return -ETIMEDOUT; + + return 0; +} + +static const struct spi_nor_controller_ops litex_spi_controller_ops = { + .read = spi_flash_nor_read, + .write = spi_flash_nor_write, + .read_reg = spi_flash_nor_read_reg, + .write_reg = spi_flash_nor_write_reg, + .erase = spi_flash_nor_erase, +}; + +static int litex_spi_flash_probe(struct platform_device *pdev) +{ + struct device_node *node; + struct resource *res; + int ret; + struct spi *spi; + struct spi_nor *nor; + + const struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_PP, + }; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + spi = devm_kzalloc(&pdev->dev, sizeof(*spi), GFP_KERNEL); + if (!spi) + return -ENOMEM; + platform_set_drvdata(pdev, spi); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spi->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spi->base)) + return PTR_ERR(spi->base); + + spi->dev = &pdev->dev; + + /* Gets attached flash */ + node = of_get_next_available_child(pdev->dev.of_node, NULL); + if (!node) { + dev_err(&pdev->dev, "no SPI flash device to configure\n"); + ret = -ENODEV; + } + nor = &spi->nor; + nor->dev = spi->dev; + nor->priv = spi; + spi_nor_set_flash_node(nor, node); + /* Sets initial configuration of registers */ + initial_config(nor); + /* Fill the hooks to spi nor */ + nor->controller_ops = &litex_spi_controller_ops; + nor->mtd.name = "spi"; + + ret = spi_nor_scan(nor, NULL, &hwcaps); + if (ret) { + dev_err(&pdev->dev, "SPI_NOR_SCAN FAILED\n"); + return ret; + } + + ret = mtd_device_register(&nor->mtd, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Fail to register device\n"); + return ret; + } + + return 0; +} + +static int litex_spi_flash_remove(struct platform_device *pdev) +{ + struct spi *spi = platform_get_drvdata(pdev); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spi->base = devm_ioremap_resource(&pdev->dev, res); + litex_write8(spi->base + SPIFLASH_BITBANG_EN_OFFSET, SPIFLASH_DISABLE); + mtd_device_unregister(&spi->nor.mtd); + + return 0; +} + +static const struct of_device_id litex_of_match[] = { + { .compatible = "litex,spiflash" }, + {} +}; + +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_spi_flash_driver = { + .probe = litex_spi_flash_probe, + .remove = litex_spi_flash_remove, + .driver = { + .name = "litex-spiflash", + .of_match_table = of_match_ptr(litex_of_match) + }, +}; +module_platform_driver(litex_spi_flash_driver); + +MODULE_DESCRIPTION("LiteX SPI Flash driver"); +MODULE_AUTHOR("Antmicro "); diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index ad04660b97b804..301b431f74bf51 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -115,6 +115,7 @@ config LANTIQ_XRX200 Support for the PMAC of the Gigabit switch (GSWIP) inside the Lantiq / Intel VRX200 VDSL SoC +source "drivers/net/ethernet/litex/Kconfig" source "drivers/net/ethernet/marvell/Kconfig" source "drivers/net/ethernet/mediatek/Kconfig" source "drivers/net/ethernet/mellanox/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 1e7dc8a7762dcd..07416689972006 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_JME) += jme.o obj-$(CONFIG_KORINA) += korina.o obj-$(CONFIG_LANTIQ_ETOP) += lantiq_etop.o obj-$(CONFIG_LANTIQ_XRX200) += lantiq_xrx200.o +obj-$(CONFIG_NET_VENDOR_LITEX) += litex/ obj-$(CONFIG_NET_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_NET_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ diff --git a/drivers/net/ethernet/litex/Kconfig b/drivers/net/ethernet/litex/Kconfig new file mode 100644 index 00000000000000..a78b10c2574c2f --- /dev/null +++ b/drivers/net/ethernet/litex/Kconfig @@ -0,0 +1,28 @@ +# +# LiteX device configuration +# + +config NET_VENDOR_LITEX + bool "LiteX devices" + default y + help + If you have a network (Ethernet) card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about LiteX devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_LITEX + +config LITEX_LITEETH + tristate "LiteX Ethernet support" + depends on LITEX + select NET_CORE + select MII + select PHYLIB + help + If you wish to compile a kernel for hardware with a LiteX LiteEth + device then you should answer Y to this. + +endif # NET_VENDOR_LITEX diff --git a/drivers/net/ethernet/litex/Makefile b/drivers/net/ethernet/litex/Makefile new file mode 100644 index 00000000000000..9343b73b8e49da --- /dev/null +++ b/drivers/net/ethernet/litex/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the LiteX network device drivers. +# + +obj-$(CONFIG_LITEX_LITEETH) += litex_liteeth.o diff --git a/drivers/net/ethernet/litex/litex_liteeth.c b/drivers/net/ethernet/litex/litex_liteeth.c new file mode 100644 index 00000000000000..2df6b6369a065c --- /dev/null +++ b/drivers/net/ethernet/litex/litex_liteeth.c @@ -0,0 +1,369 @@ +/* + * LiteX Liteeth Ethernet + * + * Copyright 2017 Joel Stanley + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "litex_liteeth.h" + +#define DRV_NAME "liteeth" +#define DRV_VERSION "0.1" + +#define LITEETH_BUFFER_SIZE 0x800 +#define MAX_PKT_SIZE LITEETH_BUFFER_SIZE + +struct liteeth { + void __iomem *base; + void __iomem *mdio_base; + struct net_device *netdev; + int use_polling; + struct timer_list poll_timer; + struct device *dev; + struct mii_bus *mii_bus; + + /* Link management */ + int cur_duplex; + int cur_speed; + + /* Tx */ + int tx_slot; + int num_tx_slots; + void __iomem *tx_base; + + /* Rx */ + int rx_slot; + int num_rx_slots; + void __iomem *rx_base; +}; + + +static int liteeth_rx(struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + struct sk_buff *skb; + unsigned char *data; + u8 rx_slot; + int len; + + rx_slot = litex_read8(priv->base + LITEETH_WRITER_SLOT_OFF); + len = litex_read32(priv->base + LITEETH_WRITER_LENGTH_OFF); + + skb = netdev_alloc_skb(netdev, len + NET_IP_ALIGN); + if (!skb) { + netdev_err(netdev, "couldn't get memory"); + netdev->stats.rx_dropped++; + return NET_RX_DROP; + } + + /* Ensure alignemnt of the ip header within the skb */ + skb_reserve(skb, NET_IP_ALIGN); + if (len == 0 || len > 2048) + return NET_RX_DROP; + data = skb_put(skb, len); + memcpy_fromio(data, priv->rx_base + rx_slot * LITEETH_BUFFER_SIZE, len); + skb->protocol = eth_type_trans(skb, netdev); + + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += len; + + return netif_rx(skb); +} + +static irqreturn_t liteeth_interrupt(int irq, void *dev_id) +{ + struct net_device *netdev = dev_id; + struct liteeth *priv = netdev_priv(netdev); + u8 reg; + + reg = litex_read8(priv->base + LITEETH_READER_EV_PENDING_OFF); + if (reg) { + netdev->stats.tx_packets++; + litex_write8(priv->base + LITEETH_READER_EV_PENDING_OFF, reg); + } + + reg = litex_read8(priv->base + LITEETH_WRITER_EV_PENDING_OFF); + if (reg) { + liteeth_rx(netdev); + litex_write8(priv->base + LITEETH_WRITER_EV_PENDING_OFF, reg); + } + + return IRQ_HANDLED; +} + +static void liteeh_timeout(struct timer_list *t) +{ + struct liteeth *priv = from_timer(priv, t, poll_timer); + + liteeth_interrupt(0, priv->netdev); + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(10)); +} + +static int liteeth_open(struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + int err; + + /* TODO: Remove these once we have working mdio support */ + priv->cur_duplex = DUPLEX_FULL; + priv->cur_speed = SPEED_100; + netif_carrier_on(netdev); + + if (!priv->use_polling) { + err = request_irq(netdev->irq, liteeth_interrupt, 0, netdev->name, netdev); + if (err) { + netdev_err(netdev, "failed to request irq %d\n", netdev->irq); + goto err_irq; + } + } + + /* Clear pending events? */ + litex_write8(priv->base + LITEETH_WRITER_EV_PENDING_OFF, 1); + litex_write8(priv->base + LITEETH_READER_EV_PENDING_OFF, 1); + + if (!priv->use_polling) { + /* Enable IRQs? */ + litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE_OFF, 1); + litex_write8(priv->base + LITEETH_READER_EV_ENABLE_OFF, 1); + } + + netif_start_queue(netdev); + + if (priv->use_polling) { + timer_setup(&priv->poll_timer, liteeh_timeout, 0); + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(50)); + } + + return 0; + +err_irq: + netif_carrier_off(netdev); + return err; +} + +static int liteeth_stop(struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + + netif_stop_queue(netdev); + + del_timer_sync(&priv->poll_timer); + + litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE_OFF, 0); + litex_write8(priv->base + LITEETH_READER_EV_ENABLE_OFF, 0); + + if (!priv->use_polling) { + free_irq(netdev->irq, netdev); + } + + return 0; +} + +static int liteeth_start_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct liteeth *priv = netdev_priv(netdev); + void *txbuffer; + int ret; + u8 val; + + /* Reject oversize packets */ + if (unlikely(skb->len > MAX_PKT_SIZE)) { + if (net_ratelimit()) + netdev_dbg(netdev, "tx packet too big\n"); + goto drop; + } + + txbuffer = priv->tx_base + priv->tx_slot * LITEETH_BUFFER_SIZE; + memcpy_fromio(txbuffer, skb->data, skb->len); + litex_write8(priv->base + LITEETH_READER_SLOT_OFF, priv->tx_slot); + litex_write16(priv->base + LITEETH_READER_LENGTH_OFF, skb->len); + + ret = readx_poll_timeout_atomic(litex_read8, + priv->base + LITEETH_READER_READY_OFF, + val, val, 5, 1000); + if (ret == -ETIMEDOUT) { + netdev_err(netdev, "LITEETH_READER_READY timed out\n"); + goto drop; + } + + litex_write8(priv->base + LITEETH_READER_START_OFF, 1); + + priv->tx_slot = (priv->tx_slot + 1) % priv->num_tx_slots; + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; +drop: + /* Drop the packet */ + dev_kfree_skb_any(skb); + netdev->stats.tx_dropped++; + + return NETDEV_TX_OK; +} + +static void liteeth_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, dev_name(&netdev->dev), sizeof(info->bus_info)); +} + +static const struct net_device_ops liteeth_netdev_ops = { + .ndo_open = liteeth_open, + .ndo_stop = liteeth_stop, + .ndo_start_xmit = liteeth_start_xmit, +}; + +static const struct ethtool_ops liteeth_ethtool_ops = { + .get_drvinfo = liteeth_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .nway_reset = phy_ethtool_nway_reset, +}; + +static void liteeth_reset_hw(struct liteeth *priv) +{ + /* Reset, twice */ + litex_write8(priv->base + LITEETH_PHY_CRG_RESET_OFF, 0); + udelay(10); + litex_write8(priv->base + LITEETH_PHY_CRG_RESET_OFF, 1); + udelay(10); + litex_write8(priv->base + LITEETH_PHY_CRG_RESET_OFF, 0); + udelay(10); +} + +static int liteeth_probe(struct platform_device *pdev) +{ + struct net_device *netdev; + void __iomem *buf_base; + struct resource *res; + struct liteeth *priv; + const char *mac_addr; + int irq, err; + + netdev = alloc_etherdev(sizeof(*priv)); + if (!netdev) + return -ENOMEM; + + priv = netdev_priv(netdev); + priv->netdev = netdev; + priv->dev = &pdev->dev; + + priv->use_polling = 0; + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ, using polling\n"); + priv->use_polling = 1; + irq = 0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) { + err = PTR_ERR(priv->base); + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->mdio_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->mdio_base)) { + err = PTR_ERR(priv->mdio_base); + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + buf_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(buf_base)) { + err = PTR_ERR(buf_base); + goto err; + } + + err = of_property_read_u32(pdev->dev.of_node, "rx-fifo-depth", + &priv->num_rx_slots); + if (err) { + dev_err(&pdev->dev, "unable to get rx-fifo-depth\n"); + goto err; + } + + err = of_property_read_u32(pdev->dev.of_node, "tx-fifo-depth", + &priv->num_tx_slots); + if (err) { + dev_err(&pdev->dev, "unable to get tx-fifo-depth\n"); + goto err; + } + + /* Rx slots */ + priv->rx_base = buf_base; + priv->rx_slot = 0; + + /* Tx slots come after Rx slots */ + priv->tx_base = buf_base + priv->num_rx_slots * LITEETH_BUFFER_SIZE; + priv->tx_slot = 0; + + mac_addr = of_get_mac_address(pdev->dev.of_node); + if (mac_addr && !IS_ERR(mac_addr) && is_valid_ether_addr(mac_addr)) + memcpy(netdev->dev_addr, mac_addr, ETH_ALEN); + else + eth_hw_addr_random(netdev); + + SET_NETDEV_DEV(netdev, &pdev->dev); + platform_set_drvdata(pdev, netdev); + + netdev->netdev_ops = &liteeth_netdev_ops; + netdev->ethtool_ops = &liteeth_ethtool_ops; + netdev->irq = irq; + + liteeth_reset_hw(priv); + + err = register_netdev(netdev); + if (err) { + dev_err(&pdev->dev, "Failed to register netdev\n"); + goto err; + } + + netdev_info(netdev, "irq %d, mapped at %px\n", netdev->irq, priv->base); + + return 0; +err: + free_netdev(netdev); + return err; +} + +static int liteeth_remove(struct platform_device *pdev) +{ + struct net_device *netdev; + + netdev = platform_get_drvdata(pdev); + unregister_netdev(netdev); + free_netdev(netdev); + return 0; +} + +static const struct of_device_id liteeth_of_match[] = { + { .compatible = "litex,liteeth" }, + { } +}; +MODULE_DEVICE_TABLE(of, liteeth_of_match); + +static struct platform_driver liteeth_driver = { + .probe = liteeth_probe, + .remove = liteeth_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = liteeth_of_match, + }, +}; +module_platform_driver(liteeth_driver); + +MODULE_AUTHOR("Joel Stanley "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/litex/litex_liteeth.h b/drivers/net/ethernet/litex/litex_liteeth.h new file mode 100644 index 00000000000000..d176526492478b --- /dev/null +++ b/drivers/net/ethernet/litex/litex_liteeth.h @@ -0,0 +1,88 @@ +#ifndef _LITEX_LITEETH_H_ +#define _LITEX_LITEETH_H_ + +#include + +/* register sizes */ +#define LITEETH_WRITER_SLOT_SIZE 1 +#define LITEETH_WRITER_LENGTH_SIZE 4 +#define LITEETH_WRITER_ERRORS_SIZE 4 +#define LITEETH_WRITER_EV_STATUS_SIZE 1 +#define LITEETH_WRITER_EV_PENDING_SIZE 1 +#define LITEETH_WRITER_EV_ENABLE_SIZE 1 +#define LITEETH_READER_START_SIZE 1 +#define LITEETH_READER_READY_SIZE 1 +#define LITEETH_READER_LEVEL_SIZE 1 +#define LITEETH_READER_SLOT_SIZE 1 +#define LITEETH_READER_LENGTH_SIZE 2 +#define LITEETH_READER_EV_STATUS_SIZE 1 +#define LITEETH_READER_EV_PENDING_SIZE 1 +#define LITEETH_READER_EV_ENABLE_SIZE 1 +#define LITEETH_PREAMBLE_CRC_SIZE 1 +#define LITEETH_PREAMBLE_ERRORS_SIZE 4 +#define LITEETH_CRC_ERRORS_SIZE 4 + +#define LITEETH_PHY_CRG_RESET_SIZE 1 +#define LITEETH_MDIO_W_SIZE 1 +#define LITEETH_MDIO_R_SIZE 1 + +/* register offsets */ +#define LITEETH_WRITER_SLOT_OFF 0x00 +#define LITEETH_WRITER_LENGTH_OFF \ + _next_reg_off(LITEETH_WRITER_SLOT_OFF, \ + LITEETH_WRITER_SLOT_SIZE) +#define LITEETH_WRITER_ERRORS_OFF \ + _next_reg_off(LITEETH_WRITER_LENGTH_OFF, \ + LITEETH_WRITER_LENGTH_SIZE) +#define LITEETH_WRITER_EV_STATUS_OFF \ + _next_reg_off(LITEETH_WRITER_ERRORS_OFF, \ + LITEETH_WRITER_ERRORS_SIZE) +#define LITEETH_WRITER_EV_PENDING_OFF \ + _next_reg_off(LITEETH_WRITER_EV_STATUS_OFF, \ + LITEETH_WRITER_EV_STATUS_SIZE) +#define LITEETH_WRITER_EV_ENABLE_OFF \ + _next_reg_off(LITEETH_WRITER_EV_PENDING_OFF, \ + LITEETH_WRITER_EV_PENDING_SIZE) +#define LITEETH_READER_START_OFF \ + _next_reg_off(LITEETH_WRITER_EV_ENABLE_OFF, \ + LITEETH_WRITER_EV_ENABLE_SIZE) +#define LITEETH_READER_READY_OFF \ + _next_reg_off(LITEETH_READER_START_OFF, \ + LITEETH_READER_START_SIZE) +#define LITEETH_READER_LEVEL_OFF \ + _next_reg_off(LITEETH_READER_READY_OFF, \ + LITEETH_READER_READY_SIZE) +#define LITEETH_READER_SLOT_OFF \ + _next_reg_off(LITEETH_READER_LEVEL_OFF, \ + LITEETH_READER_LEVEL_SIZE) +#define LITEETH_READER_LENGTH_OFF \ + _next_reg_off(LITEETH_READER_SLOT_OFF, \ + LITEETH_READER_SLOT_SIZE) +#define LITEETH_READER_EV_STATUS_OFF \ + _next_reg_off(LITEETH_READER_LENGTH_OFF, \ + LITEETH_READER_LENGTH_SIZE) +#define LITEETH_READER_EV_PENDING_OFF \ + _next_reg_off(LITEETH_READER_EV_STATUS_OFF, \ + LITEETH_READER_EV_STATUS_SIZE) +#define LITEETH_READER_EV_ENABLE_OFF \ + _next_reg_off(LITEETH_READER_EV_PENDING_OFF, \ + LITEETH_READER_EV_PENDING_SIZE) +#define LITEETH_PREAMBLE_CRC_OFF \ + _next_reg_off(LITEETH_READER_EV_ENABLE_OFF, \ + LITEETH_READER_EV_ENABLE_SIZE) +#define LITEETH_PREAMBLE_ERRORS_OFF \ + _next_reg_off(LITEETH_PREAMBLE_CRC_OFF, \ + LITEETH_PREAMBLE_CRC_SIZE) +#define LITEETH_CRC_ERRORS_OFF \ + _next_reg_off(LITEETH_PREAMBLE_ERRORS_OFF, \ + LITEETH_PREAMBLE_ERRORS_SIZE) + +#define LITEETH_PHY_CRG_RESET_OFF 0x00 +#define LITEETH_MDIO_W_OFF \ + _next_reg_off(LITEETH_PHY_CRG_RESET_OFF, \ + LITEETH_PHY_CRG_RESET_SIZE) +#define LITEETH_MDIO_R_OFF \ + _next_reg_off(LITEETH_MDIO_W_OFF, \ + LITEETH_MDIO_W_SIZE) + +#endif /* _LITEX_LITEETH_H_ */ diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 9a4f66ae8070da..60280bfc160ded 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -291,6 +291,14 @@ config PWM_KEEMBAY To compile this driver as a module, choose M here: the module will be called pwm-keembay. +config PWM_LITEX + tristate "LiteX PWM support" + depends on OF + depends on HAS_IOMEM + depends on LITEX_SOC_CONTROLLER + help + Generic PWM framework driver for LiteX + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 6374d3b1d6f344..db3e4dab23f710 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o +obj-$(CONFIG_PWM_LITEX) += pwm-litex.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o diff --git a/drivers/pwm/pwm-litex.c b/drivers/pwm/pwm-litex.c new file mode 100644 index 00000000000000..f2644f22ca6720 --- /dev/null +++ b/drivers/pwm/pwm-litex.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Antmicro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_EN_ENABLE 0x1 +#define REG_EN_DISABLE 0x0 + +#define ENABLE_REG_OFFSET 0x0 +#define WIDTH_REG_OFFSET 0x4 +#define PERIOD_REG_OFFSET 0x14 + +struct litex_pwm_chip { + struct pwm_chip chip; + unsigned int clock; + void __iomem *base; + void __iomem *width; + void __iomem *period; + void __iomem *enable; +}; + +#define to_litex_pwm_chip(_chip) container_of(_chip, struct litex_pwm_chip, chip) + +static int litex_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); + unsigned long period_cycles, duty_cycles; + unsigned long long c; + + /* Calculate period cycles */ + c = (unsigned long long)litex->clock * (unsigned long long)period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = c; + + /* Calculate duty cycles */ + c *= duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* Apply values to registers */ + litex_write32(litex->width, duty_cycles); + litex_write32(litex->period, period_cycles); + + return 0; +} + +static int litex_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); + + litex_write8(litex->enable, REG_EN_ENABLE); + return 0; +} + +static void litex_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct litex_pwm_chip *litex = to_litex_pwm_chip(chip); + + litex_write8(litex->enable, REG_EN_DISABLE); +} + +static const struct pwm_ops litex_pwm_ops = { + .config = litex_pwm_config, + .enable = litex_pwm_enable, + .disable = litex_pwm_disable, + .owner = THIS_MODULE, +}; + +static int litex_pwm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litex_pwm_chip *litex; + struct resource *res; + int ret; + + if (!node) { + dev_err(&pdev->dev, "Fail on obtaining device node\n"); + return -ENOMEM; + } + + litex = devm_kzalloc(&pdev->dev, sizeof(*litex), GFP_KERNEL); + + if (!litex) { + dev_err(&pdev->dev, "Fail on memory allocation\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Device is busy\n"); + return -EBUSY; + } + + litex->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(litex->base)) { + dev_err(&pdev->dev, "Fail to get base address\n"); + return -EIO; + } + + ret = of_property_read_u32(node, "clock", &(litex->clock)); + if (ret < 0) { + dev_err(&pdev->dev, "No clock record in the dts file\n"); + return -ENODEV; + } + + litex->width = litex->base + WIDTH_REG_OFFSET; + litex->period = litex->base + PERIOD_REG_OFFSET; + litex->enable = litex->base + ENABLE_REG_OFFSET; + + litex->chip.dev = &pdev->dev; + litex->chip.ops = &litex_pwm_ops; + litex->chip.base = -1; + litex->chip.npwm = 1; + + ret = pwmchip_add(&litex->chip); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to add pwm chip %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, litex); + + return 0; +} + +static int litex_pwm_remove(struct platform_device *pdev) +{ + struct litex_pwm_chip *litex = platform_get_drvdata(pdev); + + return pwmchip_remove(&litex->chip); +} + +static const struct of_device_id litex_of_match[] = { + { .compatible = "litex,pwm" }, + {} +}; +MODULE_DEVICE_TABLE(of, litex_of_match); + +static struct platform_driver litex_pwm_driver = { + .driver = { + .name = "litex-pwm", + .of_match_table = of_match_ptr(litex_of_match) + }, + .probe = litex_pwm_probe, + .remove = litex_pwm_remove, +}; +module_platform_driver(litex_pwm_driver); + +MODULE_DESCRIPTION("LiteX PWM driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 09a263cf4ae2ed..3d0882e4eee365 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -387,6 +387,13 @@ config SPI_JCORE This enables support for the SPI master controller in the J-Core synthesizable, open source SoC. +config SPI_LITESPI + tristate "LiteSPI SPI master driver" + depends on OF && LITEX + help + This enables support for LiteSPI, the SPI master controller of + the LiteX FPGA SoC builder. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 0f06fc0813c616..a470ec5689c7db 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o obj-$(CONFIG_SPI_IMX) += spi-imx.o obj-$(CONFIG_SPI_LANTIQ_SSC) += spi-lantiq-ssc.o obj-$(CONFIG_SPI_JCORE) += spi-jcore.o +obj-$(CONFIG_SPI_LITESPI) += spi-litespi.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o obj-$(CONFIG_SPI_MESON_SPICC) += spi-meson-spicc.o diff --git a/drivers/spi/spi-litespi.c b/drivers/spi/spi-litespi.c new file mode 100644 index 00000000000000..f742860dec9be2 --- /dev/null +++ b/drivers/spi/spi-litespi.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LiteSPI controller (LiteX) Driver + * + * Copyright (C) 2019 Antmicro Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "litespi" + +/* register sizes */ +#define LITESPI_SZ_CTRL 2 +#define LITESPI_SZ_STAT 1 +#define LITESPI_SZ_MOSI LITEX_SUBREG_SIZE +#define LITESPI_SZ_MISO LITEX_SUBREG_SIZE +#define LITESPI_SZ_CS 1 + +/* register offsets */ +#define LITESPI_OFF_CTRL 0x00 +#define LITESPI_OFF_STAT \ + _next_reg_off(LITESPI_OFF_CTRL, LITESPI_SZ_CTRL) +#define LITESPI_OFF_MOSI \ + _next_reg_off(LITESPI_OFF_STAT, LITESPI_SZ_STAT) +#define LITESPI_OFF_MISO \ + _next_reg_off(LITESPI_OFF_MOSI, LITESPI_SZ_MOSI) +#define LITESPI_OFF_CS \ + _next_reg_off(LITESPI_OFF_MISO, LITESPI_SZ_MISO) + +#define LITESPI_CTRL_SHIFT_BPW 8 +#define LITESPI_CTRL_START_BIT 0 + +struct litespi_hw { + struct spi_master *master; + void __iomem *base; +}; + +static inline void litespi_wait_xfer_end(struct litespi_hw *hw) +{ + while (!litex_read8(hw->base + LITESPI_OFF_STAT)) + cpu_relax(); +} + +static void litespi_cs(struct spi_device *spi, bool enable) +{ + struct litespi_hw *hw = spi_master_get_devdata(spi->master); + u8 cs = litex_read8(hw->base + LITESPI_OFF_CS); + if (!enable) {// inversed logic + cs |= BIT(spi->chip_select); + } + else { + cs &= ~BIT(spi->chip_select); + } + + litex_write8(hw->base + LITESPI_OFF_CS, cs); +} + +static int litespi_rxtx(struct spi_master *master, struct spi_device *spi, + struct spi_transfer *t) +{ + struct litespi_hw *hw = spi_master_get_devdata(master); + u16 ctl_word = t->bits_per_word << LITESPI_CTRL_SHIFT_BPW; + int i; + + /* set word size */ + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + + /* add start bit to ctl_word */ + ctl_word |= BIT(LITESPI_CTRL_START_BIT); + + /* + * Validated SPI transfer length is multiple of SPI word size, which + * is itself a power-of-two multiple, and fits within LITEX_SUBREG_SIZE + */ + if (t->bits_per_word <= 8) { + const u8 *tx = t->tx_buf; + u8 *rx = t->rx_buf; + + /* word size is 1 byte */ + for (i = 0; i < t->len; i++) { + if (tx) + litex_write8(hw->base + + LITESPI_OFF_MOSI, *tx++); + + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + litespi_wait_xfer_end(hw); + + if (rx) + *rx++ = litex_read8(hw->base + + LITESPI_OFF_MISO); + } + } else if (t->bits_per_word <= 16) { + const u16 *tx = t->tx_buf; + u16 *rx = t->rx_buf; + + /* word size is 2 bytes */ + for (i = 0; i < t->len / 2; i++) { + if (tx) + litex_write16(hw->base + LITESPI_OFF_MOSI, + be16_to_cpu(*tx++)); + + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + litespi_wait_xfer_end(hw); + + if (rx) + *rx++ = cpu_to_be16(litex_read16( + hw->base + LITESPI_OFF_MISO)); + } + } else { + const u32 *tx = t->tx_buf; + u32 *rx = t->rx_buf; + + /* word size is 4 bytes */ + for (i = 0; i < t->len / 4; i++) { + if (tx) + litex_write32(hw->base + LITESPI_OFF_MOSI, + be32_to_cpu(*tx++)); + + litex_write16(hw->base + LITESPI_OFF_CTRL, ctl_word); + litespi_wait_xfer_end(hw); + + if (rx) + *rx++ = cpu_to_be32(litex_read32( + hw->base + LITESPI_OFF_MISO)); + } + } + + return 0; +} + +static int litespi_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct litespi_hw *hw; + struct spi_master *master; + struct resource *res; + int ret; + u32 val; + + master = spi_alloc_master(&pdev->dev, sizeof(*hw)); + if (!master) + return -ENOMEM; + + master->dev.of_node = pdev->dev.of_node; + master->bus_num = pdev->id; + master->set_cs = litespi_cs; + master->transfer_one = litespi_rxtx; + master->mode_bits = SPI_MODE_0 | SPI_CS_HIGH; + master->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX; + + /* get bits per word property */ + ret = of_property_read_u32(node, "litespi,max-bpw", &val); + if (ret) + goto err; + if (val > LITEX_SUBREG_SIZE * 8) { + ret = -EINVAL; + goto err; + } + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, val); + + /* get sck frequency */ + ret = of_property_read_u32(node, "litespi,sck-frequency", &val); + if (ret) + goto err; + master->max_speed_hz = val; + + /* get num cs */ + ret = of_property_read_u32(node, "litespi,num-cs", &val); + if (ret) + goto err; + master->num_chipselect = val; + + hw = spi_master_get_devdata(master); + hw->master = master; + + /* get base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hw->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hw->base)) { + ret = PTR_ERR(hw->base); + goto err; + } + + /* register controller */ + ret = devm_spi_register_master(&pdev->dev, master); + if (ret) + goto err; + + return 0; + +err: + spi_master_put(master); + return ret; +} + +static const struct of_device_id litespi_match[] = { + { .compatible = "litex,litespi" }, + {} +}; +MODULE_DEVICE_TABLE(of, litespi_match); + +static struct platform_driver litespi_driver = { + .probe = litespi_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(litespi_match) + } +}; +module_platform_driver(litespi_driver) + +MODULE_AUTHOR("Antmicro Ltd "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME);