diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000000..2ab7c27a4973e2 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ litex-rebase ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ litex-rebase ] + schedule: + - cron: '39 9 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 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.yaml b/Documentation/devicetree/bindings/gpio/litex,gpio.yaml new file mode 100644 index 00000000000000..99564b67cc4d47 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/litex,gpio.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/litex,gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX GPIO controller + +maintainers: + - Robert Winkler + +properties: + compatible: + const: litex,gpio + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + interrupt-controller: true + + '#interrupt-cells': + const: 2 + description: + GPIO number and flags (IRQ_TYPE_EDGE_{RISING,FALLING,BOTH}). + + litex,ngpio: + description: Number of gpio pins in port + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 32 + + litex,direction: + description: Direction of gpio port + $ref: /schemas/types.yaml#/definitions/string + enum: [in, out] + +patternProperties: + "^(hog-[0-9]+|.+-hog(-[0-9]+)?)$": + type: object + properties: + gpio-hog: true + gpios: true + output-high: true + output-low: true + line-name: true + + required: + - gpio-hog + - gpios + + additionalProperties: false + +required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + - litex,ngpio + - litex,direction + +additionalProperties: false + +examples: + - | + gpio@f0003000 { + compatible = "litex,gpio"; + reg = <0xf0003800 0x1>; + interrupts = <4>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + litex,ngpio = <4>; + litex,direction = "in"; + }; + + gpio@f0003800 { + compatible = "litex,gpio"; + reg = <0xf0003800 0x1>; + gpio-controller; + #gpio-cells = <2>; + 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/litex,i2c.yaml b/Documentation/devicetree/bindings/i2c/litex,i2c.yaml new file mode 100644 index 00000000000000..50780c121a3ab2 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/litex,i2c.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/litex,i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX I2C controller + +maintainers: + - Robert Winkler + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + const: litex,i2c + + reg: + maxItems: 1 + +required: + - compatible + - reg + - '#address-cells' + - '#size-cells' + +unevaluatedProperties: false + +examples: + - | + i2c@f0003000 { + compatible = "litex,i2c"; + reg = <0xf0003000 0x5>; + #address-cells = <1>; + #size-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/mmc/litex,mmc.yaml b/Documentation/devicetree/bindings/mmc/litex,mmc.yaml new file mode 100644 index 00000000000000..ef9e0da44bf8c7 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/litex,mmc.yaml @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/litex,mmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LiteX LiteSDCard device + +maintainers: + - Gabriel Somlo + +description: | + LiteSDCard is a small footprint, configurable SDCard core for FPGA based + system on chips. + + The hardware source is Open Source and can be found on at + https://github.com/enjoy-digital/litesdcard/. + +allOf: + - $ref: mmc-controller.yaml# + +properties: + compatible: + const: litex,mmc + + reg: + items: + - description: PHY registers + - description: CORE registers + - description: DMA Reader buffer + - description: DMA Writer buffer + - description: IRQ registers + minItems: 4 + + reg-names: + items: + - const: phy + - const: core + - const: reader + - const: writer + - const: irq + minItems: 4 + + clocks: + maxItems: 1 + description: + Handle to reference clock. + + vmmc-supply: + description: + Handle to fixed-voltage supply for the card power. + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + - clocks + - vmmc-supply + +additionalProperties: false + +examples: + - | + mmc: mmc@12005000 { + compatible = "litex,mmc"; + reg = <0x12005000 0x100>, + <0x12003800 0x100>, + <0x12003000 0x100>, + <0x12004800 0x100>, + <0x12004000 0x100>; + reg-names = "phy", "core", "reader", "writer", "irq"; + clocks = <&reference_clk>; + vmmc-supply = <&vreg_mmc>; + interrupts = <4>; + }; 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/MAINTAINERS b/MAINTAINERS index 05fd080b82f3a3..05c0c6b0bc9d7c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11130,12 +11130,17 @@ F: lib/list-test.c LITEX PLATFORM M: Karol Gugala M: Mateusz Holenko +M: Gabriel Somlo +M: Joel Stanley S: Maintained F: Documentation/devicetree/bindings/*/litex,*.yaml F: arch/openrisc/boot/dts/or1klitex.dts -F: drivers/soc/litex/litex_soc_ctrl.c -F: drivers/tty/serial/liteuart.c F: include/linux/litex.h +F: drivers/tty/serial/liteuart.c +F: drivers/soc/litex/* +F: drivers/net/ethernet/litex/* +F: drivers/mmc/host/litex_mmc.c +N: litex LIVE PATCHING M: Josh Poimboeuf diff --git a/arch/riscv/configs/litex_rocket_defconfig b/arch/riscv/configs/litex_rocket_defconfig new file mode 100644 index 00000000000000..9b8bcc1b94d19c --- /dev/null +++ b/arch/riscv/configs/litex_rocket_defconfig @@ -0,0 +1,142 @@ +CONFIG_DEFAULT_HOSTNAME="litex" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_BPF_SYSCALL=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_SGETMASK_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_SERIAL_LITEUART=y +CONFIG_SERIAL_LITEUART_CONSOLE=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 +# +CONFIG_RISCV_SBI_V01=y +# Netboot +#CONFIG_IP_PNP=y +#CONFIG_NFS_FS=y +#CONFIG_NFS_V4=y +#CONFIG_NFS_SWAP=y +#CONFIG_NFS_V4_1=y +#CONFIG_NFS_V4_2=y +#CONFIG_NFS_V4_1_MIGRATION=y +#CONFIG_ROOT_NFS=y +# RAMDISK +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_GZIP=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_RD_XZ=y +CONFIG_RD_LZO=y +CONFIG_RD_LZ4=y +CONFIG_INITRAMFS_ROOT_UID=0 +CONFIG_INITRAMFS_ROOT_GID=0 +CONFIG_INITRAMFS_COMPRESSION_NONE=y +CONFIG_INITRAMFS_SOURCE=n +# SMP +CONFIG_SMP=n +# EFI +CONFIG_EFI=n 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/arch/riscv/configs/litex_vexriscv_defconfig b/arch/riscv/configs/litex_vexriscv_defconfig new file mode 100644 index 00000000000000..bd8fd421ac9807 --- /dev/null +++ b/arch/riscv/configs/litex_vexriscv_defconfig @@ -0,0 +1,181 @@ +# Architecture +CONFIG_ARCH_DEFCONFIG="arch/riscv/configs/defconfig" +CONFIG_ARCH_RV32I=y +CONFIG_RISCV_ISA_M=y +CONFIG_RISCV_ISA_A=y +CONFIG_RISCV_ISA_C=n +CONFIG_SIFIVE_PLIC=y +CONFIG_FPU=y +CONFIG_SMP=y +CONFIG_STRICT_KERNEL_RWX=n +CONFIG_EFI=n +CONFIG_HVC_RISCV_SBI=y + +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_RD_XZ=y +CONFIG_RD_LZO=y +CONFIG_RD_LZ4=y + + +# FPGA / SoC +CONFIG_FPGA=y +CONFIG_FPGA_MGR_LITEX=y +CONFIG_LITEX_SOC_CONTROLLER=y +CONFIG_LITEX_SUBREG_SIZE=4 + +# Time +CONFIG_PRINTK_TIME=y + +# Clocking +CONFIG_COMMON_CLK=y +CONFIG_COMMON_CLK_LITEX=y + +# Interrupts +CONFIG_IRQCHIP=y +CONFIG_OF_IRQ=y +CONFIG_HANDLE_DOMAIN_IRQ=y +CONFIG_LITEX_VEXRISCV_INTC=y + + +# liteuart console +CONFIG_SERIAL_EARLYCON_RISCV_SBI=y +CONFIG_SERIAL_LITEUART=y +CONFIG_SERIAL_LITEUART_CONSOLE=y + +# liteeth network +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_INET=y +CONFIG_NETDEVICES=y +CONFIG_NET_VENDOR_LITEX=y +CONFIG_LITEX_LITEETH=y +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +# CONFIG_INPUT_MOUSE is not set + +# NFS boot support +# CONFIG_MISC_FILESYSTEMS is not set +CONFIG_IP_PNP=y +CONFIG_NFS_FS=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_NFS_V4_1_MIGRATION=y +CONFIG_ROOT_NFS=y +# CONFIG_NFS_DISABLE_UDP_SUPPORT is not set + +# GPIO +CONFIG_GPIO_SYSFS=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_LITEX=y + +# PWM +CONFIG_PWM=y +CONFIG_PWM_LITEX=y + +# SPI +CONFIG_SPI=y +CONFIG_SPI_LITESPI=y +CONFIG_SPI_SPIDEV=y + +# Hardware monitoring +CONFIG_HWMON=y +CONFIG_SENSORS_LITEX_HWMON=y + +# Framebuffer +CONFIG_FB=y +CONFIG_FB_SIMPLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +CONFIG_LOGO=y +CONFIG_DRM=y +CONFIG_DRM_LITEVIDEO=y + +# Flash +CONFIG_MTD=y +CONFIG_MTD_SPI_NOR=y +CONFIG_SPI_FLASH_LITEX=y + +# MMC +CONFIG_MMC=y +CONFIG_MMC_SPI=y +CONFIG_MMC_LITEX=y + +CONFIG_EXT2_FS=y +CONFIG_EXT3_FS=y +CONFIG_EXT4_FS=y +# Filesystem +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_MSDOS_PARTITION=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_NCPFS_SMALLDOS=y +CONFIG_NLS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_SECURITY=y +CONFIG_CRYPTO=y +CONFIG_CRYPTO_RSA=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRC16=y + + +# kernel features +CONFIG_SECTION_MISMATCH_WARN_ONLY=y + +# .config in kernel +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y + +CONFIG_TMPFS=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_UNIX=y + diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index ad4256d543613e..aa010652d659ac 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -395,6 +395,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 16e58863047255..427b19a900b126 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o obj-$(CONFIG_LMK04832) += clk-lmk04832.o obj-$(CONFIG_COMMON_CLK_LAN966X) += clk-lan966x.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 991b3f361ec933..b9a60db92b6d4a 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 0bff783d1b61a8..25ce72fc3f2910 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..b3918c916ad785 --- /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_register(&pdev->dev, + "LiteX ICAPBitstream FPGA Manager", + &litex_fpga_manager_ops, fpga_s); + if (IS_ERR(mgr)) + return PTR_ERR(mgr); + + platform_set_drvdata(pdev, mgr); + return 0; +} + +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 1c211b4c63beb3..6439f1e381227a 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -360,6 +360,14 @@ 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 + select GPIOLIB_IRQCHIP + select IRQ_DOMAIN_HIERARCHY + 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 edbaa3cb343c2c..0960a3ba086e9a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -73,6 +73,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..62d64925bd4562 --- /dev/null +++ b/drivers/gpio/gpio-litex.c @@ -0,0 +1,444 @@ +// 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 GPIO_PINS_MAX 32 +#define LITEX_GPIO_VALUE_OFFSET 0x0 +#define LITEX_GPIO_MODE_OFFSET 0x4 +#define LITEX_GPIO_EDGE_OFFSET 0x8 +#define LITEX_GPIO_PENDING_OFFSET 0x10 +#define LITEX_GPIO_ENABLE_OFFSET 0x14 + +struct litex_gpio { + void __iomem *membase; + int port_direction; + int reg_span; + struct gpio_chip chip; + struct irq_chip ichip; + spinlock_t gpio_lock; + unsigned int irq_number; +}; + +/* Helper functions */ + +static inline u32 litex_gpio_get_reg(struct litex_gpio *gpio_s, int reg_offset) +{ + return _litex_get_reg(gpio_s->membase + reg_offset, gpio_s->reg_span); +} + +static inline void litex_gpio_set_reg(struct litex_gpio *gpio_s, int reg_offset, + u32 value) +{ + _litex_set_reg(gpio_s->membase + reg_offset, gpio_s->reg_span, value); +} + +/* 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; +} + +static void litex_gpio_irq_unmask(struct irq_data *idata) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(idata); + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + int offset = irqd_to_hwirq(idata) % GPIO_PINS_MAX; + unsigned long flags; + u32 bit = BIT(offset); + u32 enable; + + spin_lock_irqsave(&gpio_s->gpio_lock, flags); + + /* Clear any sticky pending interrupts */ + litex_gpio_set_reg(gpio_s, LITEX_GPIO_PENDING_OFFSET, bit); + enable = litex_gpio_get_reg(gpio_s, LITEX_GPIO_ENABLE_OFFSET); + enable |= bit; + litex_gpio_set_reg(gpio_s, LITEX_GPIO_ENABLE_OFFSET, enable); + + spin_unlock_irqrestore(&gpio_s->gpio_lock, flags); +} + +static void litex_gpio_irq_mask(struct irq_data *idata) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(idata); + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + int offset = irqd_to_hwirq(idata) % GPIO_PINS_MAX; + unsigned long flags; + u32 bit = BIT(offset); + u32 enable; + + spin_lock_irqsave(&gpio_s->gpio_lock, flags); + + enable = litex_gpio_get_reg(gpio_s, LITEX_GPIO_ENABLE_OFFSET); + enable &= ~bit; + litex_gpio_set_reg(gpio_s, LITEX_GPIO_ENABLE_OFFSET, enable); + + spin_unlock_irqrestore(&gpio_s->gpio_lock, flags); +} + +static int litex_gpio_irq_set_type(struct irq_data *idata, unsigned int type) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(idata); + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + int offset = irqd_to_hwirq(idata) % GPIO_PINS_MAX; + unsigned long flags; + u32 bit = BIT(offset); + u32 mode, edge; + + spin_lock_irqsave(&gpio_s->gpio_lock, flags); + + mode = litex_gpio_get_reg(gpio_s, LITEX_GPIO_MODE_OFFSET); + edge = litex_gpio_get_reg(gpio_s, LITEX_GPIO_EDGE_OFFSET); + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_NONE: + break; + + case IRQ_TYPE_EDGE_RISING: + mode &= ~bit; + edge &= ~bit; + break; + + case IRQ_TYPE_EDGE_FALLING: + mode &= ~bit; + edge |= bit; + break; + + case IRQ_TYPE_EDGE_BOTH: + mode |= bit; + break; + + default: + return -EINVAL; + } + litex_gpio_set_reg(gpio_s, LITEX_GPIO_MODE_OFFSET, mode); + litex_gpio_set_reg(gpio_s, LITEX_GPIO_EDGE_OFFSET, edge); + + spin_unlock_irqrestore(&gpio_s->gpio_lock, flags); + + return 0; +} + +static void litex_gpio_irq_eoi(struct irq_data *idata) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(idata); + struct litex_gpio *gpio_s = gpiochip_get_data(chip); + int offset = irqd_to_hwirq(idata) % GPIO_PINS_MAX; + u32 bit = BIT(offset); + unsigned long flags; + + spin_lock_irqsave(&gpio_s->gpio_lock, flags); + + /* Clear all pending interrupts */ + litex_gpio_set_reg(gpio_s, LITEX_GPIO_PENDING_OFFSET, bit); + + spin_unlock_irqrestore(&gpio_s->gpio_lock, flags); + + irq_chip_eoi_parent(idata); +} + +static int litex_gpio_irq_set_affinity(struct irq_data *idata, + const struct cpumask *dest, + bool force) +{ + if (idata->parent_data) + return irq_chip_set_affinity_parent(idata, dest, force); + + return -EINVAL; +} + +static int litex_gpio_child_to_parent_hwirq(struct gpio_chip *chip, + unsigned int child, + unsigned int child_type, + unsigned int *parent, + unsigned int *parent_type) +{ + *parent = chip->irq.child_offset_to_irq(chip, child); + *parent_type = child_type; + + return 0; +} + +static void litex_gpio_irq(struct irq_desc *desc) +{ + struct litex_gpio *gpio_s = irq_desc_get_handler_data(desc); + struct irq_domain *domain = gpio_s->chip.irq.domain; + struct irq_chip *ichip = irq_desc_get_chip(desc); + u32 enabled; + u32 pending; + unsigned long int interrupts_to_handle; + unsigned int pin, irq; + + chained_irq_enter(ichip, desc); + + enabled = litex_gpio_get_reg(gpio_s, LITEX_GPIO_ENABLE_OFFSET); + pending = litex_gpio_get_reg(gpio_s, LITEX_GPIO_PENDING_OFFSET); + interrupts_to_handle = pending & enabled; + + for_each_set_bit(pin, &interrupts_to_handle, GPIO_PINS_MAX) { + irq = irq_find_mapping(domain, pin); + if (WARN_ON(irq == 0)) + continue; + + generic_handle_irq(irq); + } + + chained_irq_exit(ichip, desc); +} + +/* Driver functions */ + +static int litex_gpio_init_irq(struct platform_device *pdev, + struct litex_gpio *gpio_s) +{ + struct device_node *node = pdev->dev.of_node; + struct device_node *irq_parent = of_irq_find_parent(node); + struct irq_domain *parent_domain; + struct gpio_irq_chip *gichip; + + if (!irq_parent) { + dev_info(&pdev->dev, "no IRQ parent node\n"); + return 0; + } + + parent_domain = irq_find_host(irq_parent); + if (!parent_domain) { + dev_err(&pdev->dev, "no IRQ parent domain\n"); + return -ENODEV; + } + + gpio_s->irq_number = platform_get_irq(pdev, 0); + + /* Disable all GPIO interrupts before enabling parent interrupts */ + litex_gpio_set_reg(gpio_s, LITEX_GPIO_ENABLE_OFFSET, 0); + + gpio_s->ichip.name = pdev->name; + gpio_s->ichip.irq_unmask = litex_gpio_irq_unmask; + gpio_s->ichip.irq_mask = litex_gpio_irq_mask; + gpio_s->ichip.irq_set_type = litex_gpio_irq_set_type; + gpio_s->ichip.irq_eoi = litex_gpio_irq_eoi; + gpio_s->ichip.irq_set_affinity = litex_gpio_irq_set_affinity; + + gichip = &gpio_s->chip.irq; + gichip->chip = &gpio_s->ichip; + gichip->fwnode = of_node_to_fwnode(node); + gichip->parent_domain = parent_domain; + gichip->child_to_parent_hwirq = litex_gpio_child_to_parent_hwirq; + gichip->handler = handle_bad_irq; + gichip->default_type = IRQ_TYPE_NONE; + gichip->parent_handler = litex_gpio_irq; + gichip->parent_handler_data = gpio_s; + gichip->num_parents = 1; + gichip->parents = &gpio_s->irq_number; + + return 0; +} + +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; + + spin_lock_init(&gpio_s->gpio_lock); + + 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; + + if (gpio_s->port_direction == GPIOF_DIR_IN) { + ret_i = litex_gpio_init_irq(pdev, gpio_s); + if (ret_i < 0) + return ret_i; + } + + 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 b1f22e457fd0c9..d74ab44f3a7dcc 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -390,6 +390,8 @@ source "drivers/gpu/drm/gud/Kconfig" source "drivers/gpu/drm/sprd/Kconfig" +source "drivers/gpu/drm/litevideo/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && MMU && HYPERV diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 301a44dc18e340..db4a656fc093b9 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -135,3 +135,4 @@ obj-y += xlnx/ obj-y += gud/ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-$(CONFIG_DRM_SPRD) += sprd/ +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 8df25f1079bac5..60115caee98eb4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1190,6 +1190,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 185f946d698b0c..940bf474df4dc5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -100,6 +100,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 8a6c6ee28556fe..08f2abdc153cc6 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -728,6 +728,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 1d00dce77098b0..8ba3b534989530 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -72,6 +72,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 7038957f4a77a9..886e94a6593a3f 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -554,6 +554,27 @@ config LOONGSON_HTPIC help Support for the Loongson-3 HyperTransport PIC Controller. +config LITEX_VEXRISCV_INTC + bool "LiteX VexRiscv interrupt controller support" + depends on RISCV + depends on LITEX || COMPILE_TEST + 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" depends on MACH_LOONGSON64 diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c1f611cbfbf882..dc8dc5889ae104 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..72d4a8be2d4997 --- /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); + generic_handle_domain_irq(root_domain, i); + } + } +} + +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 52b0b27a6839b8..af6c3c329076fe 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1094,3 +1094,16 @@ config MMC_OWL config MMC_SDHCI_EXTERNAL_DMA bool + +config MMC_LITEX + tristate "LiteX MMC Host Controller support" + depends on ((PPC_MICROWATT || LITEX) && OF && HAVE_CLK) || COMPILE_TEST + select REGULATOR + select REGULATOR_FIXED_VOLTAGE + help + This selects support for the MMC Host Controller found in LiteX SoCs. + + To compile this driver as a module, choose M here: the + module will be called litex_mmc. + + If unsure, say N. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index ea36d379bd3ca0..4e4ceb32c4b4b1 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -101,6 +101,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..74a283e36a4788 --- /dev/null +++ b/drivers/mmc/host/litex_mmc.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LiteX LiteSDCard driver + * + * Copyright (C) 2019-2020 Antmicro + * Copyright (C) 2019-2020 Kamil Rakoczy + * Copyright (C) 2019-2020 Maciej Dudek + * Copyright (C) 2020 Paul Mackerras + * Copyright (C) 2020-2022 Gabriel Somlo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define LITEX_PHY_CARDDETECT 0x00 +#define LITEX_PHY_CLOCKERDIV 0x04 +#define LITEX_PHY_INITIALIZE 0x08 +#define LITEX_PHY_WRITESTATUS 0x0C +#define LITEX_CORE_CMDARG 0x00 +#define LITEX_CORE_CMDCMD 0x04 +#define LITEX_CORE_CMDSND 0x08 +#define LITEX_CORE_CMDRSP 0x0C +#define LITEX_CORE_CMDEVT 0x1C +#define LITEX_CORE_DATEVT 0x20 +#define LITEX_CORE_BLKLEN 0x24 +#define LITEX_CORE_BLKCNT 0x28 +#define LITEX_BLK2MEM_BASE 0x00 +#define LITEX_BLK2MEM_LEN 0x08 +#define LITEX_BLK2MEM_ENA 0x0C +#define LITEX_BLK2MEM_DONE 0x10 +#define LITEX_BLK2MEM_LOOP 0x14 +#define LITEX_MEM2BLK_BASE 0x00 +#define LITEX_MEM2BLK_LEN 0x08 +#define LITEX_MEM2BLK_ENA 0x0C +#define LITEX_MEM2BLK_DONE 0x10 +#define LITEX_MEM2BLK_LOOP 0x14 +#define LITEX_MEM2BLK 0x18 +#define LITEX_IRQ_STATUS 0x00 +#define LITEX_IRQ_PENDING 0x04 +#define LITEX_IRQ_ENABLE 0x08 + +#define SD_CTL_DATA_XFER_NONE 0 +#define SD_CTL_DATA_XFER_READ 1 +#define SD_CTL_DATA_XFER_WRITE 2 + +#define SD_CTL_RESP_NONE 0 +#define SD_CTL_RESP_SHORT 1 +#define SD_CTL_RESP_LONG 2 +#define SD_CTL_RESP_SHORT_BUSY 3 + +#define SD_BIT_DONE BIT(0) +#define SD_BIT_WR_ERR BIT(1) +#define SD_BIT_TIMEOUT BIT(2) +#define SD_BIT_CRC_ERR BIT(3) + +#define SD_SLEEP_US 5 +#define SD_TIMEOUT_US 20000 + +#define SDIRQ_CARD_DETECT 1 +#define SDIRQ_SD_TO_MEM_DONE 2 +#define SDIRQ_MEM_TO_SD_DONE 4 +#define SDIRQ_CMD_DONE 8 + +struct litex_mmc_host { + struct mmc_host *mmc; + + void __iomem *sdphy; + void __iomem *sdcore; + void __iomem *sdreader; + void __iomem *sdwriter; + void __iomem *sdirq; + + void *buffer; + size_t buf_size; + dma_addr_t dma; + + struct completion cmd_done; + int irq; + + unsigned int ref_clk; + unsigned int sd_clk; + + u32 resp[4]; + u16 rca; + + bool is_bus_width_set; + bool app_cmd; +}; + +static int litex_mmc_sdcard_wait_done(void __iomem *reg, struct device *dev) +{ + u8 evt; + int ret; + + ret = readx_poll_timeout(litex_read8, reg, evt, evt & SD_BIT_DONE, + SD_SLEEP_US, SD_TIMEOUT_US); + if (ret) + return ret; + if (evt == SD_BIT_DONE) + return 0; + if (evt & SD_BIT_WR_ERR) + return -EIO; + if (evt & SD_BIT_TIMEOUT) + return -ETIMEDOUT; + if (evt & SD_BIT_CRC_ERR) + return -EILSEQ; + dev_err(dev, "%s: unknown error (evt=%x)\n", __func__, evt); + return -EINVAL; +} + +static int litex_mmc_send_cmd(struct litex_mmc_host *host, + u8 cmd, u32 arg, u8 response_len, u8 transfer) +{ + struct device *dev = mmc_dev(host->mmc); + void __iomem *reg; + int ret; + u8 evt; + + litex_write32(host->sdcore + LITEX_CORE_CMDARG, arg); + litex_write32(host->sdcore + LITEX_CORE_CMDCMD, + cmd << 8 | transfer << 5 | response_len); + litex_write8(host->sdcore + LITEX_CORE_CMDSND, 1); + + /* + * Wait for an interrupt if we have an interrupt and either there is + * data to be transferred, or if the card can report busy via DAT0. + */ + if (host->irq > 0 && + (transfer != SD_CTL_DATA_XFER_NONE || + response_len == SD_CTL_RESP_SHORT_BUSY)) { + reinit_completion(&host->cmd_done); + litex_write32(host->sdirq + LITEX_IRQ_ENABLE, + SDIRQ_CMD_DONE | SDIRQ_CARD_DETECT); + wait_for_completion(&host->cmd_done); + } + + ret = litex_mmc_sdcard_wait_done(host->sdcore + LITEX_CORE_CMDEVT, dev); + if (ret) { + dev_err(dev, "Command (cmd %d) error, status %d\n", cmd, ret); + return ret; + } + + if (response_len != SD_CTL_RESP_NONE) { + /* + * NOTE: this matches the semantics of litex_read32() + * regardless of underlying arch endianness! + */ + memcpy_fromio(host->resp, + host->sdcore + LITEX_CORE_CMDRSP, 0x10); + } + + if (!host->app_cmd && cmd == SD_SEND_RELATIVE_ADDR) + host->rca = (host->resp[3] >> 16); + + host->app_cmd = (cmd == MMC_APP_CMD); + + if (transfer == SD_CTL_DATA_XFER_NONE) + return ret; /* OK from prior litex_mmc_sdcard_wait_done() */ + + ret = litex_mmc_sdcard_wait_done(host->sdcore + LITEX_CORE_DATEVT, dev); + if (ret) { + dev_err(dev, "Data xfer (cmd %d) error, status %d\n", cmd, ret); + return ret; + } + + /* Wait for completion of (read or write) DMA transfer */ + reg = (transfer == SD_CTL_DATA_XFER_READ) ? + host->sdreader + LITEX_BLK2MEM_DONE : + host->sdwriter + LITEX_MEM2BLK_DONE; + ret = readx_poll_timeout(litex_read8, reg, evt, evt & SD_BIT_DONE, + SD_SLEEP_US, SD_TIMEOUT_US); + if (ret) + dev_err(dev, "DMA timeout (cmd %d)\n", cmd); + + return ret; +} + +static int litex_mmc_send_app_cmd(struct litex_mmc_host *host) +{ + return litex_mmc_send_cmd(host, MMC_APP_CMD, host->rca << 16, + SD_CTL_RESP_SHORT, SD_CTL_DATA_XFER_NONE); +} + +static int litex_mmc_send_set_bus_w_cmd(struct litex_mmc_host *host, u32 width) +{ + return litex_mmc_send_cmd(host, SD_APP_SET_BUS_WIDTH, width, + SD_CTL_RESP_SHORT, SD_CTL_DATA_XFER_NONE); +} + +static int litex_mmc_set_bus_width(struct litex_mmc_host *host) +{ + bool app_cmd_sent; + int ret; + + if (host->is_bus_width_set) + return 0; + + /* Ensure 'app_cmd' precedes 'app_set_bus_width_cmd' */ + app_cmd_sent = host->app_cmd; /* was preceding command app_cmd? */ + if (!app_cmd_sent) { + ret = litex_mmc_send_app_cmd(host); + if (ret) + return ret; + } + + /* LiteSDCard only supports 4-bit bus width */ + ret = litex_mmc_send_set_bus_w_cmd(host, MMC_BUS_WIDTH_4); + if (ret) + return ret; + + /* Re-send 'app_cmd' if necessary */ + if (app_cmd_sent) { + ret = litex_mmc_send_app_cmd(host); + if (ret) + return ret; + } + + host->is_bus_width_set = true; + + return 0; +} + +static int litex_mmc_get_cd(struct mmc_host *mmc) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + int ret; + + if (!mmc_card_is_removable(mmc)) + return 1; + + ret = mmc_gpio_get_cd(mmc); + if (ret >= 0) { + /* GPIO based card-detect explicitly specified in DTS */ + ret = !!ret; + } else { + /* Use gateware card-detect bit by default */ + ret = !litex_read8(host->sdphy + LITEX_PHY_CARDDETECT); + } + + if (ret) + return ret; + + /* Ensure bus width will be set (again) upon card (re)insertion */ + host->is_bus_width_set = false; + + return 0; +} + +static irqreturn_t litex_mmc_interrupt(int irq, void *arg) +{ + struct mmc_host *mmc = arg; + struct litex_mmc_host *host = mmc_priv(mmc); + u32 pending = litex_read32(host->sdirq + LITEX_IRQ_PENDING); + irqreturn_t ret = IRQ_NONE; + + /* Check for card change interrupt */ + if (pending & SDIRQ_CARD_DETECT) { + litex_write32(host->sdirq + LITEX_IRQ_PENDING, + SDIRQ_CARD_DETECT); + mmc_detect_change(mmc, msecs_to_jiffies(10)); + ret = IRQ_HANDLED; + } + + /* Check for command completed */ + if (pending & SDIRQ_CMD_DONE) { + /* Disable it so it doesn't keep interrupting */ + litex_write32(host->sdirq + LITEX_IRQ_ENABLE, + SDIRQ_CARD_DETECT); + complete(&host->cmd_done); + ret = IRQ_HANDLED; + } + + return ret; +} + +static u32 litex_mmc_response_len(struct mmc_command *cmd) +{ + if (cmd->flags & MMC_RSP_136) + return SD_CTL_RESP_LONG; + if (!(cmd->flags & MMC_RSP_PRESENT)) + return SD_CTL_RESP_NONE; + if (cmd->flags & MMC_RSP_BUSY) + return SD_CTL_RESP_SHORT_BUSY; + return SD_CTL_RESP_SHORT; +} + +static void litex_mmc_do_dma(struct litex_mmc_host *host, struct mmc_data *data, + unsigned int *len, bool *direct, u8 *transfer) +{ + struct device *dev = mmc_dev(host->mmc); + dma_addr_t dma; + int sg_count; + + /* + * Try to DMA directly to/from the data buffer. + * We can do that if the buffer can be mapped for DMA + * in one contiguous chunk. + */ + dma = host->dma; + *len = data->blksz * data->blocks; + sg_count = dma_map_sg(dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); + if (sg_count == 1) { + dma = sg_dma_address(data->sg); + *len = sg_dma_len(data->sg); + *direct = true; + } else if (*len > host->buf_size) + *len = host->buf_size; + + if (data->flags & MMC_DATA_READ) { + litex_write8(host->sdreader + LITEX_BLK2MEM_ENA, 0); + litex_write64(host->sdreader + LITEX_BLK2MEM_BASE, dma); + litex_write32(host->sdreader + LITEX_BLK2MEM_LEN, *len); + litex_write8(host->sdreader + LITEX_BLK2MEM_ENA, 1); + *transfer = SD_CTL_DATA_XFER_READ; + } else if (data->flags & MMC_DATA_WRITE) { + if (!*direct) + sg_copy_to_buffer(data->sg, data->sg_len, + host->buffer, *len); + litex_write8(host->sdwriter + LITEX_MEM2BLK_ENA, 0); + litex_write64(host->sdwriter + LITEX_MEM2BLK_BASE, dma); + litex_write32(host->sdwriter + LITEX_MEM2BLK_LEN, *len); + litex_write8(host->sdwriter + LITEX_MEM2BLK_ENA, 1); + *transfer = SD_CTL_DATA_XFER_WRITE; + } else { + dev_warn(dev, "Data present w/o read or write flag.\n"); + /* Continue: set cmd status, mark req done */ + } + + litex_write16(host->sdcore + LITEX_CORE_BLKLEN, data->blksz); + litex_write32(host->sdcore + LITEX_CORE_BLKCNT, data->blocks); +} + +static void litex_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + struct device *dev = mmc_dev(mmc); + struct mmc_command *cmd = mrq->cmd; + struct mmc_command *sbc = mrq->sbc; + struct mmc_data *data = mrq->data; + struct mmc_command *stop = mrq->stop; + unsigned int retries = cmd->retries; + unsigned int len = 0; + bool direct = false; + u32 response_len = litex_mmc_response_len(cmd); + u8 transfer = SD_CTL_DATA_XFER_NONE; + + /* First check that the card is still there */ + if (!litex_mmc_get_cd(mmc)) { + cmd->error = -ENOMEDIUM; + mmc_request_done(mmc, mrq); + return; + } + + /* Send set-block-count command if needed */ + if (sbc) { + sbc->error = litex_mmc_send_cmd(host, sbc->opcode, sbc->arg, + litex_mmc_response_len(sbc), + SD_CTL_DATA_XFER_NONE); + if (sbc->error) { + host->is_bus_width_set = false; + mmc_request_done(mmc, mrq); + return; + } + } + + 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! + */ + cmd->error = litex_mmc_set_bus_width(host); + if (cmd->error) { + dev_err(dev, "Can't set bus width!\n"); + mmc_request_done(mmc, mrq); + return; + } + + litex_mmc_do_dma(host, data, &len, &direct, &transfer); + } + + do { + cmd->error = litex_mmc_send_cmd(host, cmd->opcode, cmd->arg, + response_len, transfer); + } while (cmd->error && retries-- > 0); + + if (cmd->error) { + /* Card may be gone; don't assume bus width is still set */ + host->is_bus_width_set = false; + } + + if (response_len == SD_CTL_RESP_SHORT) { + /* Pull short response fields from appropriate host registers */ + cmd->resp[0] = host->resp[3]; + cmd->resp[1] = host->resp[2] & 0xFF; + } else if (response_len == SD_CTL_RESP_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]; + } + + /* Send stop-transmission command if required */ + if (stop && (cmd->error || !sbc)) { + stop->error = litex_mmc_send_cmd(host, stop->opcode, stop->arg, + litex_mmc_response_len(stop), + SD_CTL_DATA_XFER_NONE); + if (stop->error) + host->is_bus_width_set = false; + } + + if (data) { + dma_unmap_sg(dev, data->sg, data->sg_len, + mmc_get_dma_dir(data)); + } + + if (!cmd->error && transfer != SD_CTL_DATA_XFER_NONE) { + data->bytes_xfered = min(len, mmc->max_req_size); + if (transfer == SD_CTL_DATA_XFER_READ && !direct) { + sg_copy_from_buffer(data->sg, sg_nents(data->sg), + host->buffer, data->bytes_xfered); + } + } + + mmc_request_done(mmc, mrq); +} + +static void litex_mmc_setclk(struct litex_mmc_host *host, unsigned int freq) +{ + struct device *dev = mmc_dev(host->mmc); + u32 div; + + div = freq ? host->ref_clk / freq : 256U; + div = roundup_pow_of_two(div); + div = clamp(div, 2U, 256U); + dev_dbg(dev, "sd_clk_freq=%d: set to %d via div=%d\n", + freq, host->ref_clk / div, div); + litex_write16(host->sdphy + LITEX_PHY_CLOCKERDIV, div); + host->sd_clk = freq; +} + +static void litex_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct litex_mmc_host *host = mmc_priv(mmc); + + /* + * NOTE: Ignore any ios->bus_width updates; they occur right after + * the mmc core sends its own acmd6 bus-width change notification, + * which is redundant since we snoop on the command flow and inject + * an early acmd6 before the first data transfer command is sent! + */ + + /* Update sd_clk */ + if (ios->clock != host->sd_clk) + litex_mmc_setclk(host, ios->clock); +} + +static const struct mmc_host_ops litex_mmc_ops = { + .get_cd = litex_mmc_get_cd, + .request = litex_mmc_request, + .set_ios = litex_mmc_set_ios, +}; + +static int litex_mmc_irq_init(struct platform_device *pdev, + struct litex_mmc_host *host) +{ + struct device *dev = mmc_dev(host->mmc); + int ret; + + ret = platform_get_irq_optional(pdev, 0); + if (ret < 0 && ret != -ENXIO) + return ret; + if (ret > 0) + host->irq = ret; + else { + dev_warn(dev, "Failed to get IRQ, using polling\n"); + goto use_polling; + } + + host->sdirq = devm_platform_ioremap_resource_byname(pdev, "irq"); + if (IS_ERR(host->sdirq)) + return PTR_ERR(host->sdirq); + + ret = devm_request_irq(dev, host->irq, litex_mmc_interrupt, 0, + "litex-mmc", host->mmc); + if (ret < 0) { + dev_warn(dev, "IRQ request error %d, using polling\n", ret); + goto use_polling; + } + + /* Clear & enable card-change interrupts */ + litex_write32(host->sdirq + LITEX_IRQ_PENDING, SDIRQ_CARD_DETECT); + litex_write32(host->sdirq + LITEX_IRQ_ENABLE, SDIRQ_CARD_DETECT); + + return 0; + +use_polling: + host->mmc->caps |= MMC_CAP_NEEDS_POLL; + return 0; +} + +static void litex_mmc_free_host_wrapper(void *mmc) +{ + mmc_free_host(mmc); +} + +static int litex_mmc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct litex_mmc_host *host; + struct mmc_host *mmc; + struct clk *clk; + int ret; + + /* + * NOTE: defaults to max_[req,seg]_size=PAGE_SIZE, max_blk_size=512, + * and max_blk_count accordingly set to 8; + * If for some reason we need to modify max_blk_count, we must also + * re-calculate `max_[req,seg]_size = max_blk_size * max_blk_count;` + */ + mmc = mmc_alloc_host(sizeof(struct litex_mmc_host), dev); + if (!mmc) + return -ENOMEM; + + ret = devm_add_action_or_reset(dev, litex_mmc_free_host_wrapper, mmc); + if (ret) + return dev_err_probe(dev, ret, + "Can't register mmc_free_host action\n"); + + host = mmc_priv(mmc); + host->mmc = mmc; + + /* Initialize clock source */ + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "can't get clock\n"); + host->ref_clk = clk_get_rate(clk); + host->sd_clk = 0; + + /* + * 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; + + /* LiteSDCard can support 64-bit DMA addressing */ + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + host->buf_size = mmc->max_req_size * 2; + host->buffer = dmam_alloc_coherent(dev, host->buf_size, + &host->dma, GFP_KERNEL); + if (host->buffer == NULL) + return -ENOMEM; + + host->sdphy = devm_platform_ioremap_resource_byname(pdev, "phy"); + if (IS_ERR(host->sdphy)) + return PTR_ERR(host->sdphy); + + host->sdcore = devm_platform_ioremap_resource_byname(pdev, "core"); + if (IS_ERR(host->sdcore)) + return PTR_ERR(host->sdcore); + + host->sdreader = devm_platform_ioremap_resource_byname(pdev, "reader"); + if (IS_ERR(host->sdreader)) + return PTR_ERR(host->sdreader); + + host->sdwriter = devm_platform_ioremap_resource_byname(pdev, "writer"); + if (IS_ERR(host->sdwriter)) + return PTR_ERR(host->sdwriter); + + /* Ensure DMA bus masters are disabled */ + litex_write8(host->sdreader + LITEX_BLK2MEM_ENA, 0); + litex_write8(host->sdwriter + LITEX_MEM2BLK_ENA, 0); + + init_completion(&host->cmd_done); + ret = litex_mmc_irq_init(pdev, host); + if (ret) + return ret; + + mmc->ops = &litex_mmc_ops; + + ret = mmc_regulator_get_supply(mmc); + if (ret || mmc->ocr_avail == 0) { + dev_warn(dev, "can't get voltage, defaulting to 3.3V\n"); + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + } + + /* + * Set default sd_clk frequency range based on empirical observations + * of LiteSDCard gateware behavior on typical SDCard media + */ + mmc->f_min = 12.5e6; + mmc->f_max = 50e6; + + ret = mmc_of_parse(mmc); + if (ret) + return ret; + + /* Force 4-bit bus_width (only width supported by hardware) */ + mmc->caps &= ~MMC_CAP_8_BIT_DATA; + mmc->caps |= MMC_CAP_4_BIT_DATA; + + /* Set default capabilities */ + mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | + MMC_CAP_DRIVER_TYPE_D | + MMC_CAP_CMD23; + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT | + MMC_CAP2_NO_SDIO | + MMC_CAP2_NO_MMC; + + platform_set_drvdata(pdev, host); + + ret = mmc_add_host(mmc); + if (ret) + return ret; + + dev_info(dev, "LiteX MMC controller initialized.\n"); + return 0; +} + +static int litex_mmc_remove(struct platform_device *pdev) +{ + struct litex_mmc_host *host = platform_get_drvdata(pdev); + + mmc_remove_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 = { + .probe = litex_mmc_probe, + .remove = litex_mmc_remove, + .driver = { + .name = "litex-mmc", + .of_match_table = litex_match, + }, +}; +module_platform_driver(litex_mmc_driver); + +MODULE_DESCRIPTION("LiteX SDCard driver"); +MODULE_AUTHOR("Antmicro "); +MODULE_AUTHOR("Kamil Rakoczy "); +MODULE_AUTHOR("Maciej Dudek "); +MODULE_AUTHOR("Paul Mackerras "); +MODULE_AUTHOR("Gabriel Somlo "); +MODULE_LICENSE("GPL v2"); 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 6b904e43937289..042f1a7391a931 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/litex/litex_liteeth.c b/drivers/net/ethernet/litex/litex_liteeth.c index fdd99f0de424dc..724893c5c1e889 100644 --- a/drivers/net/ethernet/litex/litex_liteeth.c +++ b/drivers/net/ethernet/litex/litex_liteeth.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,10 @@ struct liteeth { struct device *dev; u32 slot_size; + /* Polling support */ + int use_polling; + struct timer_list poll_timer; + /* Tx */ u32 tx_slot; u32 num_tx_slots; @@ -112,6 +117,14 @@ static irqreturn_t liteeth_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } +static void liteeth_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); @@ -121,10 +134,16 @@ static int liteeth_open(struct net_device *netdev) litex_write8(priv->base + LITEETH_WRITER_EV_PENDING, 1); litex_write8(priv->base + LITEETH_READER_EV_PENDING, 1); - err = request_irq(netdev->irq, liteeth_interrupt, 0, netdev->name, netdev); - if (err) { - netdev_err(netdev, "failed to request irq %d\n", netdev->irq); - return err; + if (priv->use_polling) { + timer_setup(&priv->poll_timer, liteeth_timeout, 0); + mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(50)); + } else { + err = request_irq(netdev->irq, liteeth_interrupt, 0, netdev->name, netdev); + if (err) { + netdev_err(netdev, "failed to request irq %d\n", netdev->irq); + return err; + } + } /* Enable IRQs */ @@ -147,7 +166,11 @@ static int liteeth_stop(struct net_device *netdev) litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE, 0); litex_write8(priv->base + LITEETH_READER_EV_ENABLE, 0); - free_irq(netdev->irq, netdev); + if (priv->use_polling) { + del_timer_sync(&priv->poll_timer); + } else { + free_irq(netdev->irq, netdev); + } return 0; } @@ -241,9 +264,13 @@ static int liteeth_probe(struct platform_device *pdev) priv->netdev = netdev; priv->dev = &pdev->dev; + priv->use_polling = 0; irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ, using polling\n"); + priv->use_polling = 1; + irq = 0; + } netdev->irq = irq; priv->base = devm_platform_ioremap_resource_byname(pdev, "mac"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 21e3b05a5153a9..15f145c6384fbe 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -292,6 +292,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 708840b7fba8d8..b8f969c92cd092 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..c6b99b6e098341 --- /dev/null +++ b/drivers/pwm/pwm-litex.c @@ -0,0 +1,179 @@ +// 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); + + pwmchip_remove(&litex->chip); + return 0; +} + +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 b2a8821971e1d1..7826a2314f30b1 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -434,6 +434,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 dd7393a6046fa4..c88b673fbd459b 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_SPI_IMX) += spi-imx.o obj-$(CONFIG_SPI_INGENIC) += spi-ingenic.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..7b02b6792ddc67 --- /dev/null +++ b/drivers/spi/spi-litespi.c @@ -0,0 +1,202 @@ +// 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 4 +#define LITESPI_SZ_MISO 4 +#define LITESPI_SZ_CS 1 + +/* register offsets */ +#define LITESPI_OFF_CTRL 0x00 +#define LITESPI_OFF_STAT 0x04 +#define LITESPI_OFF_MOSI 0x08 +#define LITESPI_OFF_MISO 0x0C +#define LITESPI_OFF_CS 0x10 + +#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 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 chip select */ + litex_write8(hw->base + LITESPI_OFF_CS, BIT(spi->chip_select)); + + /* 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 in a litex subregister + */ + 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->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 > 8*sizeof(u32)) { // (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); diff --git a/include/linux/litex.h b/include/linux/litex.h index f2edb86d5f44c7..e373f5e7a24014 100644 --- a/include/linux/litex.h +++ b/include/linux/litex.h @@ -80,4 +80,75 @@ static inline u64 litex_read64(void __iomem *reg) _read_litex_subregister(reg + 4); } +/* FIXME: some LiteX drivers still write to a programmatically computed CSR + * size, which should really be rewritten to use fixed-size accessors (see + * above). The list currently includes: + * - drivers/gpio/gpio-litex.c + * - drivers/clk/clk-litex.c + * For now, we provide arbitrary-size CSR accessors below, for compatibility: + */ + +#define LITEX_SUBREG_SIZE 0x4 +#define LITEX_SUBREG_SIZE_BIT (LITEX_SUBREG_SIZE * 8) +#define LITEX_SUBREG_ALIGN 0x4 + +/* number of LiteX subregisters needed to store a register of given reg_size */ +#define _litex_num_subregs(reg_size) \ + (((reg_size) - 1) / LITEX_SUBREG_SIZE + 1) + +/* + * The purpose of `_litex_[set|get]_reg()` is to implement the logic of + * writing to/reading from the LiteX CSR in a single place that can be then + * reused by all LiteX drivers via the `litex_[write|read][8|16|32|64]()` + * accessors for the appropriate data width. + * NOTE: direct use of `_litex_[set|get]_reg()` by LiteX drivers is strongly + * discouraged, as they perform no error checking on the requested data width! + */ + +/** + * _litex_set_reg() - Writes a value to the LiteX CSR (Control&Status Register) + * @reg: Address of the CSR + * @reg_size: The width of the CSR expressed in the number of bytes + * @val: Value to be written to the CSR + * + * This function splits a single (possibly multi-byte) LiteX CSR write into + * a series of subregister writes with a proper offset. + * NOTE: caller is responsible for ensuring (0 < reg_size <= sizeof(u64)). + */ +static inline void _litex_set_reg(void __iomem *reg, size_t reg_size, u64 val) +{ + u8 shift = _litex_num_subregs(reg_size) * LITEX_SUBREG_SIZE_BIT; + + while (shift > 0) { + shift -= LITEX_SUBREG_SIZE_BIT; + _write_litex_subregister(val >> shift, reg); + reg += LITEX_SUBREG_ALIGN; + } +} + +/** + * _litex_get_reg() - Reads a value of the LiteX CSR (Control&Status Register) + * @reg: Address of the CSR + * @reg_size: The width of the CSR expressed in the number of bytes + * + * Return: Value read from the CSR + * + * This function generates a series of subregister reads with a proper offset + * and joins their results into a single (possibly multi-byte) LiteX CSR value. + * NOTE: caller is responsible for ensuring (0 < reg_size <= sizeof(u64)). + */ +static inline u64 _litex_get_reg(void __iomem *reg, size_t reg_size) +{ + u64 r; + u8 i; + + r = _read_litex_subregister(reg); + for (i = 1; i < _litex_num_subregs(reg_size); i++) { + r <<= LITEX_SUBREG_SIZE_BIT; + reg += LITEX_SUBREG_ALIGN; + r |= _read_litex_subregister(reg); + } + return r; +} + #endif /* _LINUX_LITEX_H */